1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
//! Extract icons from Windows PE (.exe/.dll) files using pelite.
//!
//! Returns raw ICO file bytes that can be loaded directly by Qt's QIcon/QImage.
use std::path::Path;
use pelite::resources::{Entry, Name, Resources};
/// Extract the best (largest) icon from a PE executable as raw ICO bytes.
///
/// Returns `None` if the file can't be read, isn't a valid PE, or has no icons.
pub fn extract_icon(exe_path: &Path) -> Option<Vec<u8>> {
let data = std::fs::read(exe_path).ok()?;
// Try PE32+ (64-bit) first, fall back to PE32 (32-bit)
if let Ok(icon) = extract_from_pe64(&data) {
return Some(icon);
}
if let Ok(icon) = extract_from_pe32(&data) {
return Some(icon);
}
None
}
fn extract_from_pe64(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
use pelite::pe64::{Pe, PeFile};
let pe = PeFile::from_bytes(data)?;
let resources = pe.resources()?;
build_ico_from_resources(&resources)
}
fn extract_from_pe32(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
use pelite::pe32::{Pe, PeFile};
let pe = PeFile::from_bytes(data)?;
let resources = pe.resources()?;
build_ico_from_resources(&resources)
}
/// Build an ICO file from PE resources, picking the group icon with the
/// largest total pixel area.
fn build_ico_from_resources(
resources: &Resources,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let root = resources.root()?;
// Find RT_GROUP_ICON directory (type 14)
let group_icon_dir = root
.get_dir(Name::Id(14))
.map_err(|_| "no RT_GROUP_ICON")?;
let mut best_group: Option<(i32, Vec<u8>)> = None;
for dir_entry in group_icon_dir.entries() {
// Each entry is a group; get its subdirectory (language variants)
let sub_dir = match dir_entry.entry() {
Ok(Entry::Directory(d)) => d,
_ => continue,
};
// Get the first language variant's data
let data_entry = match sub_dir.entries().next() {
Some(e) => match e.entry() {
Ok(Entry::DataEntry(d)) => d,
_ => continue,
},
None => continue,
};
let grp_data = match data_entry.bytes() {
Ok(d) => d,
Err(_) => continue,
};
// Parse GRPICONDIR header: reserved(2) + type(2) + count(2) = 6 bytes
if grp_data.len() < 6 {
continue;
}
let count = u16::from_le_bytes([grp_data[4], grp_data[5]]) as usize;
// Each GRPICONDIRENTRY is 14 bytes
if grp_data.len() < 6 + count * 14 {
continue;
}
// Calculate total pixel area for ranking
let mut total_area: i32 = 0;
for i in 0..count {
let offset = 6 + i * 14;
let w = if grp_data[offset] == 0 {
256i32
} else {
grp_data[offset] as i32
};
let h = if grp_data[offset + 1] == 0 {
256i32
} else {
grp_data[offset + 1] as i32
};
total_area += w * h;
}
if let Ok(ico_bytes) = build_ico_file(resources, grp_data, count) {
if best_group.is_none() || total_area > best_group.as_ref().unwrap().0 {
best_group = Some((total_area, ico_bytes));
}
}
}
best_group
.map(|(_, bytes)| bytes)
.ok_or_else(|| "no valid icon group found".into())
}
/// Build a complete ICO file from a GRPICONDIR and the corresponding RT_ICON resources.
fn build_ico_file(
resources: &Resources,
grp_data: &[u8],
count: usize,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let root = resources.root()?;
// RT_ICON = type 3
let icon_dir = root.get_dir(Name::Id(3)).map_err(|_| "no RT_ICON")?;
// Collect individual icon image data
struct IconEntry<'a> {
header: [u8; 8], // first 8 bytes of GRPICONDIRENTRY (w, h, colors, reserved, planes, bpp)
data: &'a [u8],
}
let mut entries: Vec<IconEntry> = Vec::new();
for i in 0..count {
let grp_offset = 6 + i * 14;
let icon_id =
u16::from_le_bytes([grp_data[grp_offset + 12], grp_data[grp_offset + 13]]) as u32;
// Find the RT_ICON with this ID
let icon_sub = match icon_dir.get_dir(Name::Id(icon_id)) {
Ok(d) => d,
Err(_) => continue,
};
let data_entry = match icon_sub.entries().next() {
Some(e) => match e.entry() {
Ok(Entry::DataEntry(d)) => d,
_ => continue,
},
None => continue,
};
let image_data = match data_entry.bytes() {
Ok(d) => d,
Err(_) => continue,
};
let mut header = [0u8; 8];
header.copy_from_slice(&grp_data[grp_offset..grp_offset + 8]);
entries.push(IconEntry {
header,
data: image_data,
});
}
if entries.is_empty() {
return Err("no icon entries found".into());
}
let entry_count = entries.len() as u16;
let header_size = 6 + (entry_count as usize) * 16; // ICONDIR + ICONDIRENTRYs
let mut ico = Vec::new();
// ICONDIR header
ico.extend_from_slice(&0u16.to_le_bytes()); // reserved
ico.extend_from_slice(&1u16.to_le_bytes()); // type = ICO
ico.extend_from_slice(&entry_count.to_le_bytes());
// Write ICONDIRENTRY records (16 bytes each)
let mut data_offset = header_size as u32;
for entry in &entries {
ico.extend_from_slice(&entry.header); // width, height, colors, reserved, planes, bpp (8 bytes)
let size = entry.data.len() as u32;
ico.extend_from_slice(&size.to_le_bytes()); // dwBytesInRes (4 bytes)
ico.extend_from_slice(&data_offset.to_le_bytes()); // dwImageOffset (4 bytes)
data_offset += size;
}
// Write image data
for entry in &entries {
ico.extend_from_slice(entry.data);
}
Ok(ico)
}
|