//! `.tls` section parser for PE32 PowerPC.
//!
//! When MSVC links a binary that uses `__declspec(thread)` storage, it emits
//! a `.tls` section plus an IMAGE_TLS_DIRECTORY32 inside `.rdata`. The
//! directory points at:
//!   - the raw initialised TLS data range (start, end VAs)
//!   - the address of the index field (a u32 written at runtime by the
//!     loader to identify which TLS slot was assigned)
//!   - an array of TLS callback function pointers (NUL-terminated)
//!   - the size of the zero-fill area appended after raw data
//!
//! Xbox 360 binaries follow the standard PE layout. Sylpheed has no `.tls`
//! section and no TLS directory — the parser simply returns `None` and
//! callers emit zero rows.
//!
//! Reference: Microsoft PE/COFF spec, IMAGE_TLS_DIRECTORY32 layout.

use crate::pe::PeSection;

/// One TLS callback function pointer extracted from the directory's
/// callback array.
#[derive(Debug, Clone, Copy)]
pub struct TlsCallback {
    pub address: u32,
}

/// Parsed `.tls` directory information. All fields are absolute VAs.
#[derive(Debug, Clone)]
pub struct TlsInfo {
    /// VA of the start of the initialised raw TLS data (template).
    pub raw_data_start: u32,
    /// VA of one-past-end of the raw TLS data.
    pub raw_data_end: u32,
    /// VA of the u32 the loader writes the assigned slot index into.
    pub index_address: u32,
    /// VA of the zero-terminated callback array; 0 when no callbacks.
    pub callback_array: u32,
    /// Bytes of zero-fill appended after the raw template at thread init.
    pub zero_fill_size: u32,
    /// Characteristics flags (alignment / etc).
    pub characteristics: u32,
    /// Resolved TLS callbacks (parsed from `callback_array`).
    pub callbacks: Vec<TlsCallback>,
}

/// Parse the `.tls` section. Returns `None` if the binary has no `.tls`
/// section or the directory is malformed.
pub fn parse_tls(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Option<TlsInfo> {
    // Find the `.tls` section. The IMAGE_TLS_DIRECTORY32 lives somewhere
    // in `.rdata`; rather than hunt the IMAGE_DATA_DIRECTORY entry through
    // the optional header, we accept any 24-byte struct at the start of
    // `.tls` if the section's raw data looks like a valid directory.
    //
    // Per MS docs, IMAGE_TLS_DIRECTORY32 layout (24 bytes):
    //   +0x00 StartAddressOfRawData (VA, 4)
    //   +0x04 EndAddressOfRawData   (VA, 4)
    //   +0x08 AddressOfIndex        (VA, 4)
    //   +0x0C AddressOfCallBacks    (VA, 4 — array of FN ptrs, NUL-terminated)
    //   +0x10 SizeOfZeroFill        (4)
    //   +0x14 Characteristics       (4)
    let tls_section = sections.iter().find(|s| s.name == ".tls")?;
    let off = tls_section.virtual_address as usize;
    if off + 24 > pe.len() { return None; }

    // Xbox 360 PE bodies are big-endian; this is consistent with how we
    // parse the PE elsewhere (e.g. xref scanning reads BE u32 from PE).
    let read_u32 = |start: usize| -> u32 {
        u32::from_be_bytes([pe[start], pe[start + 1], pe[start + 2], pe[start + 3]])
    };

    let raw_data_start = read_u32(off);
    let raw_data_end   = read_u32(off + 4);
    let index_address  = read_u32(off + 8);
    let callback_array = read_u32(off + 12);
    let zero_fill_size = read_u32(off + 16);
    let characteristics = read_u32(off + 20);

    // Sanity: raw_data_start should land somewhere inside the image.
    if raw_data_start == 0 && raw_data_end == 0 && index_address == 0 {
        return None;
    }

    // Walk the callback array (zero-terminated array of u32 VAs).
    let mut callbacks = Vec::new();
    if callback_array != 0 {
        let mut p = callback_array.wrapping_sub(image_base) as usize;
        while p + 4 <= pe.len() {
            let v = read_u32(p);
            if v == 0 { break; }
            callbacks.push(TlsCallback { address: v });
            p += 4;
            if callbacks.len() >= 64 { break; } // sanity cap
        }
    }

    Some(TlsInfo {
        raw_data_start,
        raw_data_end,
        index_address,
        callback_array,
        zero_fill_size,
        characteristics,
        callbacks,
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pe::PeSection;

    fn mk_section(name: &str, va: u32, size: u32) -> PeSection {
        PeSection {
            name: name.into(),
            virtual_address: va,
            virtual_size: size,
            raw_offset: va,
            raw_size: size,
            flags: 0x4000_0040,
        }
    }

    #[test]
    fn returns_none_when_no_tls_section() {
        let pe = vec![0u8; 0x100];
        let sections = vec![mk_section(".text", 0x10, 0x40)];
        assert!(parse_tls(&pe, 0x82000000, &sections).is_none());
    }

    #[test]
    fn parses_directory_and_callback_array() {
        let image_base = 0x82000000u32;
        let mut pe = vec![0u8; 0x4000];

        // Place the .tls section at RVA 0x100 with the directory.
        let tls_va: u32 = 0x100;
        let cb_va: u32 = 0x200;
        // Directory fields:
        let raw_start = 0x800u32;
        let raw_end   = 0x900u32;
        let idx       = 0x1000u32;
        let zero_fill = 0x40u32;
        let chars     = 0x0u32;
        let cb_array  = image_base + cb_va;
        for (i, v) in [
            image_base + raw_start, image_base + raw_end,
            image_base + idx, cb_array, zero_fill, chars,
        ].iter().enumerate() {
            pe[tls_va as usize + i * 4..tls_va as usize + i * 4 + 4]
                .copy_from_slice(&v.to_be_bytes());
        }

        // Two callbacks + NUL terminator at cb_va.
        let cb1 = image_base + 0x500;
        let cb2 = image_base + 0x600;
        pe[cb_va as usize..cb_va as usize + 4].copy_from_slice(&cb1.to_be_bytes());
        pe[cb_va as usize + 4..cb_va as usize + 8].copy_from_slice(&cb2.to_be_bytes());
        // pe[cb_va + 8..cb_va + 12] already zero (terminator).

        let sections = vec![mk_section(".tls", tls_va, 0x100)];
        let info = parse_tls(&pe, image_base, &sections).expect("parses");

        assert_eq!(info.raw_data_start, image_base + raw_start);
        assert_eq!(info.raw_data_end, image_base + raw_end);
        assert_eq!(info.index_address, image_base + idx);
        assert_eq!(info.callback_array, cb_array);
        assert_eq!(info.zero_fill_size, zero_fill);
        assert_eq!(info.callbacks.len(), 2);
        assert_eq!(info.callbacks[0].address, cb1);
        assert_eq!(info.callbacks[1].address, cb2);
    }
}
