Skip to main content

resource_fork/
lib.rs

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