xll-utils 0.1.0

PE/COFF parsing and export verification utilities for Excel XLL development
Documentation
use std::fmt;

/// CPU architecture for PE files.
///
/// # Examples
///
/// ```
/// use xll_utils::Architecture;
///
/// let arch = Architecture::from_machine_type(0x8664);
/// assert_eq!(arch, Architecture::X64);
/// assert!(arch.is_x64());
/// assert_eq!(arch.to_string(), "x64");
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Architecture {
    X86,
    X64,
    Arm,
    Arm64,
    Unknown(u16),
}

impl Architecture {
    /// Create an `Architecture` from a PE machine type constant.
    ///
    /// ```
    /// use xll_utils::Architecture;
    ///
    /// assert_eq!(Architecture::from_machine_type(0x014c), Architecture::X86);
    /// assert_eq!(Architecture::from_machine_type(0x8664), Architecture::X64);
    /// assert_eq!(Architecture::from_machine_type(0xaa64), Architecture::Arm64);
    /// assert_eq!(Architecture::from_machine_type(0x9999), Architecture::Unknown(0x9999));
    /// ```
    pub const fn from_machine_type(machine: u16) -> Self {
        match machine {
            0x014c => Self::X86,   // IMAGE_FILE_MACHINE_I386
            0x8664 => Self::X64,   // IMAGE_FILE_MACHINE_AMD64
            0x01c0 => Self::Arm,   // IMAGE_FILE_MACHINE_ARM
            0xaa64 => Self::Arm64, // IMAGE_FILE_MACHINE_ARM64
            other => Self::Unknown(other),
        }
    }

    /// Return the PE machine type constant, if known.
    ///
    /// ```
    /// use xll_utils::Architecture;
    ///
    /// assert_eq!(Architecture::X64.as_image_file_machine(), Some(0x8664));
    /// assert_eq!(Architecture::Unknown(0x1234).as_image_file_machine(), None);
    /// ```
    pub const fn as_image_file_machine(self) -> Option<u16> {
        match self {
            Self::X86 => Some(0x014c),
            Self::X64 => Some(0x8664),
            Self::Arm => Some(0x01c0),
            Self::Arm64 => Some(0xaa64),
            Self::Unknown(_) => None,
        }
    }

    pub const fn is_x86(self) -> bool {
        matches!(self, Self::X86)
    }
    pub const fn is_x64(self) -> bool {
        matches!(self, Self::X64)
    }
    pub const fn is_arm(self) -> bool {
        matches!(self, Self::Arm | Self::Arm64)
    }
}

impl fmt::Display for Architecture {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::X86 => write!(f, "x86"),
            Self::X64 => write!(f, "x64"),
            Self::Arm => write!(f, "ARM"),
            Self::Arm64 => write!(f, "ARM64"),
            Self::Unknown(v) => write!(f, "Unknown(0x{v:04x})"),
        }
    }
}

/// Information about a single exported symbol.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExportInfo {
    /// Export name (if any; may be `None` for ordinal-only exports).
    pub name: Option<String>,
    /// Ordinal value (ordinal base + index into the export table).
    pub ordinal: u32,
    /// Whether the export is forwarded to another DLL.
    pub is_forwarded: bool,
    /// Forward string (e.g., "NTDLL.RtlAllocateHeap") if forwarded.
    pub forward_to: Option<String>,
    /// Exported address relative to image base (if available).
    pub relative_address: Option<u32>,
}

/// Export directory information from a PE file.
///
/// Corresponds to the `IMAGE_EXPORT_DIRECTORY` structure in PE format.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExportDirectory {
    pub characteristics: u32,
    pub timestamp: u32,
    pub major_version: u16,
    pub minor_version: u16,
    pub name_rva: u32,
    pub ordinal_base: u32,
    pub address_table_entries: u32,
    pub number_of_name_pointers: u32,
    pub address_table_rva: u32,
    pub name_pointer_rva: u32,
    pub ordinal_table_rva: u32,
}

/// Result of export verification.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VerificationReport {
    /// Path to the DLL that was verified.
    pub dll_path: std::path::PathBuf,
    /// Total exports found.
    pub total_exports: usize,
    /// Required exports that were found.
    pub found: Vec<String>,
    /// Required exports that are missing.
    pub missing: Vec<String>,
    /// Whether all required exports are present.
    pub complete: bool,
    /// Unexpected exports (found but not in expected list).
    pub unexpected: Vec<String>,
    /// Exports with name mismatches.
    pub mismatches: Vec<NameMismatch>,
    /// Architecture of the DLL.
    pub architecture: Architecture,
}

/// Record of an export name mismatch.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NameMismatch {
    /// The expected export name.
    pub expected: String,
    /// The actual export name found.
    pub actual: String,
    /// Ordinal of the mismatched export.
    pub ordinal: u32,
}

/// Summary differences between two export lists.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExportDiff {
    /// Exports present in expected but not in actual.
    pub missing: Vec<String>,
    /// Exports present in actual but not in expected.
    pub extra: Vec<String>,
    /// Common exports.
    pub common: Vec<String>,
}

/// Parsed PE file information.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PeFile {
    /// File path.
    pub path: std::path::PathBuf,
    /// CPU architecture.
    pub architecture: Architecture,
    /// Whether this is a DLL (vs EXE).
    pub is_dll: bool,
    /// Image base address.
    pub image_base: u64,
    /// Entry point RVA.
    pub entry_point_rva: u32,
    /// Export directory (if present).
    pub export_directory: Option<ExportDirectory>,
    /// All exports.
    pub exports: Vec<ExportInfo>,
}