xll-utils 0.1.0

PE/COFF parsing and export verification utilities for Excel XLL development
Documentation
//! XLL-specific helpers.
//!
//! Provides functions for extracting XLL metadata, verifying required
//! Excel entry points, and validating XLL files.

use std::path::Path;

use crate::error::Result;
use crate::pe::parse_pe_file;
use crate::types::{Architecture, PeFile};

/// Information extracted from an XLL file.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XllInfo {
    /// File path.
    pub path: std::path::PathBuf,
    /// XLL name (from filename).
    pub name: String,
    /// CPU architecture.
    pub architecture: Architecture,
    /// Whether `xlAutoOpen` is exported.
    pub has_auto_open: bool,
    /// Whether `xlAutoClose` is exported.
    pub has_auto_close: bool,
    /// Whether `xlAutoFree12` is exported.
    pub has_auto_free: bool,
    /// Whether `xlAddInManagerInfo12` is exported.
    pub has_addin_manager_info: bool,
    /// Total number of exports.
    pub export_count: usize,
    /// All exported function names (sorted).
    pub exports: Vec<String>,
}

/// Extract metadata from an XLL file.
///
/// # Examples
///
/// ```no_run
/// use xll_utils::xll::xll_info;
///
/// let info = xll_info("my_xll.xll").unwrap();
/// println!("{}: {} exports", info.name, info.export_count);
/// ```
pub fn xll_info(path: impl AsRef<Path>) -> Result<XllInfo> {
    let pe = parse_pe_file(path.as_ref())?;

    let name = pe
        .path
        .file_stem()
        .and_then(|n| n.to_str())
        .unwrap_or("unknown")
        .to_string();

    let export_names = pe.export_names();

    Ok(XllInfo {
        path: pe.path.clone(),
        name,
        architecture: pe.architecture,
        has_auto_open: export_names.iter().any(|n| n == "xlAutoOpen"),
        has_auto_close: export_names.iter().any(|n| n == "xlAutoClose"),
        has_auto_free: export_names.iter().any(|n| n == "xlAutoFree12"),
        has_addin_manager_info: export_names.iter().any(|n| n == "xlAddInManagerInfo12"),
        export_count: pe.exports.len(),
        exports: export_names,
    })
}

/// Check whether an XLL has the required Excel entry points.
///
/// Returns `true` if both `xlAutoOpen` and `xlAutoFree12` are exported.
///
/// # Examples
///
/// ```no_run
/// use xll_utils::PeFile;
/// use xll_utils::xll::verify_xll_entry_points;
///
/// let pe = PeFile::parse("my_xll.xll").unwrap();
/// assert!(verify_xll_entry_points(&pe));
/// ```
pub fn verify_xll_entry_points(pe: &PeFile) -> bool {
    let names = pe.export_names();
    names.iter().any(|n| n == "xlAutoOpen") && names.iter().any(|n| n == "xlAutoFree12")
}

/// Check whether a file at the given path is a valid XLL (has required exports).
///
/// # Examples
///
/// ```no_run
/// use xll_utils::xll::is_valid_xll;
///
/// let valid = is_valid_xll("my_xll.xll").unwrap();
/// println!("valid XLL: {valid}");
/// ```
pub fn is_valid_xll(path: impl AsRef<Path>) -> Result<bool> {
    let pe = parse_pe_file(path.as_ref())?;
    Ok(verify_xll_entry_points(&pe))
}