pelite\resources/
mod.rs

1/*!
2Resources.
3*/
4
5use std::prelude::v1::*;
6
7use std::{char, fmt, iter, mem, slice};
8
9use crate::{Pod, Error, Result};
10use crate::image::*;
11
12//----------------------------------------------------------------
13
14mod find;
15pub use self::find::FindError;
16
17mod art;
18
19pub mod version_info;
20pub mod group;
21
22//----------------------------------------------------------------
23
24/// Resources filesystem.
25#[derive(Copy, Clone)]
26pub struct Resources<'a> {
27	section: &'a [u8],
28	dir: &'a IMAGE_DATA_DIRECTORY,
29}
30impl<'a> Resources<'a> {
31	/// Parse the bytes as PE resources.
32	///
33	/// No validation or integrity checking is done ahead of time.
34	pub fn new(section: &'a [u8], dir: &'a IMAGE_DATA_DIRECTORY) -> Resources<'a> {
35		// All offsets _except_ the data entry offsets are relative to the resource directory.
36		// Data entry offsets are relative virtual addresses from the PE image.
37		// Microsoft... Why would you do this?
38		Resources { section, dir }
39	}
40	/// Gets the root directory.
41	pub fn root(&self) -> Result<Directory<'a>> {
42		Directory::try_from(*self, 0)
43	}
44	/// Filesystem consistency check.
45	///
46	/// Simply walks the filesystem checking all references are valid.
47	pub fn fsck(&self) -> Result<()> {
48		self.root()?.fsck()
49	}
50
51	#[inline]
52	fn slice<T>(&self, offset: u32) -> Result<&'a T> where T: Pod {
53		let start = offset as usize;
54		let end = mem::size_of::<T>().wrapping_add(start);
55		// Alignment checking
56		if !cfg!(feature = "unsafe_alignment") && start & (mem::align_of::<T>() - 1) != 0 {
57			return Err(Error::Misaligned);
58		}
59		// Range checking done by the indexing operator
60		let bytes = self.section.get(start..end).ok_or(Error::Bounds)?;
61		// Safe because size and alignment are checked and T is Pod
62		Ok(unsafe { &*(bytes.as_ptr() as *const T) })
63	}
64	#[inline]
65	#[allow(dead_code)] // unused for now...
66	fn slice_len<T>(&self, offset: u32, len: usize) -> Result<&'a [T]> where T: Pod {
67		let start = offset as usize;
68		let size_of = mem::size_of::<T>().checked_mul(len).ok_or(Error::Overflow)?;
69		let end = start.wrapping_add(size_of);
70		// Alignment checking
71		if !cfg!(feature = "unsafe_alignment") && start & (mem::align_of::<T>() - 1) != 0 {
72			return Err(Error::Misaligned);
73		}
74		// Range checking done by the indexing operator
75		let bytes = self.section.get(start..end).ok_or(Error::Bounds)?;
76		Ok(unsafe { slice::from_raw_parts(bytes.as_ptr() as *const T, len) })
77	}
78	#[inline]
79	fn slice_ws(&self, offset: u32) -> Result<&'a [u16]> {
80		let offset = offset as usize;
81		// Alignment checking
82		if !cfg!(feature = "unsafe_alignment") && offset & 1 != 0 {
83			return Err(Error::Misaligned);
84		}
85		// The name is prefixed by its length in words
86		let len = self.section.get(offset..offset + 2).ok_or(Error::Bounds)?;
87		let len = unsafe { *(len.as_ptr() as *const u16) } as usize;
88		// Extract the name given its length
89		let name = self.section.get(offset + 2..offset + 2 + len * 2).ok_or(Error::Bounds)?;
90		let name = unsafe { slice::from_raw_parts(name.as_ptr() as *const u16, len) };
91		Ok(name)
92	}
93}
94impl<'a> fmt::Debug for Resources<'a> {
95	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96		f.write_str("Resources { .. }")
97	}
98}
99
100//----------------------------------------------------------------
101
102/// Directory.
103#[derive(Copy, Clone)]
104pub struct Directory<'a> {
105	resources: Resources<'a>,
106	image: &'a IMAGE_RESOURCE_DIRECTORY,
107}
108impl<'a> Directory<'a> {
109	fn try_from(resources: Resources<'a>, offset: u32) -> Result<Directory<'a>> {
110		let image: &IMAGE_RESOURCE_DIRECTORY = resources.slice(offset)?;
111		// Validate the number of directory entries
112		// This code has been carefully written to avoid panicking on overflow
113		// It also validates the unsafe blocks below cf. size and alignment
114		let entries_size = (image.NumberOfNamedEntries as usize + image.NumberOfIdEntries as usize) * mem::size_of::<IMAGE_RESOURCE_DIRECTORY_ENTRY>();
115		let entries_offset = offset as usize + mem::size_of::<IMAGE_RESOURCE_DIRECTORY>();
116		if entries_size > resources.section.len() - entries_offset {
117			return Err(Error::Bounds);
118		}
119		Ok(Directory { resources, image })
120	}
121	/// Gets the resources.
122	pub fn resources(&self) -> Resources<'a> {
123		self.resources
124	}
125	/// Gets the underlying resource directory image.
126	pub fn image(&self) -> &'a IMAGE_RESOURCE_DIRECTORY {
127		self.image
128	}
129	/// Gets the directory entries.
130	pub fn entries(&self) -> Entries<'a, impl Clone + FnMut(&'a IMAGE_RESOURCE_DIRECTORY_ENTRY) -> DirectoryEntry<'a>> {
131		// Validated by constructor
132		let slice = unsafe {
133			let p = (self.image as *const IMAGE_RESOURCE_DIRECTORY).offset(1) as *const IMAGE_RESOURCE_DIRECTORY_ENTRY;
134			let len = self.image.NumberOfNamedEntries as usize + self.image.NumberOfIdEntries as usize;
135			slice::from_raw_parts(p, len)
136		};
137		let resources = self.resources;
138		slice.iter().map(move |image| DirectoryEntry { resources, image })
139	}
140	/// Gets the named entries in this directory.
141	///
142	/// Note that while it would be a violation of the format spec, there's no strict safety guarantee that these are only named entries.
143	pub fn named_entries(&self) -> Entries<'a, impl Clone + FnMut(&'a IMAGE_RESOURCE_DIRECTORY_ENTRY) -> DirectoryEntry<'a>> {
144		// Validated by constructor
145		let slice = unsafe {
146			// Named entries come first in the array (see chapter "PE File Resources" in "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format")
147			let p = (self.image as *const IMAGE_RESOURCE_DIRECTORY).offset(1) as *const IMAGE_RESOURCE_DIRECTORY_ENTRY;
148			let len = self.image.NumberOfNamedEntries as usize;
149			slice::from_raw_parts(p, len)
150		};
151		let resources = self.resources;
152		slice.iter().map(move |image| DirectoryEntry { resources, image })
153	}
154	/// Gets the id entries in this directory.
155	///
156	/// Note that while it would be a violation of the format spec, there's no strict safety guarantee that these are only id entries.
157	pub fn id_entries(&self) -> Entries<'a, impl Clone + FnMut(&'a IMAGE_RESOURCE_DIRECTORY_ENTRY) -> DirectoryEntry<'a>> {
158		// Validated by the constructor
159		let slice = unsafe {
160			// Id entries come last in the array
161			let p = ((self.image as *const IMAGE_RESOURCE_DIRECTORY).offset(1) as *const IMAGE_RESOURCE_DIRECTORY_ENTRY).offset(self.image.NumberOfNamedEntries as isize);
162			let len = self.image.NumberOfIdEntries as usize;
163			slice::from_raw_parts(p, len)
164		};
165		let resources = self.resources;
166		slice.iter().map(move |image| DirectoryEntry { resources, image })
167	}
168	/// Filesystem consistency check.
169	///
170	/// Simply walks the filesystem checking all references are valid.
171	pub fn fsck(&self) -> Result<()> {
172		self.entries().try_for_each(|e| e.fsck())
173	}
174}
175impl<'a> fmt::Debug for Directory<'a> {
176	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177		f.debug_struct("Directory")
178			.field("entries", &self.entries())
179			.finish()
180	}
181}
182
183//----------------------------------------------------------------
184
185/// Iterator over entries in a directory.
186pub type Entries<'a, F> = iter::Map<slice::Iter<'a, IMAGE_RESOURCE_DIRECTORY_ENTRY>, F>;
187
188//----------------------------------------------------------------
189
190/// Represents a resource name.
191#[derive(Copy, Clone, Debug, Eq)]
192pub enum Name<'a> {
193	/// Resource ID.
194	///
195	/// Technically allows `u32` ids, but some Windows APIs will be unable to use resources with an id which isn't `u16`.
196	Id(u32),
197	/// UTF-16 named resource.
198	Wide(&'a [u16]),
199	/// UTF-8 named resource.
200	///
201	/// This variant is used when accepting user input and will be interpreted liberally when compared against other names:
202	/// When prefixed with '#' the string is parsed as a u32 and compared to resource ids.
203	/// Otherwise compares against wide strings by doing an unicode aware case sensitive comparison.
204	Str(&'a str),
205}
206/// Predefined resource name constants.
207impl<'a> Name<'a> {
208	pub const MANIFEST: Name<'a> = Name::Id(crate::image::RT_MANIFEST as u32);
209	pub const VERSION: Name<'a> = Name::Id(crate::image::RT_VERSION as u32);
210	pub const GROUP_ICON: Name<'a> = Name::Id(crate::image::RT_GROUP_ICON as u32);
211	pub const GROUP_CURSOR: Name<'a> = Name::Id(crate::image::RT_GROUP_CURSOR as u32);
212}
213impl<'a> Name<'a> {
214	#[inline(never)]
215	fn eq_string(&self, string: &str) -> bool {
216		match self {
217			&Name::Id(id) => {
218				// Resource id strings must start with #
219				if !(string.len() >= 2 && string.as_bytes()[0] == b'#') {
220					false
221				}
222				// Followed by an integer resource id
223				else if string.as_bytes()[1] > b'0' && string.as_bytes()[1] <= b'9' {
224					match string[1..].parse::<u32>() {
225						Ok(string_id) if id == string_id => true,
226						_ => false,
227					}
228				}
229				// Followed by a predefined resource type name
230				else {
231					match RSRC_TYPES.get(id as usize) {
232						Some(&Some(name)) if string == name => true,
233						_ => false,
234					}
235				}
236			},
237			&Name::Wide(words) => char::decode_utf16(words.iter().cloned()).eq(string.chars().map(Ok)),
238			&Name::Str(name) => string == name,
239		}
240	}
241	fn rename_id(self, names: &[Option<&'a str>]) -> Name<'a> {
242		if let Name::Id(id) = self {
243			if let Some(&Some(name)) = names.get(id as usize) {
244				return Name::Str(name);
245			}
246		}
247		self
248	}
249}
250impl<'a> From<u16> for Name<'a> {
251	fn from(id: u16) -> Name<'a> {
252		Name::Id(id as u32)
253	}
254}
255impl<'a> From<&'a [u16]> for Name<'a> {
256	fn from(words: &'a [u16]) -> Name<'a> {
257		Name::Wide(words)
258	}
259}
260impl<'a> From<&'a str> for Name<'a> {
261	fn from(name: &'a str) -> Name<'a> {
262		Name::Str(name)
263	}
264}
265impl PartialEq for Name<'_> {
266	#[inline(never)]
267	fn eq(&self, rhs: &Name<'_>) -> bool {
268		match (*self, *rhs) {
269			// Strict checking between ids and wide strings
270			(Name::Id(lhs), Name::Id(rhs)) => lhs == rhs,
271			(Name::Id(_), Name::Wide(_)) => false,
272			(Name::Wide(lhs), Name::Wide(rhs)) => lhs == rhs,
273			(Name::Wide(_), Name::Id(_)) => false,
274			// When comparing against Rust strings
275			(Name::Str(lhs), rhs) => rhs.eq_string(lhs),
276			(lhs, Name::Str(rhs)) => lhs.eq_string(rhs),
277		}
278	}
279}
280impl PartialEq<str> for Name<'_> {
281	fn eq(&self, rhs: &str) -> bool {
282		self.eq_string(rhs)
283	}
284}
285impl PartialEq<u32> for Name<'_> {
286	fn eq(&self, &rhs: &u32) -> bool {
287		match self {
288			&Name::Id(id) => id == rhs,
289			_ => false,
290		}
291	}
292}
293impl fmt::Display for Name<'_> {
294	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
295		match self {
296			Name::Id(id) => write!(f, "#{}", id),
297			Name::Wide(words) => {
298				for chr in char::decode_utf16(words.iter().cloned()) {
299					let chr = chr.unwrap_or(char::REPLACEMENT_CHARACTER);
300					fmt::Write::write_char(f, chr)?;
301				}
302				Ok(())
303			},
304			Name::Str(string) => f.write_str(string),
305		}
306	}
307}
308
309//----------------------------------------------------------------
310
311/// Data or directory entry.
312#[derive(Copy, Clone, Debug)]
313#[cfg_attr(feature = "serde", derive(::serde::Serialize), serde(untagged))]
314pub enum Entry<'a> {
315	Directory(Directory<'a>),
316	DataEntry(DataEntry<'a>),
317}
318impl<'a> Entry<'a> {
319	/// Returns some if the entry is a directory.
320	pub fn dir(self) -> Option<Directory<'a>> {
321		match self {
322			Entry::Directory(dir) => Some(dir),
323			Entry::DataEntry(_) => None,
324		}
325	}
326	/// Returns some if the entry is a data entry.
327	pub fn data(self) -> Option<DataEntry<'a>> {
328		match self {
329			Entry::Directory(_) => None,
330			Entry::DataEntry(data) => Some(data),
331		}
332	}
333}
334
335//----------------------------------------------------------------
336
337/// Directory child entry.
338///
339/// Contains a name and a reference to the associated data or directory entry.
340#[derive(Copy, Clone)]
341pub struct DirectoryEntry<'a> {
342	resources: Resources<'a>,
343	image: &'a IMAGE_RESOURCE_DIRECTORY_ENTRY,
344}
345impl<'a> DirectoryEntry<'a> {
346	/// Gets the resources.
347	pub fn resources(&self) -> Resources<'a> {
348		self.resources
349	}
350	/// Gets the underlying resource directory entry image.
351	pub fn image(&self) -> &'a IMAGE_RESOURCE_DIRECTORY_ENTRY {
352		self.image
353	}
354	/// Gets the name for this entry.
355	pub fn name(&self) -> Result<Name<'a>> {
356		if self.image.Name & 0x80000000 != 0 {
357			let offset = self.image.Name & !0x80000000;
358			let words = self.resources.slice_ws(offset)?;
359			Ok(Name::Wide(words))
360		}
361		else {
362			// TODO: What if this doesn't fit in u16...
363			Ok(Name::Id(self.image.Name))
364		}
365	}
366	/// Returns if this entry is a directory.
367	pub fn is_dir(&self) -> bool {
368		self.image.Offset & 0x80000000 != 0
369	}
370	/// Returns the directory or data entry for this entry.
371	pub fn entry(&self) -> Result<Entry<'a>> {
372		if self.is_dir() {
373			let offset = self.image.Offset & !0x80000000;
374			Directory::try_from(self.resources, offset).map(Entry::Directory)
375		}
376		else {
377			let offset = self.image.Offset;
378			DataEntry::try_from(self.resources, offset).map(Entry::DataEntry)
379		}
380	}
381	/// Filesystem consistency check.
382	///
383	/// Simply walks the filesystem checking all references are valid.
384	pub fn fsck(&self) -> Result<()> {
385		self.name()?;
386		match self.entry()? {
387			Entry::Directory(dir) => dir.fsck(),
388			Entry::DataEntry(data) => data.fsck(),
389		}
390	}
391}
392impl<'a> fmt::Debug for DirectoryEntry<'a> {
393	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
394		f.debug_struct("DirectoryEntry")
395			.field("name", &format_args!("{:?}", self.name()))
396			.field("entry", &if self.is_dir() { "Directory(..)" } else { "DataEntry(..)" })
397			.finish()
398	}
399}
400
401//----------------------------------------------------------------
402
403/// Data entry.
404#[derive(Copy, Clone)]
405pub struct DataEntry<'a> {
406	resources: Resources<'a>,
407	image: &'a IMAGE_RESOURCE_DATA_ENTRY,
408}
409impl<'a> DataEntry<'a> {
410	fn try_from(resources: Resources<'a>, offset: u32) -> Result<DataEntry<'a>> {
411		let image = resources.slice(offset)?;
412		Ok(DataEntry { resources, image })
413	}
414	/// Gets the resources.
415	pub fn resources(&self) -> Resources<'a> {
416		self.resources
417	}
418	/// Gets the underlying resource data entry image.
419	pub fn image(&self) -> &'a IMAGE_RESOURCE_DATA_ENTRY {
420		self.image
421	}
422	/// Gets the actual data.
423	pub fn bytes(&self) -> Result<&'a [u8]> {
424		let start = u32::checked_sub(self.image.OffsetToData, self.resources.dir.VirtualAddress).ok_or(Error::Overflow)?;
425		let end = u32::checked_add(start, self.image.Size).ok_or(Error::Overflow)?;
426		self.resources.section.get(start as usize..end as usize).ok_or(Error::Bounds)
427	}
428	/// Gets the data size.
429	pub fn size(&self) -> usize {
430		self.image.Size as usize
431	}
432	/// Gets the code page.
433	pub fn code_page(&self) -> u32 {
434		self.image.CodePage
435	}
436	/// Filesystem consistency check.
437	///
438	/// Simply walks the filesystem checking all references are valid.
439	pub fn fsck(&self) -> Result<()> {
440		self.bytes()?;
441		Ok(())
442	}
443}
444impl<'a> fmt::Debug for DataEntry<'a> {
445	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
446		f.debug_struct("DataEntry")
447			.field("data.len", &self.image.Size)
448			.finish()
449	}
450}
451
452//----------------------------------------------------------------
453
454static RSRC_TYPES: [Option<&str>; 25] = [
455	/* 0*/ None, Some("#CURSOR"), Some("#BITMAP"), Some("#ICON"), Some("#MENU"),
456	/* 5*/ Some("#DIALOG"), Some("#STRING"), Some("#FONTDIR"), Some("#FONT"), Some("#ACCELERATOR"),
457	/*10*/ Some("#RCDATA"), Some("#MESSAGETABLE"), Some("#GROUP_CURSOR"), None, Some("#GROUP_ICON"),
458	/*15*/ None, Some("#VERSION"), Some("#DLGINCLUDE"), None, Some("#PLUGPLAY"),
459	/*20*/ Some("#VXD"), Some("#ANICURSOR"), Some("#ANIICON"), Some("#HTML"), Some("#MANIFEST"),
460];
461
462//----------------------------------------------------------------
463
464/*
465	[
466		{
467			"name": 12,
468			"directory": [
469				{
470					"name": "IMPORTANT",
471					"data": {
472						address: 0x1000,
473						size: 1234,
474						code_page: 65001,
475					}
476				}
477			]
478		}
479	]
480*/
481
482#[cfg(feature = "serde")]
483mod serde {
484	use crate::util::serde_helper::*;
485	use super::{Resources, Directory, Name, DirectoryEntry, DataEntry};
486
487	// Rename the toplevel directory ids to their names
488	struct NamedDirectoryEntry<'a>(DirectoryEntry<'a>);
489	impl<'a> Serialize for NamedDirectoryEntry<'a> {
490		fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
491			let mut state = serializer.serialize_struct("DirectoryEntry", 2)?;
492			state.serialize_field("name", &self.0.name().ok().map(|name| name.rename_id(&super::RSRC_TYPES)))?;
493			state.serialize_field(if self.0.is_dir() { "directory" } else { "data" }, &self.0.entry().ok())?;
494			state.end()
495		}
496	}
497
498	impl<'a> Serialize for Resources<'a> {
499		fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
500			match self.root() {
501				Ok(root) => serializer.collect_seq(root.entries().map(NamedDirectoryEntry)),
502				Err(_) => serializer.serialize_none(),
503			}
504		}
505	}
506	impl<'a> Serialize for Directory<'a> {
507		fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
508			serializer.collect_seq(self.entries())
509		}
510	}
511	impl<'a> Serialize for Name<'a> {
512		fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
513			match self {
514				Name::Id(id) => id.serialize(serializer),
515				Name::Wide(words) => serializer.serialize_str(&String::from_utf16_lossy(words)),
516				Name::Str(name) => serializer.serialize_str(name),
517			}
518		}
519	}
520	impl<'a> Serialize for DirectoryEntry<'a> {
521		fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
522			let mut state = serializer.serialize_struct("DirectoryEntry", 2)?;
523			state.serialize_field("name", &self.name().ok())?;
524			state.serialize_field(if self.is_dir() { "directory" } else { "data" }, &self.entry().ok())?;
525			state.end()
526		}
527	}
528	impl<'a> Serialize for DataEntry<'a> {
529		fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
530			let mut state = serializer.serialize_struct("DataEntry", 3)?;
531			state.serialize_field("address", &self.image().OffsetToData)?;
532			state.serialize_field("size", &self.size())?;
533			state.serialize_field("code_page", &self.code_page())?;
534			state.end()
535		}
536	}
537}
538
539//----------------------------------------------------------------
540
541#[cfg(test)]
542pub(crate) fn test(resources: Resources<'_>) -> Result<()> {
543	fn test_dir(dir: Directory<'_>) {
544		let _ = format!("{}", dir);
545		let _ = format!("{:?}", dir);
546
547		for entry in dir.entries() {
548			let _ = format!("{:?}\n{:?}", entry.name(), entry);
549
550			// Check consistency in id vs named entries
551			if let Ok(name) = entry.name() {
552				let mut id_entries;
553				let mut named_entries;
554				let mut entries: &mut dyn Iterator<Item = _> = match name {
555					Name::Id(_) => { id_entries = dir.id_entries(); &mut id_entries },
556					Name::Wide(_) => { named_entries = dir.named_entries(); &mut named_entries },
557					Name::Str(_) => unreachable!()
558				};
559				assert!((&mut entries).any(|entry| entry.name() == Ok(name)));
560			}
561
562			// Inspect the entry recursively
563			match entry.entry() {
564				Ok(Entry::DataEntry(data)) => {
565					assert!(!entry.is_dir());
566					let _ = format!("{:?}", data);
567					let _size = data.size();
568					let _code_page = data.code_page();
569					let _bytes = data.bytes();
570				},
571				Ok(Entry::Directory(dir)) => {
572					assert!(entry.is_dir());
573					let _ = test_dir(dir);
574				},
575				Err(_) => (),
576			}
577		}
578	}
579	let _ = resources.fsck();
580	println!("{}", resources);
581	if let Ok(version_info) = resources.version_info() {
582		self::version_info::test(version_info)
583	}
584	resources.root().map(test_dir)
585}