//! Minimal PE parser for Xbox 360 executables. //! PE headers are little-endian even on the big-endian Xbox 360. use serde::Serialize; #[derive(Serialize, Debug, Clone)] pub struct PeSection { pub name: String, pub virtual_address: u32, pub virtual_size: u32, pub raw_offset: u32, pub raw_size: u32, pub flags: u32, } impl PeSection { pub fn is_code(&self) -> bool { self.flags & 0x20000000 != 0 // IMAGE_SCN_MEM_EXECUTE } } fn le_u16(data: &[u8], off: usize) -> u16 { u16::from_le_bytes([data[off], data[off + 1]]) } fn le_u32(data: &[u8], off: usize) -> u32 { u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) } pub fn parse_sections(pe: &[u8]) -> anyhow::Result> { anyhow::ensure!(pe.len() >= 64, "PE too small"); anyhow::ensure!(pe[0] == b'M' && pe[1] == b'Z', "not a PE (bad MZ)"); let e_lfanew = le_u32(pe, 0x3C) as usize; anyhow::ensure!(e_lfanew + 4 <= pe.len(), "e_lfanew out of bounds"); let nt_sig = le_u32(pe, e_lfanew); anyhow::ensure!(nt_sig == 0x00004550, "bad PE signature: 0x{nt_sig:08X}"); let file_header_off = e_lfanew + 4; let num_sections = le_u16(pe, file_header_off + 2) as usize; let opt_header_size = le_u16(pe, file_header_off + 16) as usize; let section_table_off = file_header_off + 20 + opt_header_size; let mut sections = Vec::new(); for i in 0..num_sections { let s = section_table_off + i * 40; if s + 40 > pe.len() { break; } let name_bytes = &pe[s..s + 8]; let name = std::str::from_utf8(name_bytes) .unwrap_or("???") .trim_end_matches('\0') .to_string(); sections.push(PeSection { name, virtual_size: le_u32(pe, s + 8), virtual_address: le_u32(pe, s + 12), raw_size: le_u32(pe, s + 16), raw_offset: le_u32(pe, s + 20), flags: le_u32(pe, s + 36), }); } Ok(sections) }