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
12pub mod structs;
14pub use resource_fork_macros::Resource;
15
16pub 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 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
255pub 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
264pub 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#[derive(ThisError, Debug)]
300pub enum Error {
301 #[error("Resource fork could not be found")]
303 NotFound,
304
305 #[error("Resource type {} does not exist", .0.to_string())]
307 ResourceTypeNotFound(fourcc::FourCC),
308
309 #[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 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}