object/read/pe/
resource.rs

1use alloc::string::String;
2use core::char;
3
4use crate::endian::{LittleEndian as LE, U16Bytes};
5use crate::pe;
6use crate::read::{ReadError, ReadRef, Result};
7
8/// The `.rsrc` section of a PE file.
9///
10/// Returned by [`DataDirectories::resource_directory`](super::DataDirectories::resource_directory).
11#[derive(Debug, Clone, Copy)]
12pub struct ResourceDirectory<'data> {
13    data: &'data [u8],
14}
15
16impl<'data> ResourceDirectory<'data> {
17    /// Construct from the data of the `.rsrc` section.
18    pub fn new(data: &'data [u8]) -> Self {
19        ResourceDirectory { data }
20    }
21
22    /// Parses the root resource directory.
23    pub fn root(&self) -> Result<ResourceDirectoryTable<'data>> {
24        ResourceDirectoryTable::parse(self.data, 0)
25    }
26}
27
28/// A table of resource entries.
29#[derive(Debug, Clone)]
30pub struct ResourceDirectoryTable<'data> {
31    /// The table header.
32    pub header: &'data pe::ImageResourceDirectory,
33    /// The table entries.
34    pub entries: &'data [pe::ImageResourceDirectoryEntry],
35}
36
37impl<'data> ResourceDirectoryTable<'data> {
38    fn parse(data: &'data [u8], offset: u32) -> Result<Self> {
39        let mut offset = u64::from(offset);
40        let header = data
41            .read::<pe::ImageResourceDirectory>(&mut offset)
42            .read_error("Invalid resource table header")?;
43        let entries_count = header.number_of_id_entries.get(LE) as usize
44            + header.number_of_named_entries.get(LE) as usize;
45        let entries = data
46            .read_slice::<pe::ImageResourceDirectoryEntry>(&mut offset, entries_count)
47            .read_error("Invalid resource table entries")?;
48        Ok(Self { header, entries })
49    }
50}
51
52impl pe::ImageResourceDirectoryEntry {
53    /// Returns true if the entry has a name, rather than an ID.
54    pub fn has_name(&self) -> bool {
55        self.name_or_id.get(LE) & pe::IMAGE_RESOURCE_NAME_IS_STRING != 0
56    }
57
58    /// Returns the section offset of the name.
59    ///
60    /// Valid if `has_name()` returns true.
61    fn name(&self) -> ResourceName {
62        let offset = self.name_or_id.get(LE) & !pe::IMAGE_RESOURCE_NAME_IS_STRING;
63        ResourceName { offset }
64    }
65
66    /// Returns the ID.
67    ///
68    /// Valid if `has_string_name()` returns false.
69    fn id(&self) -> u16 {
70        (self.name_or_id.get(LE) & 0x0000_FFFF) as u16
71    }
72
73    /// Returns the entry name
74    pub fn name_or_id(&self) -> ResourceNameOrId {
75        if self.has_name() {
76            ResourceNameOrId::Name(self.name())
77        } else {
78            ResourceNameOrId::Id(self.id())
79        }
80    }
81
82    /// Returns true if the entry is a subtable.
83    pub fn is_table(&self) -> bool {
84        self.offset_to_data_or_directory.get(LE) & pe::IMAGE_RESOURCE_DATA_IS_DIRECTORY != 0
85    }
86
87    /// Returns the section offset of the associated table or data.
88    pub fn data_offset(&self) -> u32 {
89        self.offset_to_data_or_directory.get(LE) & !pe::IMAGE_RESOURCE_DATA_IS_DIRECTORY
90    }
91
92    /// Returns the data associated to this directory entry.
93    pub fn data<'data>(
94        &self,
95        section: ResourceDirectory<'data>,
96    ) -> Result<ResourceDirectoryEntryData<'data>> {
97        if self.is_table() {
98            ResourceDirectoryTable::parse(section.data, self.data_offset())
99                .map(ResourceDirectoryEntryData::Table)
100        } else {
101            section
102                .data
103                .read_at::<pe::ImageResourceDataEntry>(self.data_offset().into())
104                .read_error("Invalid resource entry")
105                .map(ResourceDirectoryEntryData::Data)
106        }
107    }
108}
109
110/// Data associated with a resource directory entry.
111#[derive(Debug, Clone)]
112pub enum ResourceDirectoryEntryData<'data> {
113    /// A subtable entry.
114    Table(ResourceDirectoryTable<'data>),
115    /// A resource data entry.
116    Data(&'data pe::ImageResourceDataEntry),
117}
118
119impl<'data> ResourceDirectoryEntryData<'data> {
120    /// Converts to an option of table.
121    ///
122    /// Helper for iterator filtering.
123    pub fn table(self) -> Option<ResourceDirectoryTable<'data>> {
124        match self {
125            Self::Table(dir) => Some(dir),
126            _ => None,
127        }
128    }
129
130    /// Converts to an option of data entry.
131    ///
132    /// Helper for iterator filtering.
133    pub fn data(self) -> Option<&'data pe::ImageResourceDataEntry> {
134        match self {
135            Self::Data(rsc) => Some(rsc),
136            _ => None,
137        }
138    }
139}
140
141/// A resource name.
142#[derive(Debug, Clone, Copy)]
143pub struct ResourceName {
144    offset: u32,
145}
146
147impl ResourceName {
148    /// Converts to a `String`.
149    pub fn to_string_lossy(&self, directory: ResourceDirectory<'_>) -> Result<String> {
150        let d = self.data(directory)?.iter().map(|c| c.get(LE));
151
152        Ok(char::decode_utf16(d)
153            .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER))
154            .collect::<String>())
155    }
156
157    /// Returns the string unicode buffer.
158    pub fn data<'data>(
159        &self,
160        directory: ResourceDirectory<'data>,
161    ) -> Result<&'data [U16Bytes<LE>]> {
162        let mut offset = u64::from(self.offset);
163        let len = directory
164            .data
165            .read::<U16Bytes<LE>>(&mut offset)
166            .read_error("Invalid resource name offset")?;
167        directory
168            .data
169            .read_slice::<U16Bytes<LE>>(&mut offset, len.get(LE).into())
170            .read_error("Invalid resource name length")
171    }
172
173    /// Returns the string buffer as raw bytes.
174    pub fn raw_data<'data>(&self, directory: ResourceDirectory<'data>) -> Result<&'data [u8]> {
175        self.data(directory).map(crate::pod::bytes_of_slice)
176    }
177}
178
179/// A resource name or ID.
180///
181/// Can be either a string or a numeric ID.
182#[derive(Debug)]
183pub enum ResourceNameOrId {
184    /// A resource name.
185    Name(ResourceName),
186    /// A resource ID.
187    Id(u16),
188}
189
190impl ResourceNameOrId {
191    /// Converts to an option of name.
192    ///
193    /// Helper for iterator filtering.
194    pub fn name(self) -> Option<ResourceName> {
195        match self {
196            Self::Name(name) => Some(name),
197            _ => None,
198        }
199    }
200
201    /// Converts to an option of ID.
202    ///
203    /// Helper for iterator filtering.
204    pub fn id(self) -> Option<u16> {
205        match self {
206            Self::Id(id) => Some(id),
207            _ => None,
208        }
209    }
210}