///|
/// Decoded content for the common Windows resource types.
///
/// The fields are deliberately flat: a caller can ask the same structure for
/// string-table entries, bitmap dimensions, icon members or manifest text
/// without first matching a large, format-specific enum.
///|
pub(all) struct ResourceString {
id : Int32
text : String
} derive(@debug.Debug)
///|
pub(all) struct GroupIconMember {
width : Int32
height : Int32
color_count : Int32
planes : Int32
bit_count : Int32
resource_id : Int32
bytes : Int32
} derive(@debug.Debug)
///|
pub(all) struct ResourceContent {
kind : String
summary : String
mut strings : Array[ResourceString]
mut group_icons : Array[GroupIconMember]
mut width : Int32?
mut height : Int32?
mut bit_count : Int32?
mut image_bytes : Int32?
mut png : Bool
mut text : String?
mut version : VersionInfo?
} derive(@debug.Debug)
///|
fn empty_resource_content(kind : String, summary : String) -> ResourceContent {
{
kind,
summary,
strings: [],
group_icons: [],
width: None,
height: None,
bit_count: None,
image_bytes: None,
png: false,
text: None,
version: None,
}
}
///|
fn resource_is_type(entry : ResourceEntry, id : Int32, name : String) -> Bool {
match entry.type_id {
Some(value) => value == id
None =>
match entry.type_name {
Some(value) => value == name
None => false
}
}
}
///|
fn abs32(value : Int32) -> Int32 {
if value < 0 {
0 - value
} else {
value
}
}
///|
fn be_u32(bytes : Bytes, offset : Int32) -> Int32 raise PeError {
if offset < 0 || offset + 4 > bytes.length() {
raise PeError::OutOfBounds(offset, 4, bytes.length() - offset)
}
(bytes[offset].to_int() << 24) |
(bytes[offset + 1].to_int() << 16) |
(bytes[offset + 2].to_int() << 8) |
bytes[offset + 3].to_int()
}
///|
/// Decode one resource leaf. Unsupported data content is returned as `raw`
/// with its byte size, while malformed data for a recognised type is an error.
pub fn Pe::decode_resource(
self : Pe,
entry : ResourceEntry,
) -> ResourceContent raise PeError {
if entry.size < 0 {
raise PeError::Malformed("negative resource size", 0)
}
let offset = match self.rva_to_offset(entry.rva) {
Some(value) => value
None =>
raise PeError::Malformed(
"resource RVA " + hex(entry.rva) + " is not backed by file bytes",
0,
)
}
let end = offset + entry.size
if end > self.bytes.length() {
raise PeError::Malformed(
"resource extends past the end of the file", offset,
)
}
if resource_is_type(entry, 6, "STRING") {
return self.decode_string_resource(entry, offset, end)
}
if resource_is_type(entry, 2, "BITMAP") {
return self.decode_bitmap_resource(entry.size, offset, end)
}
if resource_is_type(entry, 3, "ICON") {
return self.decode_icon_resource(entry.size, offset, end)
}
if resource_is_type(entry, 14, "GROUP_ICON") {
return self.decode_group_icon_resource(offset, end)
}
if resource_is_type(entry, 16, "VERSION") {
let version = self.version_info_for_entry(entry)
let content = empty_resource_content(
"version-info",
match version {
Some(info) =>
"file " + info.file_version + ", product " + info.product_version
None => "VERSIONINFO present but incomplete"
},
)
content.version = version
return content
}
if resource_is_type(entry, 24, "MANIFEST") {
let content = empty_resource_content("manifest", "UTF-8 manifest text")
content.text = Some(decode_utf8(self.bytes, offset, end))
return content
}
empty_resource_content(
"raw",
entry.size.to_string() + " byte(s), not decoded for this resource type",
)
}
///|
fn Pe::decode_string_resource(
self : Pe,
entry : ResourceEntry,
offset : Int32,
end : Int32,
) -> ResourceContent raise PeError {
let strings : Array[ResourceString] = []
let block = match entry.name_id {
Some(id) => if id > 0 { id - 1 } else { 0 }
None => 0
}
let reader = Reader::at(self.bytes, offset)
let mut slot = 0
while slot < 16 && reader.position() + 2 <= end {
let length = reader.u16()
let text_end = reader.position() + length * 2
if text_end > end {
raise PeError::Malformed(
"STRING resource entry extends past its data block",
reader.position(),
)
}
if length > 0 {
let text = read_utf16(self.bytes, reader.position(), text_end)
strings.push({ id: block * 16 + slot, text, })
reader.skip(length * 2)
}
slot = slot + 1
}
let content = empty_resource_content(
"string-table",
strings.length().to_string() + " non-empty string(s)",
)
content.strings = strings
content
}
///|
fn Pe::decode_bitmap_resource(
self : Pe,
size : Int32,
offset : Int32,
end : Int32,
) -> ResourceContent raise PeError {
if size < 16 {
raise PeError::Malformed(
"BITMAP resource is shorter than its header", offset,
)
}
let reader = Reader::at(self.bytes, offset)
let header_size = reader.u32()
if header_size < 16 || offset + header_size > end {
raise PeError::Malformed("invalid BITMAPINFOHEADER size", offset)
}
let width = reader.u32_raw()
let stored_height = reader.u32_raw()
let planes = reader.u16()
let bit_count = reader.u16()
let content = empty_resource_content(
"bitmap",
abs32(width).to_string() +
" x " +
abs32(stored_height / 2).to_string() +
", " +
bit_count.to_string() +
" bpp",
)
content.width = Some(abs32(width))
content.height = Some(abs32(stored_height / 2))
content.bit_count = Some(bit_count)
content.image_bytes = Some(size)
let _ = planes
content
}
///|
fn Pe::decode_icon_resource(
self : Pe,
size : Int32,
offset : Int32,
end : Int32,
) -> ResourceContent raise PeError {
if offset + 8 <= end &&
self.bytes[offset].to_int() == 0x89 &&
self.bytes[offset + 1].to_int() == 0x50 &&
self.bytes[offset + 2].to_int() == 0x4E &&
self.bytes[offset + 3].to_int() == 0x47 &&
self.bytes[offset + 4].to_int() == 0x0D &&
self.bytes[offset + 5].to_int() == 0x0A &&
self.bytes[offset + 6].to_int() == 0x1A &&
self.bytes[offset + 7].to_int() == 0x0A {
if offset + 24 > end ||
self.bytes[offset + 12].to_int() != 0x49 ||
self.bytes[offset + 13].to_int() != 0x48 ||
self.bytes[offset + 14].to_int() != 0x44 ||
self.bytes[offset + 15].to_int() != 0x52 {
raise PeError::Malformed("PNG icon is missing its IHDR header", offset)
}
let width = be_u32(self.bytes, offset + 16)
let height = be_u32(self.bytes, offset + 20)
let content = empty_resource_content(
"icon",
"PNG " + width.to_string() + " x " + height.to_string(),
)
content.width = Some(width)
content.height = Some(height)
content.image_bytes = Some(size)
content.png = true
return content
}
if size < 16 {
raise PeError::Malformed("ICON resource is shorter than its header", offset)
}
let reader = Reader::at(self.bytes, offset)
let header_size = reader.u32()
if header_size < 16 || offset + header_size > end {
raise PeError::Malformed("invalid icon BITMAPINFOHEADER size", offset)
}
let width = abs32(reader.u32_raw())
let height = abs32(reader.u32_raw() / 2)
let planes = reader.u16()
let bit_count = reader.u16()
let content = empty_resource_content(
"icon",
"DIB " +
width.to_string() +
" x " +
height.to_string() +
", " +
bit_count.to_string() +
" bpp",
)
content.width = Some(width)
content.height = Some(height)
content.bit_count = Some(bit_count)
content.image_bytes = Some(size)
content.png = false
let _ = planes
content
}
///|
fn Pe::decode_group_icon_resource(
self : Pe,
offset : Int32,
end : Int32,
) -> ResourceContent raise PeError {
if offset + 6 > end {
raise PeError::Malformed("GROUP_ICON resource is too short", offset)
}
let reader = Reader::at(self.bytes, offset)
reader.skip(2)
let kind = reader.u16()
let count = reader.u16()
if kind != 1 {
raise PeError::Malformed("GROUP_ICON type is not 1", offset + 2)
}
if count > 4096 {
raise PeError::Malformed("GROUP_ICON has too many members", offset + 4)
}
let members : Array[GroupIconMember] = []
let mut i = 0
while i < count {
if reader.position() + 14 > end {
raise PeError::Malformed(
"GROUP_ICON entry is truncated",
reader.position(),
)
}
let stored_width = reader.u8()
let stored_height = reader.u8()
let color_count = reader.u8()
reader.skip(1)
let planes = reader.u16()
let bit_count = reader.u16()
let bytes = reader.u32()
let resource_id = reader.u16()
members.push({
width: if stored_width == 0 {
256
} else {
stored_width
},
height: if stored_height == 0 {
256
} else {
stored_height
},
color_count,
planes,
bit_count,
resource_id,
bytes,
})
i = i + 1
}
let content = empty_resource_content(
"group-icon",
count.to_string() + " icon member(s)",
)
content.group_icons = members
content
}