Initial xenia-rs

This commit is contained in:
MechaCat02
2026-04-12 18:25:46 +02:00
parent 1da37db584
commit 06a23212fb
50 changed files with 6759 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
[package]
name = "xenia-xex"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
xenia-types = { workspace = true }
xenia-memory = { workspace = true }
tracing = { workspace = true }
byteorder = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }

View File

@@ -0,0 +1,55 @@
/// XEX2 file header. Parsed from the beginning of an Xbox 360 executable.
#[derive(Debug)]
pub struct Xex2Header {
pub magic: u32,
pub module_flags: u32,
pub header_size: u32,
pub security_offset: u32,
pub header_count: u32,
pub optional_headers: Vec<Xex2OptionalHeader>,
pub security_info: Option<Xex2SecurityInfo>,
}
#[derive(Debug)]
pub struct Xex2OptionalHeader {
pub key: u32,
pub value: u32,
}
#[derive(Debug)]
pub struct Xex2SecurityInfo {
pub image_size: u32,
pub load_address: u32,
pub export_table_address: u32,
pub image_flags: u32,
pub page_descriptors: Vec<Xex2PageDescriptor>,
}
#[derive(Debug, Clone, Copy)]
pub struct Xex2PageDescriptor {
pub size_and_info: u32,
}
impl Xex2PageDescriptor {
pub fn page_count(&self) -> u32 {
self.size_and_info >> 4
}
pub fn info(&self) -> u32 {
self.size_and_info & 0xF
}
}
/// XEX2 magic: "XEX2"
pub const XEX2_MAGIC: u32 = 0x58455832;
/// Optional header keys
pub mod header_keys {
pub const ENTRY_POINT: u32 = 0x00010100;
pub const IMAGE_BASE_ADDRESS: u32 = 0x00010201;
pub const IMPORT_LIBRARIES: u32 = 0x000103FF;
pub const TLS_INFO: u32 = 0x00020200;
pub const EXECUTION_INFO: u32 = 0x00040006;
pub const DEFAULT_STACK_SIZE: u32 = 0x00020200;
pub const ORIGINAL_PE_NAME: u32 = 0x000183FF;
}

View File

@@ -0,0 +1,4 @@
pub mod header;
pub mod loader;
pub use header::Xex2Header;

View File

@@ -0,0 +1,98 @@
use crate::header::*;
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{self, Cursor, Read, Seek, SeekFrom};
/// Parse a XEX2 header from raw file data.
pub fn parse_xex2_header(data: &[u8]) -> io::Result<Xex2Header> {
let mut cursor = Cursor::new(data);
let magic = cursor.read_u32::<BigEndian>()?;
if magic != XEX2_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid XEX2 magic: {:#010x} (expected {:#010x})", magic, XEX2_MAGIC),
));
}
let module_flags = cursor.read_u32::<BigEndian>()?;
let header_size = cursor.read_u32::<BigEndian>()?;
let _reserved = cursor.read_u32::<BigEndian>()?;
let security_offset = cursor.read_u32::<BigEndian>()?;
let header_count = cursor.read_u32::<BigEndian>()?;
let mut optional_headers = Vec::new();
for _ in 0..header_count {
let key = cursor.read_u32::<BigEndian>()?;
let value = cursor.read_u32::<BigEndian>()?;
optional_headers.push(Xex2OptionalHeader { key, value });
}
// Parse security info
let security_info = if (security_offset as usize) < data.len() {
cursor.seek(SeekFrom::Start(security_offset as u64))?;
Some(parse_security_info(&mut cursor)?)
} else {
None
};
Ok(Xex2Header {
magic,
module_flags,
header_size,
security_offset,
header_count,
optional_headers,
security_info,
})
}
fn parse_security_info(cursor: &mut Cursor<&[u8]>) -> io::Result<Xex2SecurityInfo> {
let _header_size = cursor.read_u32::<BigEndian>()?;
let image_size = cursor.read_u32::<BigEndian>()?;
// Skip RSA signature (256 bytes) and other security fields
let mut skip_buf = [0u8; 256];
cursor.read_exact(&mut skip_buf)?;
// Skip image info hash (20 bytes) and import table hash (20 bytes)
cursor.read_exact(&mut [0u8; 20])?;
cursor.read_exact(&mut [0u8; 20])?;
let load_address = cursor.read_u32::<BigEndian>()?;
let _load_size = cursor.read_u32::<BigEndian>()?;
let export_table_address = cursor.read_u32::<BigEndian>()?;
let image_flags = cursor.read_u32::<BigEndian>()?;
// Read page descriptor count
let page_descriptor_count = cursor.read_u32::<BigEndian>()?;
let mut page_descriptors = Vec::new();
for _ in 0..page_descriptor_count {
let size_and_info = cursor.read_u32::<BigEndian>()?;
page_descriptors.push(Xex2PageDescriptor { size_and_info });
}
Ok(Xex2SecurityInfo {
image_size,
load_address,
export_table_address,
image_flags,
page_descriptors,
})
}
/// Get an optional header value by key.
pub fn get_opt_header(header: &Xex2Header, key: u32) -> Option<u32> {
header.optional_headers.iter()
.find(|h| h.key == key)
.map(|h| h.value)
}
/// Get the entry point address from the XEX2 header.
pub fn get_entry_point(header: &Xex2Header) -> Option<u32> {
get_opt_header(header, header_keys::ENTRY_POINT)
}
/// Get the image base address.
pub fn get_image_base(header: &Xex2Header) -> Option<u32> {
get_opt_header(header, header_keys::IMAGE_BASE_ADDRESS)
}