feat: add export database and import record decoding (M7)
- New src/exports.rs: embeds doc/xbox360_exports.md via include_str! and lazily parses it into a lookup table (OnceLock). Covers 2,913 exports across xboxkrnl.exe, xam.xex, and xbdm.xex. - New src/imports.rs: decodes import records from extracted PE images by reading the u32 at each import address, extracting record type (variable/thunk) and ordinal, and resolving against the export DB. - inspect command now shows a full resolved imports table with names. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
121
src/imports.rs
Normal file
121
src/imports.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
/// 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<ExportInfo>,
|
||||
}
|
||||
|
||||
/// 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<Vec<ResolvedImport>> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user