resource_dll_parser/lib.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![forbid(unsafe_code)]
5
6//! Package to agnostically parse a resource dll for a given ID.
7//!
8//! # Example
9//! ```no_run
10//! use resource_dll_parser::{DllResourceDescriptor, try_find_resource_from_dll};
11//! use fs_err::File;
12//!
13//! # fn main() -> anyhow::Result<()> {
14//! let file = File::open("vmfirmware.dll")?;
15//! let descriptor = DllResourceDescriptor::new(b"VMFW", 13515);
16//! let resource = try_find_resource_from_dll(&file, &descriptor)?;
17//! # Ok(())
18//! # }
19//! ```
20
21use anyhow::Context;
22use anyhow::bail;
23use fs_err::File;
24use object::LittleEndian;
25use object::ReadCache;
26use object::read::pe::PeFile64;
27
28/// Tries to read the given resource from a resource dll. If the given data
29/// buffer is not a valid PE file this function returns Ok(None). If it is a PE
30/// file, but the given resource can not be found or loaded this function
31/// returns Err(...). On success the return value contains the starting offset
32/// into the file and its length.
33/// TODO: change the return types to a proper enum with variants like 'NotPeFile, NotFound, Ok(u64, usize)'
34pub fn try_find_resource_from_dll(
35 file: &File,
36 descriptor: &DllResourceDescriptor,
37) -> anyhow::Result<Option<(u64, usize)>> {
38 let data = &ReadCache::new(file);
39 if let Ok(pe_file) = PeFile64::parse(data) {
40 let rsrc = pe_file
41 .data_directories()
42 .resource_directory(data, &pe_file.section_table())?
43 .context("no resource section")?;
44
45 let type_match = rsrc
46 .root()?
47 .entries
48 .iter()
49 .find(|e| {
50 e.name_or_id().name().map(|n| n.raw_data(rsrc))
51 == Some(Ok(&descriptor.resource_type))
52 })
53 .context("no entry for resource type found")?
54 .data(rsrc)?
55 .table()
56 .context("resource type entry not a table")?;
57
58 let id_match = type_match
59 .entries
60 .iter()
61 .find(|e| e.name_or_id.get(LittleEndian) == descriptor.id)
62 .context("no entry for id found")?
63 .data(rsrc)?
64 .table()
65 .context("id entry not a table")?;
66
67 if id_match.entries.len() != 1 {
68 bail!(
69 "id table doesn't contain exactly 1 entry, contains {}",
70 id_match.entries.len()
71 );
72 }
73 let data_desc = id_match.entries[0]
74 .data(rsrc)?
75 .data()
76 .context("resource entry not data")?;
77
78 let (offset, len) = (
79 data_desc.offset_to_data.get(LittleEndian),
80 data_desc.size.get(LittleEndian),
81 );
82
83 let result = &pe_file
84 .section_table()
85 .pe_file_range_at(offset)
86 .context("unable to map data offset")?;
87
88 Ok(Some((result.0 as u64, len as usize)))
89 } else {
90 // Failing to parse the file as a dll is fine, it means the file is
91 // probably a blob instead.
92 Ok(None)
93 }
94}
95
96/// Descriptor for locating a resource within a DLL file.
97///
98/// Contains the resource type (as a 4-character ASCII string encoded in LE UTF-16)
99/// and a numeric resource ID.
100pub struct DllResourceDescriptor {
101 /// 4 characters encoded in LE UTF-16
102 resource_type: [u8; 8],
103 id: u32,
104}
105
106impl DllResourceDescriptor {
107 /// Creates a new DLL resource descriptor with the given resource type and ID.
108 ///
109 /// The resource type must be a 4-character ASCII string, which will be converted
110 /// to little-endian UTF-16 encoding.
111 pub const fn new(resource_type: &[u8; 4], id: u32) -> Self {
112 Self {
113 id,
114 // Convert to LE UTF-16, only support ASCII names today
115 resource_type: [
116 resource_type[0],
117 0,
118 resource_type[1],
119 0,
120 resource_type[2],
121 0,
122 resource_type[3],
123 0,
124 ],
125 }
126 }
127}