summaryrefslogtreecommitdiffstats
path: root/third_party/rust/embed-manifest/src/embed
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/embed-manifest/src/embed/coff.rs192
-rw-r--r--third_party/rust/embed-manifest/src/embed/error.rs57
-rw-r--r--third_party/rust/embed-manifest/src/embed/mod.rs139
-rw-r--r--third_party/rust/embed-manifest/src/embed/test.rs173
4 files changed, 561 insertions, 0 deletions
diff --git a/third_party/rust/embed-manifest/src/embed/coff.rs b/third_party/rust/embed-manifest/src/embed/coff.rs
new file mode 100644
index 0000000000..bf751c3bf5
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/coff.rs
@@ -0,0 +1,192 @@
+//! Just as much COFF object file support as is needed to write a resource data segment
+//! for GNU Windows targets. Inspired by the `write::Object` code from the `object` crate.
+//!
+//! Integers are converted from u64 to u32 and added without checking because the manifest
+//! data cannot get anywhere close to overflowing unless the supplied application name or
+//! number of dependencies was extremely long. If this code was used more generally or if
+//! the input was less trustworthy then more checked conversions and checked arithmetic
+//! would be needed.
+
+use std::io::{self, Seek, SeekFrom, Write};
+use std::time::SystemTime;
+
+#[derive(Debug, Clone, Copy)]
+pub enum MachineType {
+ I386,
+ X86_64,
+ Aarch64,
+}
+
+impl MachineType {
+ pub fn machine(&self) -> u16 {
+ match self {
+ Self::I386 => 0x014c,
+ Self::X86_64 => 0x8664,
+ Self::Aarch64 => 0xaa64,
+ }
+ }
+
+ pub fn relocation_type(&self) -> u16 {
+ match self {
+ Self::I386 => 7,
+ Self::X86_64 => 3,
+ Self::Aarch64 => 2,
+ }
+ }
+}
+
+pub struct CoffWriter<W> {
+ /// wrapped writer or buffer
+ writer: W,
+ /// machine type for file header
+ machine_type: MachineType,
+ // size in bytes of resource section data
+ size_of_raw_data: u32,
+ // number of relocations at the end of the section
+ number_of_relocations: u16,
+}
+
+impl<W: Write + Seek> CoffWriter<W> {
+ /// Create a new instance wrapping a writer.
+ pub fn new(mut writer: W, machine_type: MachineType) -> io::Result<Self> {
+ // Add space for file header and section table.
+ writer.write_all(&[0; 60])?;
+ Ok(Self {
+ writer,
+ machine_type,
+ size_of_raw_data: 0,
+ number_of_relocations: 0,
+ })
+ }
+
+ /// Add data to a section and return its offset within the section.
+ pub fn add_data(&mut self, data: &[u8]) -> io::Result<u32> {
+ let start = self.size_of_raw_data;
+ self.writer.write_all(data)?;
+ self.size_of_raw_data = start + data.len() as u32;
+ Ok(start)
+ }
+
+ // Pad the resource data to a multiple of `n` bytes.
+ pub fn align_to(&mut self, n: u32) -> io::Result<()> {
+ let offset = self.size_of_raw_data % n;
+ if offset != 0 {
+ let padding = n - offset;
+ for _ in 0..padding {
+ self.writer.write_all(&[0])?;
+ }
+ self.size_of_raw_data += padding;
+ }
+ Ok(())
+ }
+
+ /// Write a relocation for a symbol at the end of the section.
+ pub fn add_relocation(&mut self, address: u32) -> io::Result<()> {
+ self.number_of_relocations += 1;
+ self.writer.write_all(&address.to_le_bytes())?;
+ self.writer.write_all(&[0, 0, 0, 0])?;
+ self.writer.write_all(&self.machine_type.relocation_type().to_le_bytes())
+ }
+
+ /// Write the object and section headers and write the symbol table.
+ pub fn finish(mut self) -> io::Result<W> {
+ // Get the timestamp for the header. `as` is correct here, as the low 32 bits
+ // should be used.
+ let timestamp = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .map_or(0, |d| d.as_secs() as u32);
+
+ // Copy file location of the symbol table.
+ let pointer_to_symbol_table = self.writer.stream_position()? as u32;
+
+ // Write the symbols and auxiliary data for the section.
+ self.writer.write_all(b".rsrc\0\0\0")?; // name
+ self.writer.write_all(&[0, 0, 0, 0])?; // address
+ self.writer.write_all(&[1, 0])?; // section number (1-based)
+ self.writer.write_all(&[0, 0, 3, 1])?; // type = 0, class = static, aux symbols = 1
+ self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
+ self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
+ self.writer.write_all(&[0; 12])?;
+
+ // Write the empty string table.
+ self.writer.write_all(&[0; 4])?;
+
+ // Write the object header.
+ let end_of_file = self.writer.seek(SeekFrom::Start(0))?;
+ self.writer.write_all(&self.machine_type.machine().to_le_bytes())?;
+ self.writer.write_all(&[1, 0])?; // number of sections
+ self.writer.write_all(&timestamp.to_le_bytes())?;
+ self.writer.write_all(&pointer_to_symbol_table.to_le_bytes())?;
+ self.writer.write_all(&[2, 0, 0, 0])?; // number of symbol table entries
+ self.writer.write_all(&[0; 4])?; // optional header size = 0, characteristics = 0
+
+ // Write the section header.
+ self.writer.write_all(b".rsrc\0\0\0")?;
+ self.writer.write_all(&[0; 8])?; // virtual size = 0 and virtual address = 0
+ self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
+ self.writer.write_all(&[60, 0, 0, 0])?; // pointer to raw data
+ self.writer.write_all(&(self.size_of_raw_data + 60).to_le_bytes())?; // pointer to relocations
+ self.writer.write_all(&[0; 4])?; // pointer to line numbers
+ self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
+ self.writer.write_all(&[0; 2])?; // number of line numbers
+ self.writer.write_all(&[0x40, 0, 0x30, 0xc0])?; // characteristics
+
+ // Return the inner writer and dispose of this object.
+ self.writer.seek(SeekFrom::Start(end_of_file))?;
+ Ok(self.writer)
+ }
+}
+
+/// Returns the bytes for a resource directory table.
+///
+/// Most of the fields are set to zero, including the timestamp, to aid
+/// with making builds reproducible.
+///
+/// ```c
+/// typedef struct {
+/// DWORD Characteristics,
+/// DWORD TimeDateStamp,
+/// WORD MajorVersion,
+/// WORD MinorVersion,
+/// WORD NumberOfNamedEntries,
+/// WORD NumberOfIdEntries
+/// } IMAGE_RESOURCE_DIRECTORY;
+/// ```
+pub fn resource_directory_table(number_of_id_entries: u16) -> [u8; 16] {
+ let mut table = [0; 16];
+ table[14..16].copy_from_slice(&number_of_id_entries.to_le_bytes());
+ table
+}
+
+/// Returns the bytes for a resource directory entry for an ID.
+///
+/// ```c
+/// typedef struct {
+/// DWORD Name,
+/// DWORD OffsetToData
+/// } IMAGE_RESOURCE_DIRECTORY_ENTRY;
+/// ```
+pub fn resource_directory_id_entry(id: u32, offset: u32, subdirectory: bool) -> [u8; 8] {
+ let mut entry = [0; 8];
+ entry[0..4].copy_from_slice(&id.to_le_bytes());
+ let flag: u32 = if subdirectory { 0x80000000 } else { 0 };
+ entry[4..8].copy_from_slice(&((offset & 0x7fffffff) | flag).to_le_bytes());
+ entry
+}
+
+/// Returns the bytes for a resource data entry.
+///
+/// ```c
+/// typedef struct {
+/// DWORD OffsetToData,
+/// DWORD Size,
+/// DWORD CodePage,
+/// DWORD Reserved
+/// } IMAGE_RESOURCE_DATA_ENTRY;
+/// ```
+pub fn resource_data_entry(rva: u32, size: u32) -> [u8; 16] {
+ let mut entry = [0; 16];
+ entry[0..4].copy_from_slice(&rva.to_le_bytes());
+ entry[4..8].copy_from_slice(&size.to_le_bytes());
+ entry
+}
diff --git a/third_party/rust/embed-manifest/src/embed/error.rs b/third_party/rust/embed-manifest/src/embed/error.rs
new file mode 100644
index 0000000000..365d0525ed
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/error.rs
@@ -0,0 +1,57 @@
+//! Error handling for application manifest embedding.
+
+use std::fmt::{self, Display, Formatter};
+use std::io::{self, ErrorKind};
+
+/// The error type which is returned when application manifest embedding fails.
+#[derive(Debug)]
+pub struct Error {
+ repr: Repr,
+}
+
+#[derive(Debug)]
+enum Repr {
+ IoError(io::Error),
+ UnknownTarget,
+}
+
+impl Error {
+ pub(crate) fn unknown_target() -> Error {
+ Error {
+ repr: Repr::UnknownTarget,
+ }
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match self.repr {
+ Repr::IoError(ref e) => write!(f, "I/O error: {}", e),
+ Repr::UnknownTarget => f.write_str("unknown target"),
+ }
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self.repr {
+ Repr::IoError(ref e) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(e: io::Error) -> Self {
+ Error { repr: Repr::IoError(e) }
+ }
+}
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ match e.repr {
+ Repr::IoError(ioe) => ioe,
+ _ => io::Error::new(ErrorKind::Other, e),
+ }
+ }
+}
diff --git a/third_party/rust/embed-manifest/src/embed/mod.rs b/third_party/rust/embed-manifest/src/embed/mod.rs
new file mode 100644
index 0000000000..b054878ac6
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/mod.rs
@@ -0,0 +1,139 @@
+use std::env;
+use std::fs::{self, File};
+use std::io::{self, stdout, BufWriter, Cursor, Write};
+use std::path::{Path, PathBuf};
+
+use crate::manifest::ManifestBuilder;
+
+use self::coff::{resource_data_entry, resource_directory_id_entry, resource_directory_table, CoffWriter, MachineType};
+use self::error::Error;
+
+mod coff;
+pub mod error;
+
+#[cfg(test)]
+mod test;
+
+/// Embeds the manifest described by `manifest` by converting it to XML,
+/// then saving it to a file and passing the correct options to the linker
+/// on MSVC targets, or by building a static library and instructing Cargo
+/// to link the executable against it on GNU targets.
+pub fn embed_manifest(manifest: ManifestBuilder) -> Result<(), Error> {
+ let out_dir = get_out_dir()?;
+ let target = get_target()?;
+ if matches!(target.os, TargetOs::WindowsMsvc) {
+ let manifest_file = out_dir.join("manifest.xml");
+ write!(BufWriter::new(File::create(&manifest_file)?), "{}", manifest)?;
+ link_manifest_msvc(&manifest_file, &mut stdout().lock())
+ } else {
+ let manifest_data = manifest.to_string();
+ link_manifest_gnu(manifest_data.as_bytes(), &out_dir, target.arch, &mut stdout().lock())
+ }
+}
+
+/// Directly embeds the manifest in the provided `file` by passing the correct
+/// options to the linker on MSVC targets, or by building a static library
+/// and instructing Cargo to link the executable against it on GNU targets.
+pub fn embed_manifest_file<P: AsRef<Path>>(file: P) -> Result<(), io::Error> {
+ let out_dir = get_out_dir()?;
+ let target = get_target()?;
+ if matches!(target.os, TargetOs::WindowsMsvc) {
+ Ok(link_manifest_msvc(file.as_ref(), &mut stdout().lock())?)
+ } else {
+ let manifest = fs::read(file.as_ref())?;
+ Ok(link_manifest_gnu(&manifest, &out_dir, target.arch, &mut stdout().lock())?)
+ }
+}
+
+fn get_out_dir() -> Result<PathBuf, io::Error> {
+ match env::var_os("OUT_DIR") {
+ Some(dir) => Ok(PathBuf::from(dir)),
+ None => env::current_dir(),
+ }
+}
+
+enum TargetOs {
+ WindowsGnu,
+ WindowsMsvc,
+}
+
+struct Target {
+ arch: MachineType,
+ os: TargetOs,
+}
+
+fn get_target() -> Result<Target, Error> {
+ match env::var("TARGET") {
+ Ok(target) => parse_target(&target),
+ _ => Err(Error::unknown_target()),
+ }
+}
+
+fn parse_target(target: &str) -> Result<Target, Error> {
+ let mut iter = target.splitn(3, '-');
+ let arch = match iter.next() {
+ Some("i686") => MachineType::I386,
+ Some("aarch64") => MachineType::Aarch64,
+ Some("x86_64") => MachineType::X86_64,
+ _ => return Err(Error::unknown_target()),
+ };
+ if iter.next() != Some("pc") {
+ return Err(Error::unknown_target());
+ }
+ let os = match iter.next() {
+ Some("windows-gnu") => TargetOs::WindowsGnu,
+ Some("windows-gnullvm") => TargetOs::WindowsGnu,
+ Some("windows-msvc") => TargetOs::WindowsMsvc,
+ _ => return Err(Error::unknown_target()),
+ };
+ Ok(Target { arch, os })
+}
+
+fn link_manifest_msvc<W: Write>(manifest_path: &Path, out: &mut W) -> Result<(), Error> {
+ writeln!(out, "cargo:rustc-link-arg-bins=/MANIFEST:EMBED")?;
+ writeln!(
+ out,
+ "cargo:rustc-link-arg-bins=/MANIFESTINPUT:{}",
+ manifest_path.canonicalize()?.display()
+ )?;
+ writeln!(out, "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO")?;
+ Ok(())
+}
+
+fn link_manifest_gnu<W: Write>(manifest: &[u8], out_dir: &Path, arch: MachineType, out: &mut W) -> Result<(), Error> {
+ // Generate a COFF object file containing the manifest in a .rsrc section.
+ let object_data = create_object_file(manifest, arch)?;
+ let path = out_dir.join("embed-manifest.o");
+ fs::create_dir_all(out_dir)?;
+ fs::write(&path, object_data)?;
+
+ // Link the manifest with the executable.
+ writeln!(out, "cargo:rustc-link-arg-bins={}", path.display())?;
+ Ok(())
+}
+
+fn create_object_file(manifest: &[u8], arch: MachineType) -> Result<Vec<u8>, io::Error> {
+ // Start object file with .rsrc section.
+ let mut obj = CoffWriter::new(Cursor::new(Vec::with_capacity(4096)), arch)?;
+
+ // Create resource directories for type ID 24, name ID 1, language ID 1033.
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(24, 24, true))?;
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(1, 48, true))?;
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(1033, 72, false))?;
+
+ // Add resource data entry with relocated address.
+ let address = obj.add_data(&resource_data_entry(88, manifest.len() as u32))?;
+
+ // Add the manifest data.
+ obj.add_data(manifest)?;
+ obj.align_to(8)?;
+
+ // Write manifest data relocation at the end of the section.
+ obj.add_relocation(address)?;
+
+ // Finish writing file and return the populated object data.
+ Ok(obj.finish()?.into_inner())
+}
diff --git a/third_party/rust/embed-manifest/src/embed/test.rs b/third_party/rust/embed-manifest/src/embed/test.rs
new file mode 100644
index 0000000000..c464c6044c
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/test.rs
@@ -0,0 +1,173 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+
+use object::coff::CoffFile;
+use object::pe::{ImageResourceDataEntry, ImageResourceDirectory, ImageResourceDirectoryEntry, IMAGE_RESOURCE_DATA_IS_DIRECTORY};
+use object::{
+ pod, Architecture, LittleEndian, Object, ObjectSection, ObjectSymbol, RelocationEncoding, RelocationKind, SectionKind,
+};
+use tempfile::{tempdir, TempDir};
+
+use crate::embed::coff::MachineType;
+use crate::embed::{create_object_file, link_manifest_gnu, link_manifest_msvc, TargetOs};
+use crate::new_manifest;
+
+#[test]
+fn create_obj() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
+ let data = fs::read(&res.object_file()).unwrap();
+ let obj = CoffFile::parse(&data[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::X86_64);
+ let expected_manifest = fs::read(&sample_manifest_path()).unwrap();
+ check_object_file(obj, &expected_manifest);
+}
+
+#[test]
+fn link_lib_gnu() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
+ assert!(res.object_file().exists());
+ let object_option = format!("cargo:rustc-link-arg-bins={}", res.object_file().display());
+ assert_eq!(res.lines(), &[object_option.as_str()]);
+}
+
+#[test]
+fn link_file_msvc() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsMsvc);
+ assert!(!res.object_file().exists());
+ let mut input_option = String::from("cargo:rustc-link-arg-bins=/MANIFESTINPUT:");
+ input_option.push_str(res.manifest_path.canonicalize().unwrap().to_str().unwrap());
+ assert_eq!(
+ res.lines(),
+ &[
+ "cargo:rustc-link-arg-bins=/MANIFEST:EMBED",
+ input_option.as_str(),
+ "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO"
+ ]
+ );
+}
+
+struct EmbedResult {
+ manifest_path: PathBuf,
+ out_dir: TempDir,
+ output: String,
+}
+
+impl EmbedResult {
+ fn object_file(&self) -> PathBuf {
+ self.out_dir.path().join("embed-manifest.o")
+ }
+
+ fn lines(&self) -> Vec<&str> {
+ self.output.lines().collect()
+ }
+}
+
+fn sample_manifest_path() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/sample.exe.manifest")
+}
+
+fn do_embed_file(arch: MachineType, os: TargetOs) -> EmbedResult {
+ let manifest_path = sample_manifest_path();
+ let out_dir = tempdir().unwrap();
+ let mut buf: Vec<u8> = Vec::new();
+ if matches!(os, TargetOs::WindowsMsvc) {
+ link_manifest_msvc(&manifest_path, &mut buf).unwrap();
+ } else {
+ link_manifest_gnu(&fs::read(&manifest_path).unwrap(), out_dir.path(), arch, &mut buf).unwrap();
+ }
+ EmbedResult {
+ manifest_path,
+ out_dir,
+ output: String::from_utf8(buf).unwrap(),
+ }
+}
+
+#[test]
+fn object_file_x86() {
+ let manifest = new_manifest("Test.X86").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::I386).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::I386);
+ check_object_file(obj, &manifest);
+}
+
+#[test]
+fn object_file_x86_64() {
+ let manifest = new_manifest("Test.X86_64").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::X86_64).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::X86_64);
+ check_object_file(obj, &manifest);
+}
+
+#[test]
+fn object_file_aarch64() {
+ let manifest = new_manifest("Test.AARCH64").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::Aarch64).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::Aarch64);
+ check_object_file(obj, &manifest);
+}
+
+fn check_object_file(obj: CoffFile, expected_manifest: &[u8]) {
+ // There should be one sections `.rsrc`.
+ assert_eq!(
+ obj.sections().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
+ &[".rsrc"]
+ );
+
+ // There should be one section symbol.
+ assert_eq!(
+ obj.symbols().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
+ &[".rsrc"]
+ );
+
+ // The resource section must be a data section.
+ let rsrc = obj.section_by_name(".rsrc").unwrap();
+ assert_eq!(rsrc.address(), 0);
+ assert_eq!(rsrc.kind(), SectionKind::Data);
+
+ // The data RVA in the resource data entry must be relocatable.
+ let (addr, reloc) = rsrc.relocations().next().unwrap();
+ assert_eq!(reloc.kind(), RelocationKind::ImageOffset);
+ assert_eq!(reloc.encoding(), RelocationEncoding::Generic);
+ assert_eq!(addr, 0x48); // size of the directory table, three directories, and no strings
+ assert_eq!(reloc.addend(), 0);
+
+ // The resource directory contains one manifest resource type subdirectory.
+ let data = rsrc.data().unwrap();
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(data).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(24, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
+
+ // The manifest subdirectory contains one image (not DLL) manifest subdirectory.
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(1, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
+
+ // The image manifest subdirectory contains one US English manifest data entry.
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(0x0409, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(0, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = offset as usize;
+
+ // The manifest data matches what was added.
+ let (entry, resource_data) = pod::from_bytes::<ImageResourceDataEntry>(&data[offset..]).unwrap();
+ let end = entry.size.get(LittleEndian) as usize;
+ let manifest = &resource_data[..end];
+ assert_eq!(manifest, expected_manifest);
+}