//! 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<Vec<PeSection>> {
    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)
}
