/// Import record decoding and resolution for XEX2 PE images. /// /// Reads import records from the extracted PE image, decodes their type and /// ordinal, and resolves them against the Xbox 360 export database. use std::fmt; use crate::error::{Result, Xex2Error}; use crate::exports::{self, ExportInfo}; use crate::util::read_u32_be; use crate::Xex2File; /// The type of an import record. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ImportRecordType { /// A 4-byte variable slot (record_type 0x00). Variable, /// A 16-byte function thunk stub (record_type 0x01). Thunk, } impl fmt::Display for ImportRecordType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ImportRecordType::Variable => write!(f, "variable"), ImportRecordType::Thunk => write!(f, "thunk"), } } } /// A decoded import record from the PE image. #[derive(Debug, Clone)] pub struct ImportRecord { /// The original memory address from the XEX2 import table. pub address: u32, /// Offset within the PE image (address - load_address). pub pe_offset: usize, /// Whether this is a variable slot or function thunk. pub record_type: ImportRecordType, /// The ordinal number identifying the imported function/variable. pub ordinal: u16, } /// An import record resolved against the export database. #[derive(Debug, Clone)] pub struct ResolvedImport { /// The decoded import record. pub record: ImportRecord, /// The library filename (e.g. "xboxkrnl.exe"). pub library: String, /// The export info if the ordinal was found in the database. pub export: Option, } /// Decodes all import records from the PE image and resolves them against /// the export database. /// /// Iterates over each import library's addresses, reads the u32 value at /// the corresponding PE offset, extracts record_type and ordinal, then /// looks up the ordinal in the export database. pub fn decode_import_records(pe_image: &[u8], xex: &Xex2File) -> Result> { let imports = xex .optional_headers .import_libraries .as_ref() .ok_or_else(|| Xex2Error::InvalidPeImage("no import libraries header".into()))?; let load_address = xex.security_info.load_address; let mut resolved = Vec::new(); for lib in &imports.libraries { for &addr in &lib.import_addresses { let pe_offset = (addr.wrapping_sub(load_address)) as usize; let raw = read_u32_be(pe_image, pe_offset).map_err(|_| { Xex2Error::InvalidPeImage(format!( "import address 0x{addr:08X} (offset 0x{pe_offset:08X}) out of bounds" )) })?; let record_type_byte = (raw >> 24) & 0xFF; let ordinal = (raw & 0xFFFF) as u16; let record_type = match record_type_byte { 0x00 => ImportRecordType::Variable, 0x01 => ImportRecordType::Thunk, _ => { // Unknown record types — treat as variable (best effort) ImportRecordType::Variable } }; let record = ImportRecord { address: addr, pe_offset, record_type, ordinal, }; let export = exports::lookup(&lib.name, ordinal).cloned(); resolved.push(ResolvedImport { record, library: lib.name.clone(), export, }); } } Ok(resolved) } #[cfg(test)] mod tests { use super::*; #[test] fn test_import_record_type_display() { assert_eq!(ImportRecordType::Variable.to_string(), "variable"); assert_eq!(ImportRecordType::Thunk.to_string(), "thunk"); } }