pelite\resources/
group.rs

1/*!
2Group Icons and Cursors.
3
4References:
5
6* http://msdn.microsoft.com/en-us/library/ms997538.aspx
7* https://devblogs.microsoft.com/oldnewthing/20120720-00/?p=7083
8* https://github.com/MathewSachin/NIco/wiki/Ico,-Cur-and-PE-Formats
9
10# Examples
11
12The following example prints all group icon resource names which contain a PNG image.
13
14```
15// Aqcuire the resources of a Portable Executable file
16let resources: pelite::resources::Resources;
17
18# fn example(resources: pelite::resources::Resources<'_>) {
19// Iterate over the group icons in the resources and throw away any invalid results
20// If the resources contain no group icons the iterator is empty
21for (name, group) in resources.icons().filter_map(Result::ok) {
22	// Enumerate the entries in the group
23	for entry in group.entries() {
24		// Fetch the image data for this entry
25		match group.image(entry.nId) {
26			Ok(image) => {
27				// Check if the image data starts with the PNG magic bytes
28				if image.starts_with(b"\x89PNG") {
29					println!("{}: contains PNG", name);
30				}
31			},
32			Err(err) => {
33				println!("{}: Error {}!", name, err)
34			},
35		}
36	}
37}
38# }
39```
40
41 */
42
43use std::prelude::v1::*;
44
45#[cfg(feature = "std")]
46use std::io;
47
48use crate::util::AlignTo;
49use crate::Error;
50use std::{fmt, mem, slice};
51
52use super::{FindError, Resources};
53
54use self::image::*;
55
56//----------------------------------------------------------------
57
58/// Icon or Cursor type.
59#[derive(Copy, Clone, Debug, Eq, PartialEq)]
60pub enum ResourceType {
61	Icon,
62	Cursor,
63}
64impl ResourceType {
65	#[inline]
66	pub fn id(self) -> u16 {
67		match self {
68			ResourceType::Icon => crate::image::RT_ICON,
69			ResourceType::Cursor => crate::image::RT_CURSOR,
70		}
71	}
72}
73impl<'a> From<ResourceType> for super::Name<'a> {
74	fn from(resource_type: ResourceType) -> super::Name<'a> {
75		resource_type.id().into()
76	}
77}
78
79/// Group resources, Icons and Cursors.
80#[derive(Copy, Clone)]
81pub struct GroupResource<'a> {
82	resources: Resources<'a>,
83	image: &'a GRPICONDIR,
84}
85impl<'a> GroupResource<'a> {
86	/// Parses the GroupResource from the byte slice.
87	///
88	/// The pixel data of the group resource is stored in separate data entries, requiring the resources to access.
89	pub fn new(resources: Resources<'a>, bytes: &'a [u8]) -> Result<GroupResource<'a>, Error> {
90		if !bytes.as_ptr().aligned_to(2) {
91			return Err(Error::Misaligned);
92		}
93		if bytes.len() < mem::size_of::<GRPICONDIR>() {
94			return Err(Error::Bounds);
95		}
96		let image: &'a GRPICONDIR = unsafe { &*(bytes.as_ptr() as *const GRPICONDIR) };
97		if image.idReserved != 0 || !(image.idType == 1 || image.idType == 2) {
98			return Err(Error::BadMagic);
99		}
100		let total_size = mem::size_of::<GRPICONDIR>() + image.idCount as usize * mem::size_of::<GRPICONDIRENTRY>();
101		if bytes.len() != total_size {
102			return Err(Error::Bounds);
103		}
104		Ok(GroupResource { resources, image })
105	}
106	/// Gets the Group header.
107	pub fn header(&self) -> &'a GRPICONDIR {
108		self.image
109	}
110	/// Gets the Group entries.
111	pub fn entries(&self) -> &'a [GRPICONDIRENTRY] {
112		let len = self.image.idCount as usize;
113		// Checked by try_from constructor
114		unsafe {
115			let ptr = (self.image as *const GRPICONDIR).offset(1) as *const GRPICONDIRENTRY;
116			slice::from_raw_parts(ptr, len)
117		}
118	}
119	/// Gets the Group resource type.
120	pub fn ty(&self) -> ResourceType {
121		match self.image.idType {
122			1 => ResourceType::Icon,
123			2 => ResourceType::Cursor,
124			_ => unreachable!(), // Checked by constructor
125		}
126	}
127	/// Gets the image data for the given icon id.
128	pub fn image(&self, id: u16) -> Result<&'a [u8], FindError> {
129		self.resources.root()?
130			.get_dir(self.ty().into())?
131			.get_dir(id.into())?
132			.first_data()?
133			.bytes().map_err(FindError::Pe)
134	}
135	/// Reassemble the file.
136	#[cfg(feature = "std")]
137	pub fn write(&self, dest: &mut dyn io::Write) -> io::Result<()> {
138		// Start by appending the header
139		dest.write(dataview::bytes(self.image))?;
140		// Write all the icon entries
141		let entries = self.entries();
142		let mut image_offset = (6 + entries.len() * 16) as u32;
143		for entry in entries {
144			// Fixup the dwImageOffset field of the icon entry
145			// NOTE! It is expected that the actual icon data size matches dwBytesInRes information!
146			let mut icon_entry = [0u32; 4];
147			dataview::bytes_mut(&mut icon_entry)[..14].copy_from_slice(dataview::bytes(entry));
148			icon_entry[3] = image_offset;
149			image_offset += entry.bytes_in_resource();
150			dest.write(dataview::bytes(&icon_entry))?;
151		}
152		// Append the bytes for every entry
153		for entry in entries {
154			// Find the Icon data and append it
155			// FIXME! What do if dwBytesInRes does not match the icon data size?
156			// Ignoring this check may lead to corrupt icon files
157			if let Ok(bytes) = self.image(entry.nId) {
158				// assert_eq!(entry.bytes_in_resource() as usize, bytes.len());
159				dest.write(bytes)?;
160			}
161		}
162		Ok(())
163	}
164}
165
166impl fmt::Debug for GroupResource<'_> {
167	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168		f.debug_struct("GroupResource")
169			.field("type", &self.ty())
170			.field("entries.len", &self.entries().len())
171			.finish()
172	}
173}
174
175#[cfg(feature = "serde")]
176impl serde::Serialize for GroupResource<'_> {
177	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
178		let mut bytes = Vec::new();
179		mem::forget(self.write(&mut bytes));
180		#[cfg(feature = "data-encoding")]
181		{if serializer.is_human_readable() {
182			return serializer.serialize_str(&data_encoding::BASE64.encode(&bytes));
183		}}
184		serializer.serialize_bytes(&bytes)
185	}
186}
187
188/// Group Icon.
189pub type GroupIcon<'a> = GroupResource<'a>;
190/// Group Cursor.
191pub type GroupCursor<'a> = GroupResource<'a>;
192
193//----------------------------------------------------------------
194
195#[allow(non_snake_case)]
196pub mod image {
197	use crate::Pod;
198	#[derive(Copy, Clone, Debug)]
199	#[repr(C)]
200	pub struct GRPICONDIR {
201		pub idReserved: u16,
202		pub idType: u16,
203		pub idCount: u16,
204		pub idEntries: [GRPICONDIRENTRY; 0],
205	}
206	#[derive(Copy, Clone, Debug)]
207	#[repr(C)]
208	pub struct GRPICONDIRENTRY {
209		pub bWidth: u8,
210		pub bHeight: u8,
211		pub bColorCount: u8,
212		pub bReserved: u8,
213		pub wPlanes: u16,
214		pub wBitCount: u16,
215		pub dwBytesInResLo: u16,
216		pub dwBytesInResHi: u16,
217		pub nId: u16,
218	}
219	impl GRPICONDIRENTRY {
220		pub fn bytes_in_resource(&self) -> u32 {
221			self.dwBytesInResHi as u32 * 0x10000 + self.dwBytesInResLo as u32
222		}
223	}
224	unsafe impl Pod for GRPICONDIR {}
225	unsafe impl Pod for GRPICONDIRENTRY {}
226}