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 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}