Skip to main content

resource_fork/
lib.rs

1#![doc = include_str!("../README.md")]
2use std::{collections::HashSet, fmt::Debug, io::SeekFrom, path::Path};
3
4use binrw::{BinReaderExt, Error as BinError};
5use macintosh_utils::{fourcc, FourCC, PascalString};
6use structs::{ReferenceListItem, ResourceHeader, ResourceMap, TypeMapItem};
7use thiserror::Error as ThisError;
8
9mod discovery;
10use discovery::{DefaultStrategy, Strategy};
11
12/// On-disk structures used in the binary representation of resource forks
13pub mod structs;
14pub use resource_fork_macros::Resource;
15
16/// Reader for reading resource forks in a structured way
17pub struct ResourceFork<R: std::io::Read + std::io::Seek + Sized> {
18    reader: R,
19}
20
21impl<Reader: std::io::Read + std::io::Seek + Sized> From<Reader> for ResourceFork<Reader> {
22    fn from(reader: Reader) -> Self {
23        Self { reader }
24    }
25}
26
27impl<Reader: std::io::Read + std::io::Seek + Sized> ResourceFork<Reader> {
28    pub fn into_reader(self) -> Reader {
29        self.reader
30    }
31
32    pub fn type_list(&mut self) -> Result<Vec<FourCC>, Error> {
33        self.reader.seek(SeekFrom::Start(0))?;
34        let resource_header: ResourceHeader = self.reader.read_be()?;
35
36        self.reader
37            .seek(SeekFrom::Start(resource_header.resource_map_offset as u64))?;
38        let resource_map: ResourceMap = self.reader.read_be()?;
39
40        self.reader.seek(SeekFrom::Start(
41            resource_header.resource_map_offset as u64 + resource_map.type_list_offset as u64 + 2,
42        ))?;
43
44        let count = resource_map.last_type_index as usize + 1;
45        let mut resource_types = Vec::with_capacity(count);
46        for _ in 0..count {
47            let item: TypeMapItem = self.reader.read_be()?;
48            resource_types.push(item.resource_type);
49        }
50        Ok(resource_types)
51    }
52
53    pub fn list_resources(&mut self, resource_type: FourCC) -> Result<Vec<u16>, Error> {
54        self.reader.seek(SeekFrom::Start(0))?;
55        let resource_header: ResourceHeader = self.reader.read_be()?;
56
57        self.reader
58            .seek(SeekFrom::Start(resource_header.resource_map_offset as u64))?;
59        let resource_map: ResourceMap = self.reader.read_be()?;
60
61        self.reader.seek(SeekFrom::Start(
62            resource_header.resource_map_offset as u64 + resource_map.type_list_offset as u64 + 2,
63        ))?;
64
65        for _ in 0..(resource_map.last_type_index as usize + 1) {
66            let item: TypeMapItem = self.reader.read_be()?;
67            if item.resource_type != resource_type {
68                continue;
69            }
70
71            self.reader.seek(SeekFrom::Start(
72                resource_header.resource_map_offset as u64
73                    + resource_map.type_list_offset as u64
74                    + item.reference_list_offset as u64,
75            ))?;
76
77            let count = item.last_resource_index as usize + 1;
78            let mut resources = Vec::with_capacity(count);
79            for _ in 0..count {
80                let item: ReferenceListItem = self.reader.read_be()?;
81                resources.push(item.resource_id.cast_unsigned());
82            }
83            return Ok(resources);
84        }
85
86        Err(Error::ResourceTypeNotFound(resource_type))
87    }
88
89    pub fn list_resources_with_name(
90        &mut self,
91        resource_type: FourCC,
92    ) -> Result<Vec<(u16, Option<String>)>, Error> {
93        self.reader.seek(SeekFrom::Start(0))?;
94        let resource_header: ResourceHeader = self.reader.read_be()?;
95
96        self.reader
97            .seek(SeekFrom::Start(resource_header.resource_map_offset as u64))?;
98        let resource_map: ResourceMap = self.reader.read_be()?;
99
100        self.reader.seek(SeekFrom::Start(
101            resource_header.resource_map_offset as u64 + resource_map.type_list_offset as u64 + 2,
102        ))?;
103
104        for _ in 0..(resource_map.last_type_index as usize + 1) {
105            let item: TypeMapItem = self.reader.read_be()?;
106            if item.resource_type != resource_type {
107                continue;
108            }
109
110            self.reader.seek(SeekFrom::Start(
111                resource_header.resource_map_offset as u64
112                    + resource_map.type_list_offset as u64
113                    + item.reference_list_offset as u64,
114            ))?;
115
116            let count = item.last_resource_index as usize + 1;
117            let mut resources = Vec::with_capacity(count);
118            for _ in 0..count {
119                let item: ReferenceListItem = self.reader.read_be()?;
120                let id = item.resource_id.cast_unsigned();
121                let name = if item.resource_name_offset == -1 {
122                    None
123                } else {
124                    assert!(item.resource_name_offset >= 0);
125                    let current = self.reader.stream_position()?;
126                    self.reader.seek(SeekFrom::Start(
127                        resource_header.resource_map_offset as u64
128                            + resource_map.name_list_offset as u64
129                            + item.resource_name_offset as u64,
130                    ))?;
131                    let str: PascalString = self.reader.read_be()?;
132                    self.reader.seek(SeekFrom::Start(current))?;
133                    Some(str.contents())
134                };
135
136                resources.push((id, name));
137            }
138            return Ok(resources);
139        }
140
141        Err(Error::ResourceTypeNotFound(resource_type))
142    }
143
144    pub fn read_data(
145        &mut self,
146        resource_type: FourCC,
147        resource_id: u16,
148    ) -> Result<(Vec<u8>, Option<String>), Error> {
149        self.reader.seek(SeekFrom::Start(0))?;
150        let resource_header: ResourceHeader = self.reader.read_be()?;
151
152        self.reader
153            .seek(SeekFrom::Start(resource_header.resource_map_offset as u64))?;
154        let resource_map: ResourceMap = self.reader.read_be()?;
155
156        self.reader.seek(SeekFrom::Start(
157            resource_header.resource_map_offset as u64 + resource_map.type_list_offset as u64 + 2,
158        ))?;
159
160        for _ in 0..(resource_map.last_type_index as usize + 1) {
161            let item: TypeMapItem = self.reader.read_be()?;
162            if item.resource_type != resource_type {
163                continue;
164            }
165
166            self.reader.seek(SeekFrom::Start(
167                resource_header.resource_map_offset as u64
168                    + resource_map.type_list_offset as u64
169                    + item.reference_list_offset as u64,
170            ))?;
171
172            let count = item.last_resource_index as usize + 1;
173            for _ in 0..count {
174                let item: ReferenceListItem = self.reader.read_be()?;
175                let id = item.resource_id.cast_unsigned();
176                if id != resource_id {
177                    continue;
178                }
179
180                let name = if item.resource_name_offset == -1 {
181                    None
182                } else {
183                    assert!(item.resource_name_offset >= 0);
184                    let current = self.reader.stream_position()?;
185                    self.reader.seek(SeekFrom::Start(
186                        resource_header.resource_map_offset as u64
187                            + resource_map.name_list_offset as u64
188                            + item.resource_name_offset as u64,
189                    ))?;
190                    let str: PascalString = self.reader.read_be()?;
191                    self.reader.seek(SeekFrom::Start(current))?;
192                    Some(str.contents())
193                };
194
195                self.reader.seek(SeekFrom::Start(
196                    resource_header.resource_data_offset as u64 + item.resource_data_offset as u64,
197                ))?;
198                let size: u32 = self.reader.read_be()?;
199                if item.resource_data_offset as u64 + size as u64
200                    > resource_header.resource_data_len as u64
201                {
202                    eprintln!(
203                        "WARNING: Resource is too large for data length ({resource_type} {resource_id})"
204                    );
205                }
206
207                let mut resource_data = vec![0u8; size as usize];
208                self.reader.read_exact(&mut resource_data)?;
209
210                return Ok((resource_data, name));
211            }
212
213            return Err(Error::ResourceNotFound(resource_id, resource_type));
214        }
215
216        Err(Error::ResourceTypeNotFound(resource_type))
217    }
218
219    pub fn try_read_data(
220        &mut self,
221        resource_type: FourCC,
222        id: u16,
223    ) -> Result<Option<Vec<u8>>, Error> {
224        match self.read_data(resource_type, id) {
225            Ok((value, _)) => Ok(Some(value)),
226            Err(Error::NotFound) => Ok(None),
227            Err(Error::ResourceNotFound(_, _)) => Ok(None),
228            Err(Error::ResourceTypeNotFound(_)) => Ok(None),
229            Err(e) => Err(e),
230        }
231    }
232}
233
234impl ResourceFork<std::fs::File> {
235    /// Open resource fork at the given path
236    pub fn open<P: AsRef<Path>>(path: P) -> Result<ResourceFork<std::fs::File>, Error> {
237        Ok(ResourceFork::from(DefaultStrategy.discover(path)?))
238    }
239}
240
241impl<T: std::io::Read + std::io::Seek + Sized> Debug for ResourceFork<T> {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        f.debug_struct("ResourceFork").finish()
244    }
245}
246
247impl<R: std::io::Read + std::io::Seek + Clone> Clone for ResourceFork<R> {
248    fn clone(&self) -> Self {
249        Self {
250            reader: self.reader.clone(),
251        }
252    }
253}
254
255/// Implementing this trait allow reading structs transparently from a resource fork
256pub trait ResourceReading {
257    const FOURCC: FourCC;
258
259    fn from_slice(buffer: &[u8], id: u16, name: &str) -> Result<Self, Error>
260    where
261        Self: Sized;
262}
263
264/// Interface for reading typed resource from a resource fork
265pub trait ResourceForkReader {
266    fn list_types(&mut self) -> Result<HashSet<FourCC>, Error>;
267    fn list<Rsrc: ResourceReading>(&mut self) -> Result<Vec<u16>, Error>;
268    fn read<Rsrc: ResourceReading>(&mut self, id: u16) -> Result<Rsrc, Error>;
269    fn try_read<Rsrc: ResourceReading>(&mut self, id: u16) -> Result<Option<Rsrc>, Error>;
270}
271
272impl<R: std::io::Read + std::io::Seek + Sized> ResourceForkReader for ResourceFork<R> {
273    fn list_types(&mut self) -> Result<HashSet<FourCC>, Error> {
274        Ok(ResourceFork::type_list(self)?.into_iter().collect())
275    }
276
277    fn list<Rsrc: ResourceReading>(&mut self) -> Result<Vec<u16>, Error> {
278        self.list_resources(Rsrc::FOURCC)
279    }
280
281    fn read<Rsrc: ResourceReading>(&mut self, id: u16) -> Result<Rsrc, Error> {
282        let (data, name) = &self.read_data(Rsrc::FOURCC, id)?;
283        let name = name.clone().unwrap_or_default();
284        Rsrc::from_slice(data, id, &name)
285    }
286
287    fn try_read<Rsrc: ResourceReading>(&mut self, id: u16) -> Result<Option<Rsrc>, Error> {
288        match self.read::<Rsrc>(id) {
289            Err(Error::NotFound) => Ok(None),
290            Err(Error::ResourceTypeNotFound(_)) => Ok(None),
291            Err(Error::ResourceNotFound(_, _)) => Ok(None),
292            Ok(rsrc) => Ok(Some(rsrc)),
293            Err(e) => Err(e),
294        }
295    }
296}
297
298/// General error used to describe parsing errors
299#[derive(ThisError, Debug)]
300pub enum Error {
301    /// No resource fork could be found
302    #[error("Resource fork could not be found")]
303    NotFound,
304
305    /// The requested resource type does not exist
306    #[error("Resource type {} does not exist", .0.to_string())]
307    ResourceTypeNotFound(fourcc::FourCC),
308
309    /// The requested resource does not exist
310    #[error("Resource {} of type {} does not exist", .0, .1.to_string())]
311    ResourceNotFound(u16, fourcc::FourCC),
312
313    #[error(transparent)]
314    IO(#[from] std::io::Error),
315
316    #[error(transparent)]
317    BinRw(#[from] BinError),
318
319    #[error("Resource parsing failed")]
320    // Can't be bothered to find out how to wrap winnow errors
321    ParsingError,
322
323    #[error("Resource parsing failed")]
324    Other(Box<dyn std::error::Error>),
325}
326
327#[cfg(test)]
328mod tests {
329    use std::path::PathBuf;
330
331    use macintosh_utils::fourcc as fcc;
332
333    use super::*;
334
335    #[test]
336    fn test_listing_resource_types() {
337        let mut fork = open_fixture("ThreeTypes.rsrc");
338        let discovered_types = fork.type_list().unwrap();
339
340        assert_eq!(
341            discovered_types,
342            vec![fcc!("TYP1"), fcc!("TYP2"), fcc!("TYP3")]
343        );
344    }
345
346    #[test]
347    fn test_listing_resources_of_type() {
348        let mut fork = open_fixture("ThreeTypes.rsrc");
349        let resource_ids = fork.list_resources(fcc!("TYP1")).unwrap();
350        assert_eq!(resource_ids, vec![128, 129]);
351
352        let mut fork = open_fixture("ThreeTypes.rsrc");
353        let resource_ids = fork.list_resources(fcc!("TYP3")).unwrap();
354        assert_eq!(resource_ids, vec![128]);
355    }
356
357    #[test]
358    fn test_listing_resources_of_type_with_name() {
359        let mut fork = open_fixture("ThreeTypes.rsrc");
360        let resource_ids = fork.list_resources_with_name(fcc!("TYP1")).unwrap();
361        assert_eq!(
362            resource_ids,
363            vec![(128, Some("Typ1.1".to_string())), (129, None)]
364        );
365
366        let mut fork = open_fixture("ThreeTypes.rsrc");
367        let resource_ids = fork.list_resources_with_name(fcc!("TYP2")).unwrap();
368        assert_eq!(resource_ids, vec![(128, None)]);
369
370        let mut fork = open_fixture("ThreeTypes.rsrc");
371        let resource_ids = fork.list_resources_with_name(fcc!("TYP3")).unwrap();
372        assert_eq!(resource_ids, vec![(128, None)]);
373    }
374
375    #[test]
376    fn test_reading_data() {
377        let mut fork = open_fixture("ThreeTypes.rsrc");
378        let (data, name) = fork.read_data(fcc!("TYP1"), 128).unwrap();
379        assert_eq!(data, vec![171u8, 205]);
380        assert_eq!(name, Some(String::from("Typ1.1")));
381
382        let mut fork = open_fixture("ThreeTypes.rsrc");
383        let (data, name) = fork.read_data(fcc!("TYP1"), 129).unwrap();
384        assert_eq!(data, vec![239, 1]);
385        assert_eq!(name, None);
386
387        let mut fork = open_fixture("ThreeTypes.rsrc");
388        let (data, name) = fork.read_data(fcc!("TYP3"), 128).unwrap();
389        assert_eq!(data, vec![]);
390        assert_eq!(name, None);
391    }
392
393    fn open_fixture(name: &'static str) -> ResourceFork<std::fs::File> {
394        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
395            .join("test/")
396            .join(name);
397
398        std::fs::File::open(path)
399            .unwrap_or_else(|_| panic!("Could not open test fixture {name}"))
400            .into()
401    }
402}