# Copyright (C) 2026, Advanced Micro Devices, Inc.  All rights reserved.
# Portions of this file consist of AI-generated content.
# SPDX-License-Identifier: MIT

"""Versal Gen 2 (VE/VM) boot image loader module.

This module provides support for Versal AI Edge Gen 2 (VE) and Versal Prime Gen 2 (VM)
boot images (PDI files).

Reference: https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide/Versal-AI-Edge-Series-Gen-2-and-Versal-Prime-Series-Gen-2-Boot-Image-Format
"""

from typing import Optional, List, Tuple
from enum import IntEnum
import struct
from .loader import (
    BootHeader,
    ImageHeaderTable,
    ImageHeader,
    PartitionHeader,
)
from .versal import VersalBootImageLoader


class Versal2VE2VMBootHeader(BootHeader):
    """Versal Gen 2 (VE/VM)-specific boot header with additional fields.
    
    The Versal Gen 2 boot header (PDI header) is used by PMC BootROM.
    Based on the attributes set in the boot header, PMC BootROM validates
    the Platform Loader and Manager (PLM) and loads it to the PPU RAM.
    """
    
    # SelectMAP bus width patterns (first 16 bytes)
    SMAP_WIDTH_X8 = bytes([0x00, 0x00, 0x00, 0xDD, 0x11, 0x22, 0x33, 0x44,
                           0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC])
    SMAP_WIDTH_X16 = bytes([0x00, 0x00, 0xDD, 0x00, 0x22, 0x11, 0x44, 0x33,
                            0x66, 0x55, 0x88, 0x77, 0xAA, 0x99, 0xCC, 0xBB])
    SMAP_WIDTH_X32 = bytes([0xDD, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
                            0x88, 0x77, 0x66, 0x55, 0xCC, 0xBB, 0xAA, 0x99])
    
    # QSPI bus width word (0xAA995566 little-endian)
    QSPI_BUS_WIDTH = 0xAA995566
    
    # Image identification string "XLNX" (0x584C4E58 little-endian)
    IMAGE_IDENTIFICATION = 0x584C4E58
    
    # Encryption key source values
    KEY_SOURCE_UNENCRYPTED = 0x00000000
    KEY_SOURCE_EFUSE_RED = 0xA5C3C5A3
    KEY_SOURCE_EFUSE_BLACK = 0xA5C3C5A5
    KEY_SOURCE_EFUSE_OBFUSCATED = 0xA5C3C5A7
    KEY_SOURCE_BBRAM_RED = 0x3A5C3C5A
    KEY_SOURCE_BBRAM_BLACK = 0x3A5C3C59
    KEY_SOURCE_BBRAM_OBFUSCATED = 0x3A5C3C57
    KEY_SOURCE_BH_BLACK = 0xA35C7C53
    KEY_SOURCE_BH_OBFUSCATED = 0xA35C7CA5
    
    # Boot header offsets (per Versal Gen 2 documentation)
    BH_SMAP_BUS_WIDTH_OFFSET = 0x00  # 16 bytes
    BH_QSPI_BUS_WIDTH_OFFSET = 0x10
    BH_IMAGE_ID_OFFSET = 0x14
    BH_KEY_SOURCE_OFFSET = 0x18
    BH_PLM_SOURCE_OFFSET = 0x1C
    BH_PMC_CDO_LOAD_ADDR_OFFSET = 0x20
    BH_PMC_CDO_LENGTH_OFFSET = 0x24
    BH_TOTAL_PMC_CDO_LENGTH_OFFSET = 0x28
    BH_PLM_LENGTH_OFFSET = 0x2C
    BH_TOTAL_PLM_LENGTH_OFFSET = 0x30
    BH_ATTRIBUTES_OFFSET = 0x34
    BH_GREY_BLACK_KEY_OFFSET = 0x38  # 32 bytes
    BH_GREY_BLACK_IV_OFFSET = 0x58  # 12 bytes
    BH_SECURE_HDR_IV_OFFSET = 0x64  # 12 bytes for Hash Block
    BH_PUF_SHUTTER_VALUE_OFFSET = 0x70
    BH_RING_OSCILLATOR_CONFIG_OFFSET = 0x74
    BH_PARTITION_REVOCATION_ID_OFFSET = 0x78
    BH_USER_DATA_OFFSET = 0x7C  # 516 bytes
    BH_AUTH_HEADER_1_OFFSET = 0x280
    BH_HASH_BLOCK_SIZE_1_OFFSET = 0x284
    BH_TOTAL_PPK_SIZE_1_OFFSET = 0x288
    BH_ACTUAL_PPK_SIZE_1_OFFSET = 0x28C
    BH_TOTAL_PDI_SIGNATURE_SIZE_1_OFFSET = 0x290
    BH_ACTUAL_PDI_SIGNATURE_SIZE_1_OFFSET = 0x294
    BH_ROM_RESERVED_OFFSET = 0x298  # 56 bytes
    BH_PLM_RESERVED_OFFSET = 0x2D0  # 100 bytes
    BH_REGISTER_INIT_OFFSET = 0x334  # 2048 bytes
    BH_PUF_HELPER_DATA_OFFSET = 0xB34  # 1544 bytes
    BH_CHECKSUM_OFFSET = 0x113C
    
    # Boot header total size
    BOOT_HEADER_SIZE = 0x1140  # 4416 bytes
    
    # Boot header attribute bit shifts (per Versal Gen 2 documentation)
    BH_ATTR_CDI_GENERATION_SHIFT = 20
    BH_ATTR_SIGNED_IMAGE_SHIFT = 18
    BH_ATTR_PUF_MODE_SHIFT = 16
    BH_ATTR_BH_AUTH_SHIFT = 14
    BH_ATTR_DPA_CM_SHIFT = 10
    BH_ATTR_BI_INTEGRITY_SHIFT = 8
    BH_ATTR_PUF_HD_SHIFT = 6
    
    # Boot header attribute bit masks
    BH_ATTR_CDI_GENERATION_MASK = 0x3
    BH_ATTR_SIGNED_IMAGE_MASK = 0x3
    BH_ATTR_PUF_MODE_MASK = 0x3
    BH_ATTR_BH_AUTH_MASK = 0x3
    BH_ATTR_DPA_CM_MASK = 0x3
    BH_ATTR_BI_INTEGRITY_MASK = 0x3
    BH_ATTR_PUF_HD_MASK = 0x3

    class CdiGeneration(IntEnum):
        """CDI Generation types (bits 21:20)."""
        EFUSE_BASED = 0  # DICE CDI generation based on eFuse UDS programmed
        RESERVED_1 = 1
        RESERVED_2 = 2
        GENERATE_CDI = 3  # Generate DICE CDI

    class SignedImage(IntEnum):
        """Signed image types (bits 19:18)."""
        UNSIGNED = 0
        RESERVED_1 = 1
        RESERVED_2 = 2
        SIGNED = 3  # Signed image

    class PufMode(IntEnum):
        """PUF Mode types (bits 17:16)."""
        PUF_12K = 0  # PUF 12K mode
        RESERVED_1 = 1
        RESERVED_2 = 2
        PUF_4K = 3  # PUF 4K mode

    class BHAuthentication(IntEnum):
        """Boot Header Authentication types (bits 15:14)."""
        EFUSE_BASED = 0  # Authentication decided based on eFUSE RSA/ECDSA bits
        RESERVED_1 = 1
        RESERVED_2 = 2
        BH_AUTH = 3  # Authenticates boot image excluding PPK hash and SPK ID verification

    class BIIntegrity(IntEnum):
        """Boot Image Integrity selection types (bits 9:8)."""
        RESERVED_0 = 0
        RESERVED_1 = 1
        RESERVED_2 = 2
        SHA3 = 3  # SHA3 used as hash function for BI integrity check

    class PufHdLocation(IntEnum):
        """PUF HD Location types (bits 7:6)."""
        EFUSE = 0  # PUF HD is in eFUSE
        RESERVED_1 = 1
        RESERVED_2 = 2
        BOOT_HEADER = 3  # PUF HD is part of boot header

    class DpaCm(IntEnum):
        """DPA Counter Measure types (bits 11:10)."""
        DISABLED_0 = 0
        DISABLED_1 = 1
        DISABLED_2 = 2
        ENABLED = 3  # Enabled (eFUSE overrides this)
    
    def __init__(self):
        """Initialize the Versal Gen 2 boot header."""
        super().__init__()
        self.smap_bus_width: Optional[bytes] = None  # 0x00 - 0x0F (16 bytes)
        self.qspi_bus_width: Optional[int] = None  # 0x10
        self.image_identification: Optional[int] = None  # 0x14
        self.encryption_key_source: Optional[int] = None  # 0x18
        self.plm_source_offset: Optional[int] = None  # 0x1C
        self.pmc_cdo_load_address: Optional[int] = None  # 0x20
        self.pmc_cdo_length: Optional[int] = None  # 0x24
        self.total_pmc_cdo_length: Optional[int] = None  # 0x28
        self.plm_length: Optional[int] = None  # 0x2C
        self.total_plm_length: Optional[int] = None  # 0x30
        self.boot_header_attributes: Optional[int] = None  # 0x34
        self.grey_black_key: Optional[bytes] = None  # 0x38-0x57 (32 bytes)
        self.grey_black_iv: Optional[bytes] = None  # 0x58-0x63 (12 bytes)
        self.secure_header_iv: Optional[bytes] = None  # 0x64-0x6F (12 bytes) for Hash Block
        self.puf_shutter_value: Optional[int] = None  # 0x70
        self.ring_oscillator_config: Optional[int] = None  # 0x74
        self.partition_revocation_id: Optional[int] = None  # 0x78
        self.user_data: Optional[bytes] = None  # 0x7C-0x27F (516 bytes)
        self.auth_header_1: Optional[int] = None  # 0x280
        self.hash_block_size_1: Optional[int] = None  # 0x284
        self.total_ppk_size_1: Optional[int] = None  # 0x288
        self.actual_ppk_size_1: Optional[int] = None  # 0x28C
        self.total_pdi_signature_size_1: Optional[int] = None  # 0x290
        self.actual_pdi_signature_size_1: Optional[int] = None  # 0x294
        # ROM Reserved 0x298-0x2CF (56 bytes)
        self.rom_reserved: Optional[bytes] = None  # 0x298-0x2CF (56 bytes)
        # PLM Reserved 0x2D0-0x333 (100 bytes)
        self.plm_reserved: Optional[bytes] = None  # 0x2D0-0x333 (100 bytes)
        self.register_init: Optional[bytes] = None  # 0x334-0xB33 (2048 bytes)
        self.puf_helper_data: Optional[bytes] = None  # 0xB34-0x113B (1544 bytes)
        self.boot_header_checksum: Optional[int] = None  # 0x113C
        # Aliases for backward compatibility
        self.pmc_data_load_address: Optional[int] = None  # Alias for pmc_cdo_load_address
        self.pmc_data_length: Optional[int] = None  # Alias for pmc_cdo_length
        self.total_pmc_data_length: Optional[int] = None  # Alias for total_pmc_cdo_length
        self.black_key: Optional[bytes] = None  # Alias for grey_black_key
        self.black_iv: Optional[bytes] = None  # Alias for grey_black_iv
        self.meta_header_offset: Optional[int] = None  # Meta header offset (derived from user_data area)

    def compute_checksum(self) -> int:
        """Compute the checksum over the boot header fields.
        
        The checksum is computed over all words from 0x10 to 0x1138 inclusive.
        (Excludes SMAP bus width detection at the beginning)
        First, sum all the values then negate that and extract 4 bytes.
        
        Returns:
            The computed checksum as a 32-bit unsigned integer.
        """
        words = [
            self.qspi_bus_width or 0,  # 0x10
            self.image_identification or 0,  # 0x14
            self.encryption_key_source or 0,  # 0x18
            self.plm_source_offset or 0,  # 0x1C
            self.pmc_cdo_load_address or 0,  # 0x20
            self.pmc_cdo_length or 0,  # 0x24
            self.total_pmc_cdo_length or 0,  # 0x28
            self.plm_length or 0,  # 0x2C
            self.total_plm_length or 0,  # 0x30
            self.boot_header_attributes or 0,  # 0x34
        ]
        
        # Add grey/black key (32 bytes = 8 words)
        if self.grey_black_key:
            for i in range(0, 32, 4):
                words.append(int.from_bytes(self.grey_black_key[i:i+4], 'little'))
        else:
            words.extend([0] * 8)
        
        # Add grey/black IV (12 bytes = 3 words)
        if self.grey_black_iv:
            for i in range(0, 12, 4):
                words.append(int.from_bytes(self.grey_black_iv[i:i+4], 'little'))
        else:
            words.extend([0] * 3)
        
        # Add secure header IV (12 bytes = 3 words)
        if self.secure_header_iv:
            for i in range(0, 12, 4):
                words.append(int.from_bytes(self.secure_header_iv[i:i+4], 'little'))
        else:
            words.extend([0] * 3)
        
        # Add PUF shutter value (0x70)
        words.append(self.puf_shutter_value or 0)
        
        # Add Ring Oscillator Config (0x74)
        words.append(self.ring_oscillator_config or 0)
        
        # Add Partition Revocation ID (0x78)
        words.append(self.partition_revocation_id or 0)
        
        # Add User Data (516 bytes = 129 words, 0x7C - 0x27F)
        if self.user_data:
            for i in range(0, 516, 4):
                if i + 4 <= len(self.user_data):
                    words.append(int.from_bytes(self.user_data[i:i+4], 'little'))
                else:
                    words.append(0)
        else:
            words.extend([0] * 129)
        
        # Add Authentication Header-1 (0x280)
        words.append(self.auth_header_1 or 0)
        
        # Add Hash Block Size-1 (0x284)
        words.append(self.hash_block_size_1 or 0)
        
        # Add Total PPK Size-1 (0x288)
        words.append(self.total_ppk_size_1 or 0)
        
        # Add Actual PPK Size-1 (0x28C)
        words.append(self.actual_ppk_size_1 or 0)
        
        # Add Total PDI Signature Size-1 (0x290)
        words.append(self.total_pdi_signature_size_1 or 0)
        
        # Add Actual PDI Signature Size-1 (0x294)
        words.append(self.actual_pdi_signature_size_1 or 0)
        
        # Add ROM Reserved area (56 bytes = 14 words, 0x298 - 0x2CF)
        if self.rom_reserved:
            for i in range(0, 56, 4):
                if i + 4 <= len(self.rom_reserved):
                    words.append(int.from_bytes(self.rom_reserved[i:i+4], 'little'))
                else:
                    words.append(0)
        else:
            words.extend([0] * 14)
        
        # Add PLM Reserved area (100 bytes = 25 words, 0x2D0 - 0x333)
        if self.plm_reserved:
            for i in range(0, 100, 4):
                if i + 4 <= len(self.plm_reserved):
                    words.append(int.from_bytes(self.plm_reserved[i:i+4], 'little'))
                else:
                    words.append(0)
        else:
            words.extend([0] * 25)
        
        # Add register init (2048 bytes = 512 words, 0x334 - 0xB33)
        if self.register_init:
            for i in range(0, 2048, 4):
                if i + 4 <= len(self.register_init):
                    words.append(int.from_bytes(self.register_init[i:i+4], 'little'))
                else:
                    words.append(0)
        else:
            words.extend([0] * 512)
        
        # Add PUF helper data (1544 bytes = 386 words, 0xB34 - 0x113B)
        if self.puf_helper_data:
            for i in range(0, 1544, 4):
                if i + 4 <= len(self.puf_helper_data):
                    words.append(int.from_bytes(self.puf_helper_data[i:i+4], 'little'))
                else:
                    words.append(0)
        else:
            words.extend([0] * 386)
        
        # Sum all words and mask to 32 bits
        checksum_sum = sum(words) & 0xFFFFFFFF
        
        # Invert (one's complement)
        checksum = (~checksum_sum) & 0xFFFFFFFF
        
        return checksum

    def verify_checksum(self) -> bool:
        """Verify the boot header checksum.
        
        Returns:
            True if the checksum is valid, False otherwise.
        """
        if self.boot_header_checksum is None:
            return False
        return self.compute_checksum() == self.boot_header_checksum

    def get_cdi_generation(self) -> 'Versal2VE2VMBootHeader.CdiGeneration':
        """Extract CDI generation from boot header attributes (bits 21:20).
        
        Returns:
            CdiGeneration enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.CdiGeneration.EFUSE_BASED
        return Versal2VE2VMBootHeader.CdiGeneration((self.boot_header_attributes >> 20) & 0x3)

    def get_signed_image(self) -> 'Versal2VE2VMBootHeader.SignedImage':
        """Extract signed image flag from boot header attributes (bits 19:18).
        
        Returns:
            SignedImage enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.SignedImage.UNSIGNED
        return Versal2VE2VMBootHeader.SignedImage((self.boot_header_attributes >> 18) & 0x3)

    def get_puf_mode(self) -> 'Versal2VE2VMBootHeader.PufMode':
        """Extract PUF mode from boot header attributes (bits 17:16).
        
        Returns:
            PufMode enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.PufMode.PUF_12K
        return Versal2VE2VMBootHeader.PufMode((self.boot_header_attributes >> 16) & 0x3)

    def get_bh_authentication(self) -> 'Versal2VE2VMBootHeader.BHAuthentication':
        """Extract boot header authentication from attributes (bits 15:14).
        
        Returns:
            BHAuthentication enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.BHAuthentication.EFUSE_BASED
        return Versal2VE2VMBootHeader.BHAuthentication((self.boot_header_attributes >> 14) & 0x3)

    def get_dpa_cm(self) -> 'Versal2VE2VMBootHeader.DpaCm':
        """Extract DPA counter measure from attributes (bits 11:10).
        
        Returns:
            DpaCm enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.DpaCm.DISABLED_0
        return Versal2VE2VMBootHeader.DpaCm((self.boot_header_attributes >> 10) & 0x3)

    def get_bi_integrity(self) -> 'Versal2VE2VMBootHeader.BIIntegrity':
        """Extract boot image integrity selection from boot header attributes (bits 9:8).
        
        Returns:
            BIIntegrity enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.BIIntegrity.RESERVED_0
        return Versal2VE2VMBootHeader.BIIntegrity((self.boot_header_attributes >> 8) & 0x3)

    def get_puf_hd_location(self) -> 'Versal2VE2VMBootHeader.PufHdLocation':
        """Extract PUF HD location from boot header attributes (bits 7:6).
        
        Returns:
            PufHdLocation enum value.
        """
        if self.boot_header_attributes is None:
            return Versal2VE2VMBootHeader.PufHdLocation.EFUSE
        return Versal2VE2VMBootHeader.PufHdLocation((self.boot_header_attributes >> 6) & 0x3)

    def get_attributes_summary(self) -> str:
        """Get a human-readable summary of the boot header attributes.
        
        Returns:
            String describing the boot header attributes.
        """
        return (f"CDI_Gen={self.get_cdi_generation().name}, "
                f"Signed={self.get_signed_image().name}, "
                f"PUF_Mode={self.get_puf_mode().name}, "
                f"BH_Auth={self.get_bh_authentication().name}, "
                f"DPA_CM={self.get_dpa_cm().name}, "
                f"BI_Integrity={self.get_bi_integrity().name}, "
                f"PUF_HD={self.get_puf_hd_location().name}")

    def to_bytes(self, include_smap_width: bool = False) -> bytes:
        """Serialize the boot header to bytes.
        
        This method generates the binary representation of the boot header
        suitable for writing to a boot_bh.bin file. By default, it excludes
        the first 16 bytes (SMAP bus width detection words) as these are not
        required by the BootROM/PLM.
        
        The output format matches bootgen's dump format for boot headers.
        
        Args:
            include_smap_width: If True, include the 16-byte SMAP bus width
                               detection words at the beginning. Default is False.
        
        Returns:
            The boot header as bytes, starting from offset 0x10 (or 0x00 if
            include_smap_width is True).
        """
        data = bytearray()
        
        # Optionally include SMAP bus width (0x00-0x0F, 16 bytes)
        if include_smap_width:
            if self.smap_bus_width:
                data.extend(self.smap_bus_width)
            else:
                data.extend(bytes(16))
        
        # QSPI bus width / Width Detection Word (0x10, 4 bytes)
        data.extend(struct.pack('<I', self.qspi_bus_width or self.QSPI_BUS_WIDTH))
        
        # Image Identification (0x14, 4 bytes)
        data.extend(struct.pack('<I', self.image_identification or self.IMAGE_IDENTIFICATION))
        
        # Encryption Key Source (0x18, 4 bytes)
        data.extend(struct.pack('<I', self.encryption_key_source or self.KEY_SOURCE_UNENCRYPTED))
        
        # PLM Source Offset (0x1C, 4 bytes)
        data.extend(struct.pack('<I', self.plm_source_offset or 0))
        
        # PMC CDO Load Address (0x20, 4 bytes)
        data.extend(struct.pack('<I', self.pmc_cdo_load_address or 0))
        
        # PMC CDO Length (0x24, 4 bytes)
        data.extend(struct.pack('<I', self.pmc_cdo_length or 0))
        
        # Total PMC CDO Length (0x28, 4 bytes)
        data.extend(struct.pack('<I', self.total_pmc_cdo_length or 0))
        
        # PLM Length (0x2C, 4 bytes)
        data.extend(struct.pack('<I', self.plm_length or 0))
        
        # Total PLM Length (0x30, 4 bytes)
        data.extend(struct.pack('<I', self.total_plm_length or 0))
        
        # Boot Header Attributes (0x34, 4 bytes)
        data.extend(struct.pack('<I', self.boot_header_attributes or 0))
        
        # Grey/Black Key (0x38-0x57, 32 bytes)
        if self.grey_black_key:
            data.extend(self.grey_black_key)
        else:
            data.extend(bytes(32))
        
        # Grey/Black IV (0x58-0x63, 12 bytes)
        if self.grey_black_iv:
            data.extend(self.grey_black_iv)
        else:
            data.extend(bytes(12))
        
        # Secure Header IV for Hash Block (0x64-0x6F, 12 bytes)
        if self.secure_header_iv:
            data.extend(self.secure_header_iv)
        else:
            data.extend(bytes(12))
        
        # PUF Shutter Value (0x70, 4 bytes)
        data.extend(struct.pack('<I', self.puf_shutter_value or 0))
        
        # Ring Oscillator Config Value (0x74, 4 bytes)
        data.extend(struct.pack('<I', self.ring_oscillator_config or 0))
        
        # Partition Revocation ID (0x78, 4 bytes)
        data.extend(struct.pack('<I', self.partition_revocation_id or 0))
        
        # User Data (0x7C-0x27F, 516 bytes)
        if self.user_data:
            data.extend(self.user_data[:516])
            if len(self.user_data) < 516:
                data.extend(bytes(516 - len(self.user_data)))
        else:
            data.extend(bytes(516))
        
        # Authentication Header-1 (0x280, 4 bytes)
        data.extend(struct.pack('<I', self.auth_header_1 or 0))
        
        # Hash Block Size-1 (0x284, 4 bytes)
        data.extend(struct.pack('<I', self.hash_block_size_1 or 0))
        
        # Total PPK Size-1 (0x288, 4 bytes)
        data.extend(struct.pack('<I', self.total_ppk_size_1 or 0))
        
        # Actual PPK Size-1 (0x28C, 4 bytes)
        data.extend(struct.pack('<I', self.actual_ppk_size_1 or 0))
        
        # Total PDI Signature Size-1 (0x290, 4 bytes)
        data.extend(struct.pack('<I', self.total_pdi_signature_size_1 or 0))
        
        # Actual PDI Signature Size-1 (0x294, 4 bytes)
        data.extend(struct.pack('<I', self.actual_pdi_signature_size_1 or 0))
        
        # ROM Reserved (0x298-0x2CF, 56 bytes)
        data.extend(bytes(56))
        
        # PLM Reserved (0x2D0-0x333, 100 bytes)
        # Bootgen writes meta_header_offset at the first 4 bytes (0x2D0-0x2D3)
        # This appears at file offset 0x2C0 in the _bh.bin file
        plm_reserved = bytearray(100)
        if self.meta_header_offset is not None:
            plm_reserved[0:4] = struct.pack('<I', self.meta_header_offset)
        data.extend(bytes(plm_reserved))
        
        # Register Init (0x334-0xB33, 2048 bytes)
        if self.register_init:
            data.extend(self.register_init[:2048])
            if len(self.register_init) < 2048:
                data.extend(bytes(2048 - len(self.register_init)))
        else:
            data.extend(bytes(2048))
        
        # PUF Helper Data (0xB34-0x113B, 1544 bytes)
        if self.puf_helper_data:
            data.extend(self.puf_helper_data[:1544])
            if len(self.puf_helper_data) < 1544:
                data.extend(bytes(1544 - len(self.puf_helper_data)))
        else:
            data.extend(bytes(1544))
        
        # Boot Header Checksum (0x113C, 4 bytes)
        data.extend(struct.pack('<I', self.boot_header_checksum or 0))
        
        return bytes(data)


class Versal2VE2VMImageHeaderTable(ImageHeaderTable):
    """Versal Gen 2 (VE/VM)-specific image header table."""
    
    # Valid versions
    VERSION_1_0 = 0x00010000  # v1.00
    VERSION_2_0 = 0x00020000  # IHT, PHT sizes doubled
    VERSION_3_0 = 0x00030000  # updated secure chunk size to 32 KB from 64 KB
    VERSION_4_0 = 0x00040000  # Added AAD support for IHT
    
    # Image Header Table offsets (per Versal Gen 2 documentation)
    IHT_VERSION_OFFSET = 0x00
    IHT_IMAGE_COUNT_OFFSET = 0x04
    IHT_IMAGE_HEADER_OFFSET = 0x08
    IHT_PARTITION_COUNT_OFFSET = 0x0C
    IHT_PARTITION_HEADER_OFFSET = 0x10
    IHT_SECONDARY_BOOT_ADDR_OFFSET = 0x14
    IHT_ID_CODE_OFFSET = 0x18
    IHT_ATTRIBUTES_OFFSET = 0x1C
    IHT_PDI_ID_OFFSET = 0x20
    IHT_RESERVED_OFFSET = 0x24
    IHT_IDENTIFICATION_STRING_OFFSET = 0x28
    IHT_HEADERS_SIZE_OFFSET = 0x2C
    IHT_TOTAL_META_HEADER_LENGTH_OFFSET = 0x30
    IHT_META_HEADER_IV_OFFSET = 0x34  # 12 bytes
    IHT_ENCRYPTION_STATUS_OFFSET = 0x40
    IHT_EXTENDED_ID_CODE_OFFSET = 0x44
    IHT_HASH_BLOCK_AC_OFFSET = 0x48
    IHT_KEK_IV_OFFSET = 0x4C  # 12 bytes
    IHT_OPTIONAL_DATA_LENGTH_OFFSET = 0x58
    IHT_AUTH_HEADER_OFFSET = 0x5C
    IHT_HASH_BLOCK_LENGTH_OFFSET = 0x60
    IHT_HASH_BLOCK_OFFSET = 0x64
    IHT_TOTAL_PPK_SIZE_OFFSET = 0x68
    IHT_ACTUAL_PPK_SIZE_OFFSET = 0x6C
    IHT_TOTAL_HASH_BLOCK_SIG_SIZE_OFFSET = 0x70
    IHT_ACTUAL_HASH_BLOCK_SIG_SIZE_OFFSET = 0x74
    IHT_RESERVED2_OFFSET = 0x78
    IHT_CHECKSUM_OFFSET = 0x7C
    
    # Image Header Table size
    IMAGE_HEADER_TABLE_SIZE = 128  # 0x80 bytes
    
    # PDI identification strings
    PDI_FULL = 0x49445046  # "FPDI" - Full PDI with boot header
    PDI_PARTIAL = 0x49445050  # "PPDI" - Partial/Sub-system PDI
    
    # IHT attribute bit shifts (per Versal Gen 2 documentation)
    IHT_ATTR_ID_CODE_CHECK_SHIFT = 16
    IHT_ATTR_PUF_HD_LOCATION_SHIFT = 14
    IHT_ATTR_DPACM_ENABLE_SHIFT = 12
    IHT_ATTR_SECONDARY_BOOT_DEVICE_SHIFT = 6
    IHT_ATTR_IMAGE_CREATOR_SHIFT = 1
    IHT_ATTR_SILICON_REV_CHECK_SHIFT = 0
    
    # IHT attribute bit masks
    IHT_ATTR_ID_CODE_CHECK_MASK = 0x3
    IHT_ATTR_PUF_HD_LOCATION_MASK = 0x3
    IHT_ATTR_DPACM_ENABLE_MASK = 0x3
    IHT_ATTR_SECONDARY_BOOT_DEVICE_MASK = 0x3F
    IHT_ATTR_IMAGE_CREATOR_MASK = 0x1F
    IHT_ATTR_SILICON_REV_CHECK_MASK = 0x1

    class SecondaryBootDevice(IntEnum):
        """Secondary boot device types (bits 11:6)."""
        SAME_BOOT_DEVICE = 0
        QSPI32 = 1
        QSPI24 = 2
        NAND = 3
        SD0 = 4
        SD1 = 5
        SD_LS = 6
        EMMC = 7
        USB = 8
        ETHERNET = 9
        PCIE = 10
        SATA = 11
        OSPI = 12
        SMAP = 13
        SBI = 14
        SD0RAW = 15
        SD1RAW = 16
        SD_LS_RAW = 17
        EMMC_RAW = 18
        MMC0 = 19
        MMC0RAW = 20
        IMAGESTORE = 21

    class ImageCreator(IntEnum):
        """Image creator types (bits 5:1)."""
        UNKNOWN = 0
        WDI = 1
        SDK = 2
        # Others reserved
    
    def __init__(self):
        """Initialize the Versal Gen 2 image header table."""
        super().__init__()
        self.version: Optional[int] = None  # 0x00
        self.total_image_count: Optional[int] = None  # 0x04
        self.image_header_offset: Optional[int] = None  # 0x08 (word offset)
        self.total_partition_count: Optional[int] = None  # 0x0C
        self.partition_header_offset: Optional[int] = None  # 0x10 (word offset)
        self.secondary_boot_device_address: Optional[int] = None  # 0x14
        self.id_code: Optional[int] = None  # 0x18 (Device ID code)
        self.attributes: Optional[int] = None  # 0x1C
        self.pdi_id: Optional[int] = None  # 0x20
        # Reserved 0x24
        self.identification_string: Optional[int] = None  # 0x28 ("FPDI" or "PPDI")
        self.headers_size: Optional[int] = None  # 0x2C (packed sizes)
        self.total_meta_header_length: Optional[int] = None  # 0x30 (words)
        self.meta_header_iv: Optional[bytes] = None  # 0x34-0x3F (12 bytes)
        self.encryption_status: Optional[int] = None  # 0x40
        self.extended_id_code: Optional[int] = None  # 0x44
        self.hash_block_ac_offset: Optional[int] = None  # 0x48 (word offset)
        self.kek_iv: Optional[bytes] = None  # 0x4C-0x57 (12 bytes)
        self.optional_data_length: Optional[int] = None  # 0x58 (words)
        self.auth_header: Optional[int] = None  # 0x5C
        self.hash_block_length: Optional[int] = None  # 0x60
        self.hash_block_offset: Optional[int] = None  # 0x64
        self.total_ppk_size: Optional[int] = None  # 0x68
        self.actual_ppk_size: Optional[int] = None  # 0x6C
        self.total_hash_block_signature_size: Optional[int] = None  # 0x70
        self.actual_hash_block_signature_size: Optional[int] = None  # 0x74
        # Reserved 0x78
        self.checksum: Optional[int] = None  # 0x7C
        # Backward compatibility aliases
        self.parent_id: Optional[int] = None  # Deprecated, use pdi_id
        self.meta_header_ac_offset: Optional[int] = None  # Alias for hash_block_ac_offset
        self.meta_header_black_iv: Optional[bytes] = None  # Alias for kek_iv

    def get_iht_size(self) -> int:
        """Get Image Header Table size in words from headers_size field (bits 7:0).
        
        Returns:
            IHT size in words.
        """
        if self.headers_size is None:
            return 0
        return self.headers_size & 0xFF

    def get_ih_size(self) -> int:
        """Get Image Header size in words from headers_size field (bits 15:8).
        
        Returns:
            Image Header size in words.
        """
        if self.headers_size is None:
            return 0
        return (self.headers_size >> 8) & 0xFF

    def get_ph_size(self) -> int:
        """Get Partition Header size in words from headers_size field (bits 23:16).
        
        Returns:
            Partition Header size in words.
        """
        if self.headers_size is None:
            return 0
        return (self.headers_size >> 16) & 0xFF

    def get_secondary_boot_device(self) -> 'Versal2VE2VMImageHeaderTable.SecondaryBootDevice':
        """Extract secondary boot device from attributes (bits 11:6).
        
        Returns:
            SecondaryBootDevice enum value.
        """
        if self.attributes is None:
            return Versal2VE2VMImageHeaderTable.SecondaryBootDevice.SAME_BOOT_DEVICE
        device_val = (self.attributes >> 6) & 0x3F
        try:
            return Versal2VE2VMImageHeaderTable.SecondaryBootDevice(device_val)
        except ValueError:
            return Versal2VE2VMImageHeaderTable.SecondaryBootDevice.SAME_BOOT_DEVICE

    def get_dpacm_enabled(self) -> bool:
        """Check if DPA counter measure is enabled (bits 13:12, value 0x11).
        
        Returns:
            True if DPACM is enabled, False otherwise.
        """
        if self.attributes is None:
            return False
        return ((self.attributes >> 12) & 0x3) == 0x3

    def get_puf_hd_in_bh(self) -> bool:
        """Check if PUF helper data is in boot header (bits 15:14, value 0x11).
        
        Returns:
            True if PUF HD is in boot header, False if in eFuse.
        """
        if self.attributes is None:
            return False
        return ((self.attributes >> 14) & 0x3) == 0x3

    def get_id_code_check_enabled(self) -> bool:
        """Check if ID code verification is enabled (bits 17:16, default 0x00).
        
        Returns:
            True if ID code verification is enabled (default), False if disabled (0x11).
        """
        if self.attributes is None:
            return True  # Default is enabled
        return ((self.attributes >> 16) & 0x3) != 0x3

    def get_silicon_rev_check_enabled(self) -> bool:
        """Check if silicon revision check is enabled (bit 0, default 0).
        
        Returns:
            True if silicon revision check is enabled (default), False if disabled.
        """
        if self.attributes is None:
            return True  # Default is enabled
        return (self.attributes & 0x1) == 0

    def get_image_creator(self) -> 'Versal2VE2VMImageHeaderTable.ImageCreator':
        """Extract image creator from attributes (bits 5:1).
        
        Returns:
            ImageCreator enum value.
        """
        if self.attributes is None:
            return Versal2VE2VMImageHeaderTable.ImageCreator.UNKNOWN
        creator_val = (self.attributes >> 1) & 0x1F
        try:
            return Versal2VE2VMImageHeaderTable.ImageCreator(creator_val)
        except ValueError:
            return Versal2VE2VMImageHeaderTable.ImageCreator.UNKNOWN

    def is_version_valid(self) -> bool:
        """Check if the version is valid.
        
        Returns:
            True if version is a known valid version, False otherwise.
        """
        return self.version in (self.VERSION_1_0, self.VERSION_2_0, self.VERSION_3_0, self.VERSION_4_0)

    def is_full_pdi(self) -> bool:
        """Check if this is a full PDI (with boot header).
        
        Returns:
            True if identification string is "FPDI", False otherwise.
        """
        return self.identification_string == self.PDI_FULL

    def is_partial_pdi(self) -> bool:
        """Check if this is a partial PDI (sub-system PDI).
        
        Returns:
            True if identification string is "PPDI", False otherwise.
        """
        return self.identification_string == self.PDI_PARTIAL


class Versal2VE2VMImageHeader(ImageHeader):
    """Versal Gen 2 (VE/VM)-specific image header."""
    
    # Image Header offsets (per Versal Gen 2 documentation)
    IH_FIRST_PARTITION_HEADER_OFFSET = 0x00
    IH_PARTITION_COUNT_OFFSET = 0x04
    IH_META_HEADER_REVOKE_ID_OFFSET = 0x08
    IH_ATTRIBUTES_OFFSET = 0x0C
    IH_IMAGE_NAME_OFFSET = 0x10  # 16 bytes
    IH_IMAGE_NODE_ID_OFFSET = 0x20
    IH_UNIQUE_ID_OFFSET = 0x24
    IH_PARENT_UNIQUE_ID_OFFSET = 0x28
    IH_FUNCTION_ID_OFFSET = 0x2C
    IH_DDR_LOW_ADDR_OFFSET = 0x30
    IH_DDR_HIGH_ADDR_OFFSET = 0x34
    IH_PCR_MEASUREMENT_OFFSET = 0x38  # PCR Number (2 bytes) + Measurement Index (2 bytes)
    IH_CHECKSUM_OFFSET = 0x3C
    
    # Image Header size
    IMAGE_HEADER_SIZE = 64  # 0x40 bytes
    
    # Image attribute bit shifts
    IH_ATTR_DELAY_HANDOFF_SHIFT = 8
    IH_ATTR_DELAY_LOAD_SHIFT = 7
    IH_ATTR_COPY_TO_MEMORY_SHIFT = 6
    IH_ATTR_IMAGE_OWNER_SHIFT = 3
    
    # Image attribute bit masks
    IH_ATTR_DELAY_HANDOFF_MASK = 0x1
    IH_ATTR_DELAY_LOAD_MASK = 0x1
    IH_ATTR_COPY_TO_MEMORY_MASK = 0x1
    IH_ATTR_IMAGE_OWNER_MASK = 0x7

    class ImageOwner(IntEnum):
        """Image owner types (bits 5:3)."""
        PLM = 0
        NON_PLM = 1
        RESERVED_2 = 2
        RESERVED_3 = 3
        RESERVED_4 = 4
        RESERVED_5 = 5
        RESERVED_6 = 6
        RESERVED_7 = 7
    
    def __init__(self):
        """Initialize the Versal Gen 2 image header."""
        super().__init__()
        self.first_partition_header_offset: Optional[int] = None  # 0x00 (word offset)
        self.partition_count: Optional[int] = None  # 0x04
        self.meta_header_revoke_id: Optional[int] = None  # 0x08 (same revoke ID for all IHs)
        self.attributes: Optional[int] = None  # 0x0C
        self.image_name: Optional[str] = None  # 0x10-0x1F (16 bytes, null-terminated)
        self.image_node_id: Optional[int] = None  # 0x20
        self.unique_id: Optional[int] = None  # 0x24
        self.parent_unique_id: Optional[int] = None  # 0x28
        self.function_id: Optional[int] = None  # 0x2C
        self.ddr_low_address: Optional[int] = None  # 0x30
        self.ddr_high_address: Optional[int] = None  # 0x34
        self.pcr_number: Optional[int] = None  # 0x38-0x39 (valid values 2-7)
        self.measurement_index: Optional[int] = None  # 0x3A-0x3B
        self.checksum: Optional[int] = None  # 0x3C
        # Backward compatibility alias
        self.revoke_id: Optional[int] = None  # Alias for meta_header_revoke_id

    def get_image_owner(self) -> 'Versal2VE2VMImageHeader.ImageOwner':
        """Extract image owner from attributes (bits 5:3).
        
        Returns:
            ImageOwner enum value.
        """
        if self.attributes is None:
            return Versal2VE2VMImageHeader.ImageOwner.PLM
        return Versal2VE2VMImageHeader.ImageOwner((self.attributes >> 3) & 0x7)

    def get_copy_to_memory(self) -> bool:
        """Check if image should be copied to memory (bit 6).
        
        Returns:
            True if copy to memory is enabled, False otherwise.
        """
        if self.attributes is None:
            return False
        return bool((self.attributes >> 6) & 0x1)

    def get_delay_load(self) -> bool:
        """Check if image load should be delayed (bit 7).
        
        Returns:
            True if delay load is enabled, False otherwise.
        """
        if self.attributes is None:
            return False
        return bool((self.attributes >> 7) & 0x1)

    def get_delay_handoff(self) -> bool:
        """Check if image handoff should be delayed (bit 8).
        
        Returns:
            True if delay handoff is enabled, False otherwise.
        """
        if self.attributes is None:
            return False
        return bool((self.attributes >> 8) & 0x1)

    def get_ddr_address(self) -> int:
        """Get the full 64-bit DDR address for memory copy.
        
        Returns:
            The 64-bit DDR address.
        """
        lo = self.ddr_low_address or 0
        hi = self.ddr_high_address or 0
        return (hi << 32) | lo


class Versal2VE2VMPartitionHeader(PartitionHeader):
    """Versal Gen 2 (VE/VM)-specific partition header."""
    
    # Maximum partitions
    MAX_NUM_PARTITIONS = 32
    
    # Partition header offsets
    PH_ENCRYPTED_LENGTH_OFFSET = 0x00
    PH_UNENCRYPTED_LENGTH_OFFSET = 0x04
    PH_TOTAL_LENGTH_OFFSET = 0x08
    PH_NEXT_PARTITION_OFFSET = 0x0C
    PH_EXEC_ADDR_LO_OFFSET = 0x10
    PH_EXEC_ADDR_HI_OFFSET = 0x14
    PH_LOAD_ADDR_LO_OFFSET = 0x18
    PH_LOAD_ADDR_HI_OFFSET = 0x1C
    PH_DATA_OFFSET = 0x20
    PH_ATTRIBUTES_OFFSET = 0x24
    PH_SECTION_COUNT_OFFSET = 0x28
    PH_CHECKSUM_WORD_OFFSET = 0x2C
    PH_PARTITION_ID_OFFSET = 0x30
    PH_HASH_BLOCK_AC_OFFSET = 0x34
    PH_IV_OFFSET = 0x38  # 12 bytes
    PH_ENCRYPTION_KEY_SELECT_OFFSET = 0x44
    PH_KEK_IV_OFFSET = 0x48  # 12 bytes (IV for KEK decryption)
    PH_PARTITION_REVOKE_ID_OFFSET = 0x54
    PH_MEASURED_BOOT_ADDR_OFFSET = 0x58
    PH_AUTH_HEADER_OFFSET = 0x5C
    PH_HASH_BLOCK_LENGTH_OFFSET = 0x60
    PH_HASH_BLOCK_OFFSET = 0x64
    PH_TOTAL_PPK_SIZE_OFFSET = 0x68
    PH_ACTUAL_PPK_SIZE_OFFSET = 0x6C
    PH_TOTAL_HASH_BLOCK_SIG_SIZE_OFFSET = 0x70
    PH_ACTUAL_HASH_BLOCK_SIG_SIZE_OFFSET = 0x74
    PH_RESERVED_OFFSET = 0x78
    PH_HEADER_CHECKSUM_OFFSET = 0x7C
    
    # Partition header size
    PARTITION_HEADER_SIZE = 128  # 0x80 bytes
    
    # Encryption key source values
    KEY_SOURCE_UNENCRYPTED = 0x00000000
    KEY_SOURCE_EFUSE_RED = 0xA5C3C5A3
    KEY_SOURCE_EFUSE_BLACK = 0xA5C3C5A5
    KEY_SOURCE_EFUSE_OBFUSCATED = 0xA5C3C5A7
    KEY_SOURCE_BBRAM_RED = 0x3A5C3C5A
    KEY_SOURCE_BBRAM_BLACK = 0x3A5C3C59
    KEY_SOURCE_BBRAM_OBFUSCATED = 0x3A5C3C57
    KEY_SOURCE_BH_BLACK = 0xA35C7C53
    KEY_SOURCE_BH_OBFUSCATED = 0xA35C7CA5
    KEY_SOURCE_USER_KEY_0 = 0xC5C3A5A3
    KEY_SOURCE_USER_KEY_1 = 0xC3A5C5B3
    KEY_SOURCE_USER_KEY_2 = 0xC5C3A5C3
    KEY_SOURCE_USER_KEY_3 = 0xC3A5C5D3
    KEY_SOURCE_USER_KEY_4 = 0xC5C3A5E3
    KEY_SOURCE_USER_KEY_5 = 0xC3A5C5F3
    KEY_SOURCE_USER_KEY_6 = 0xC5C3A503
    KEY_SOURCE_USER_KEY_7 = 0xC3A5C513
    
    # Partition attribute bit shifts (per Versal Gen 2 documentation)
    PH_TRUSTZONE_SHIFT = 0
    PH_EXCEPTION_LEVEL_SHIFT = 1
    PH_A78_EXEC_STATE_SHIFT = 3
    PH_CLUSTER_LOCKSTEP_SHIFT = 4
    PH_DEST_CPU_SHIFT = 8
    PH_CHECKSUM_SHIFT = 12
    PH_OWNER_SHIFT = 16
    PH_ENDIANNESS_SHIFT = 18
    PH_HIVEC_SHIFT = 23
    PH_PARTITION_TYPE_SHIFT = 24
    PH_DPACM_ENABLE_SHIFT = 27
    PH_DEST_CLUSTER_SHIFT = 29
    
    # Partition attribute bit masks
    PH_TRUSTZONE_MASK = 0x1
    PH_EXCEPTION_LEVEL_MASK = 0x3
    PH_A78_EXEC_STATE_MASK = 0x1
    PH_CLUSTER_LOCKSTEP_MASK = 0x3
    PH_DEST_CPU_MASK = 0xF
    PH_CHECKSUM_MASK = 0x3
    PH_OWNER_MASK = 0x3
    PH_ENDIANNESS_MASK = 0x1
    PH_HIVEC_MASK = 0x1
    PH_PARTITION_TYPE_MASK = 0x7
    PH_DPACM_ENABLE_MASK = 0x3
    PH_DEST_CLUSTER_MASK = 0x7

    class PartitionOwner(IntEnum):
        """Partition owner types (bits 17:16)."""
        PLM = 0
        NON_PLM = 1
        RESERVED_2 = 2
        RESERVED_3 = 3

    class ChecksumType(IntEnum):
        """Checksum types (bits 13:12)."""
        NONE = 0
        RESERVED_1 = 1
        RESERVED_2 = 2
        SHA3 = 3

    class DestinationCpu(IntEnum):
        """Destination CPU types (bits 11:8) per Versal Gen 2 documentation."""
        NONE = 0
        A78_0 = 1
        A78_1 = 2
        A78_2 = 3
        A78_3 = 4
        R52_0 = 5
        R52_1 = 6
        RESERVED_7 = 7
        ASU = 8
        AIE = 9
        RESERVED_10 = 10
        RESERVED_11 = 11
        RESERVED_12 = 12
        RESERVED_13 = 13
        RESERVED_14 = 14
        RESERVED_15 = 15

    class DestinationCluster(IntEnum):
        """Destination cluster types (bits 31:29) per Versal Gen 2 documentation."""
        CLUSTER_0 = 0  # A78 Cluster 0 or R52 Cluster 0
        CLUSTER_1 = 1  # A78 Cluster 1 or R52 Cluster 1
        CLUSTER_2 = 2  # A78 Cluster 2 or R52 Cluster 2
        CLUSTER_3 = 3  # A78 Cluster 3 or R52 Cluster 3
        R52_CLUSTER_4 = 4  # R52 Cluster 4

    class PartitionType(IntEnum):
        """Partition type (bits 26:24)."""
        RESERVED_0 = 0
        ELF = 1
        CDO = 2
        CFRAME = 3
        RAW = 4
        RAW_ELF = 5
        CFI_GSR_UNMASK = 6
        CFI_GSR_MASK = 7

    class ExceptionLevel(IntEnum):
        """Exception level types for A78 core (bits 2:1)."""
        EL0 = 0
        EL1 = 1
        EL2 = 2
        EL3 = 3  # Default

    class A78ExecState(IntEnum):
        """A78 CPU execution state types (bit 3)."""
        AARCH64 = 0  # Default
        AARCH32 = 1

    class ClusterLockstep(IntEnum):
        """Cluster Lockstep types (bits 5:4)."""
        DISABLED = 0
        RESERVED_1 = 1
        RESERVED_2 = 2
        ENABLED = 3

    class VectorLocation(IntEnum):
        """VInitHi setting for RPU/APU(32-bit) processor (bit 23)."""
        LOVEC = 0
        HIVEC = 1

    class Endianness(IntEnum):
        """Endianness types (bit 18)."""
        LITTLE_ENDIAN = 0  # Default
        BIG_ENDIAN = 1

    class Trustzone(IntEnum):
        """Trustzone types (bit 0)."""
        NON_SECURE = 0
        SECURE = 1  # Default - PLM configures core as TZ secure

    def __init__(self):
        """Initialize the Versal Gen 2 partition header."""
        super().__init__()
        self.encrypted_partition_length: Optional[int] = None  # 0x00 (word length)
        self.unencrypted_partition_length: Optional[int] = None  # 0x04 (word length)
        self.total_partition_word_length: Optional[int] = None  # 0x08 (word length)
        self.next_partition_header_offset: Optional[int] = None  # 0x0C (word offset)
        self.destination_execution_address_lo: Optional[int] = None  # 0x10 (low 32-bits)
        self.destination_execution_address_hi: Optional[int] = None  # 0x14 (high 32-bits)
        self.destination_load_address_lo: Optional[int] = None  # 0x18 (low 32-bits)
        self.destination_load_address_hi: Optional[int] = None  # 0x1C (high 32-bits)
        self.data_word_offset: Optional[int] = None  # 0x20 (word offset)
        self.attribute_bits: Optional[int] = None  # 0x24
        self.section_count: Optional[int] = None  # 0x28
        self.checksum_word_offset: Optional[int] = None  # 0x2C (word offset)
        self.partition_id: Optional[int] = None  # 0x30
        self.hash_block_ac_word_offset: Optional[int] = None  # 0x34 (word offset)
        self.iv: Optional[bytes] = None  # 0x38-0x43 (12 bytes) for PH secure header
        self.encryption_key_select: Optional[int] = None  # 0x44
        self.kek_iv: Optional[bytes] = None  # 0x48-0x53 (12 bytes) for KEK decryption
        self.partition_revoke_id: Optional[int] = None  # 0x54
        self.measured_boot_address: Optional[int] = None  # 0x58
        self.auth_header: Optional[int] = None  # 0x5C
        self.hash_block_length: Optional[int] = None  # 0x60
        self.hash_block_offset: Optional[int] = None  # 0x64
        self.total_ppk_size: Optional[int] = None  # 0x68
        self.actual_ppk_size: Optional[int] = None  # 0x6C
        self.total_hash_block_signature_size: Optional[int] = None  # 0x70
        self.actual_hash_block_signature_size: Optional[int] = None  # 0x74
        # Reserved 0x78
        self.header_checksum: Optional[int] = None  # 0x7C
        # Backward compatibility aliases
        self.authentication_certificate_word_offset: Optional[int] = None  # Alias for hash_block_ac_word_offset
        self.black_iv: Optional[bytes] = None  # Alias for kek_iv
        self.revoke_id: Optional[int] = None  # Alias for partition_revoke_id

    def get_destination_execution_address(self) -> int:
        """Get the full 64-bit destination execution address.
        
        Returns:
            The 64-bit execution address.
        """
        lo = self.destination_execution_address_lo or 0
        hi = self.destination_execution_address_hi or 0
        return (hi << 32) | lo

    def get_destination_load_address(self) -> int:
        """Get the full 64-bit destination load address.
        
        Returns:
            The 64-bit load address.
        """
        lo = self.destination_load_address_lo or 0
        hi = self.destination_load_address_hi or 0
        return (hi << 32) | lo

    def get_trustzone(self) -> 'Versal2VE2VMPartitionHeader.Trustzone':
        """Extract trustzone from attribute bits (bit 0).
        
        Returns:
            Trustzone enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.Trustzone.NON_SECURE
        return Versal2VE2VMPartitionHeader.Trustzone(self.attribute_bits & 0x1)

    def get_exception_level(self) -> 'Versal2VE2VMPartitionHeader.ExceptionLevel':
        """Extract exception level from attribute bits (bits 2:1).
        
        Returns:
            ExceptionLevel enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.ExceptionLevel.EL3
        return Versal2VE2VMPartitionHeader.ExceptionLevel((self.attribute_bits >> 1) & 0x3)

    def get_a78_exec_state(self) -> 'Versal2VE2VMPartitionHeader.A78ExecState':
        """Extract A78 execution state from attribute bits (bit 3).
        
        Returns:
            A78ExecState enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.A78ExecState.AARCH64
        return Versal2VE2VMPartitionHeader.A78ExecState((self.attribute_bits >> 3) & 0x1)

    def get_cluster_lockstep(self) -> 'Versal2VE2VMPartitionHeader.ClusterLockstep':
        """Extract cluster lockstep from attribute bits (bits 5:4).
        
        Returns:
            ClusterLockstep enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.ClusterLockstep.DISABLED
        return Versal2VE2VMPartitionHeader.ClusterLockstep((self.attribute_bits >> 4) & 0x3)

    def get_destination_cpu(self) -> 'Versal2VE2VMPartitionHeader.DestinationCpu':
        """Extract destination CPU from attribute bits (bits 11:8).
        
        Returns:
            DestinationCpu enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.DestinationCpu.NONE
        return Versal2VE2VMPartitionHeader.DestinationCpu((self.attribute_bits >> 8) & 0xF)

    def get_destination_cluster(self) -> 'Versal2VE2VMPartitionHeader.DestinationCluster':
        """Extract destination cluster from attribute bits (bits 31:29).
        
        Returns:
            DestinationCluster enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.DestinationCluster.CLUSTER_0
        return Versal2VE2VMPartitionHeader.DestinationCluster((self.attribute_bits >> 29) & 0x7)

    def get_checksum_type(self) -> 'Versal2VE2VMPartitionHeader.ChecksumType':
        """Extract checksum type from attribute bits (bits 13:12).
        
        Returns:
            ChecksumType enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.ChecksumType.NONE
        return Versal2VE2VMPartitionHeader.ChecksumType((self.attribute_bits >> 12) & 0x3)

    def get_partition_owner(self) -> 'Versal2VE2VMPartitionHeader.PartitionOwner':
        """Extract partition owner from attribute bits (bits 17:16).
        
        Returns:
            PartitionOwner enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.PartitionOwner.PLM
        return Versal2VE2VMPartitionHeader.PartitionOwner((self.attribute_bits >> 16) & 0x3)

    def get_endianness(self) -> 'Versal2VE2VMPartitionHeader.Endianness':
        """Extract endianness from attribute bits (bit 18).
        
        Returns:
            Endianness enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.Endianness.LITTLE_ENDIAN
        return Versal2VE2VMPartitionHeader.Endianness((self.attribute_bits >> 18) & 0x1)

    def get_vector_location(self) -> 'Versal2VE2VMPartitionHeader.VectorLocation':
        """Extract vector location from attribute bits (bit 23).
        
        Returns:
            VectorLocation enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.VectorLocation.LOVEC
        return Versal2VE2VMPartitionHeader.VectorLocation((self.attribute_bits >> 23) & 0x1)

    def get_partition_type(self) -> 'Versal2VE2VMPartitionHeader.PartitionType':
        """Extract partition type from attribute bits (bits 26:24).
        
        Returns:
            PartitionType enum value.
        """
        if self.attribute_bits is None:
            return Versal2VE2VMPartitionHeader.PartitionType.RESERVED_0
        return Versal2VE2VMPartitionHeader.PartitionType((self.attribute_bits >> 24) & 0x7)

    def get_dpacm_enabled(self) -> bool:
        """Check if DPA counter measure is enabled (bits 28:27).
        
        Returns:
            True if DPACM is enabled (value is non-zero), False otherwise.
        """
        if self.attribute_bits is None:
            return False
        return bool((self.attribute_bits >> 27) & 0x3)

    def get_encryption_present(self) -> bool:
        """Check if partition is encrypted.
        
        Returns:
            True if partition is encrypted, False otherwise.
        """
        if self.encryption_key_select is None:
            return False
        return self.encryption_key_select != self.KEY_SOURCE_UNENCRYPTED

    def get_attribute_summary(self) -> str:
        """Get a human-readable summary of the attribute bits.
        
        Returns:
            String describing the partition attributes.
        """
        return (f"Type={self.get_partition_type().name}, "
                f"Owner={self.get_partition_owner().name}, "
                f"CPU={self.get_destination_cpu().name}, "
                f"Cluster={self.get_destination_cluster().name}, "
                f"TZ={self.get_trustzone().name}, "
                f"EL={self.get_exception_level().name}, "
                f"ExecState={self.get_a78_exec_state().name}, "
                f"Lockstep={self.get_cluster_lockstep().name}")

    def get_encrypted_partition_length_bytes(self) -> int:
        """Get the encrypted partition length in bytes.
        
        The length is stored in words (4 bytes each) in the header.
        
        Returns:
            The encrypted partition length in bytes.
        """
        if self.encrypted_partition_length is None:
            return 0
        return self.encrypted_partition_length * 4

    def get_unencrypted_partition_length_bytes(self) -> int:
        """Get the unencrypted partition length in bytes.
        
        The length is stored in words (4 bytes each) in the header.
        
        Returns:
            The unencrypted partition length in bytes.
        """
        if self.unencrypted_partition_length is None:
            return 0
        return self.unencrypted_partition_length * 4

    def get_total_partition_length_bytes(self) -> int:
        """Get the total partition length in bytes.
        
        The length is stored in words (4 bytes each) in the header.
        
        Returns:
            The total partition length in bytes.
        """
        if self.total_partition_word_length is None:
            return 0
        return self.total_partition_word_length * 4

    def get_data_offset_bytes(self) -> int:
        """Get the data offset in bytes.
        
        The offset is stored in words (4 bytes each) in the header.
        
        Returns:
            The data offset in bytes.
        """
        if self.data_word_offset is None:
            return 0
        return self.data_word_offset * 4

    def compute_checksum(self) -> int:
        """Compute the checksum over the partition header fields.
        
        The checksum is the sum of all 32-bit words in the header,
        excluding the checksum field itself (last 4 bytes).
        
        Returns:
            The computed checksum as a 32-bit unsigned integer.
        """
        # Pack the header fields into words (excluding checksum)
        words = [
            self.encrypted_partition_length or 0,          # 0x00
            self.unencrypted_partition_length or 0,        # 0x04
            self.total_partition_word_length or 0,         # 0x08
            self.next_partition_header_offset or 0,        # 0x0C
            self.destination_execution_address_lo or 0,    # 0x10
            self.destination_execution_address_hi or 0,    # 0x14
            self.destination_load_address_lo or 0,         # 0x18
            self.destination_load_address_hi or 0,         # 0x1C
            self.data_word_offset or 0,                    # 0x20
            self.attribute_bits or 0,                      # 0x24
            self.section_count or 0,                       # 0x28
            self.checksum_word_offset or 0,                # 0x2C
            self.partition_id or 0,                        # 0x30
            self.hash_block_ac_word_offset or 0,           # 0x34
        ]
        
        # Add IV (12 bytes = 3 words)
        if self.iv:
            for i in range(0, 12, 4):
                words.append(int.from_bytes(self.iv[i:i+4], 'little'))
        else:
            words.extend([0] * 3)
        
        # Add encryption key select
        words.append(self.encryption_key_select or 0)  # 0x44
        
        # Add KEK IV (12 bytes = 3 words)
        if self.kek_iv:
            for i in range(0, 12, 4):
                words.append(int.from_bytes(self.kek_iv[i:i+4], 'little'))
        else:
            words.extend([0] * 3)
        
        # Add partition revoke ID
        words.append(self.partition_revoke_id or 0)  # 0x54
        
        # Add measured boot address
        words.append(self.measured_boot_address or 0)  # 0x58
        
        # Add auth header
        words.append(self.auth_header or 0)  # 0x5C
        
        # Add hash block length
        words.append(self.hash_block_length or 0)  # 0x60
        
        # Add hash block offset
        words.append(self.hash_block_offset or 0)  # 0x64
        
        # Add total PPK size
        words.append(self.total_ppk_size or 0)  # 0x68
        
        # Add actual PPK size
        words.append(self.actual_ppk_size or 0)  # 0x6C
        
        # Add total hash block signature size
        words.append(self.total_hash_block_signature_size or 0)  # 0x70
        
        # Add actual hash block signature size
        words.append(self.actual_hash_block_signature_size or 0)  # 0x74
        
        # Add reserved (0x78 = 1 word)
        words.append(0)
        
        # Sum all words and mask to 32 bits
        checksum = sum(words) & 0xFFFFFFFF
        
        # Invert (one's complement)
        checksum = (~checksum) & 0xFFFFFFFF
        
        return checksum

    def verify_checksum(self) -> bool:
        """Verify the partition header checksum.
        
        Returns:
            True if the checksum is valid, False otherwise.
        """
        if self.header_checksum is None:
            return False
        return self.compute_checksum() == self.header_checksum


class Versal2VE2VMBootImageLoader(VersalBootImageLoader):
    """Versal Gen 2 (VE/VM)-specific boot image loader implementation."""

    def __init__(self, image_path=None, image_path_offset=0):
        """Initialize the Versal Gen 2 boot image loader.
        
        Args:
            image_path: Path to the boot image file (PDI).
            image_path_offset: Offset within the image file to start reading from.
        """
        super().__init__(image_path, image_path_offset)
        self.boot_header = Versal2VE2VMBootHeader()
        self.image_header_table = Versal2VE2VMImageHeaderTable()

    def parse(self):
        """Parse the Versal Gen 2 boot image (PDI).
        
        Raises:
            RuntimeError: If no image data is loaded.
        """
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        # Validate the boot header before reading too much

        # Image Identification at 0x14 (already read by verify_magic)
        if not self.verify_magic():
            raise ValueError(f"Invalid boot image: magic bytes do not match {self.boot_header.header_signature.hex()}")
        
        if not self.detect_width():
            raise ValueError(f"Width detection indicates an unsupported configuration: {self.boot_header.width_detection_word.hex()}") 
        
        # Read SelectMAP bus width at 0x00-0x0F (16 bytes)
        self._safe_seek(self.image_path_offset + 0x00, 16)
        self.boot_header.smap_bus_width = self.image_fd.read(16)
        
        # QSPI bus width at 0x10 (4 bytes) - already read by detect_width
        self._safe_seek(self.image_path_offset + 0x10, 4)
        self.boot_header.qspi_bus_width = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Image Identification at 0x14 (4 bytes) - already read by verify_magic
        self._safe_seek(self.image_path_offset + 0x14, 4)
        self.boot_header.image_identification = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Encryption Key Source at 0x18 (4 bytes)
        self._safe_seek(self.image_path_offset + 0x18, 4)
        self.boot_header.encryption_key_source = struct.unpack('<I', self.image_fd.read(4))[0]
        if self.boot_header.encryption_key_source != Versal2VE2VMBootHeader.KEY_SOURCE_UNENCRYPTED:
            raise ValueError("Encryption is not supported")
        
        # PLM Source Offset at 0x1C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x1C, 4)
        self.boot_header.plm_source_offset = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # PMC CDO Load Address at 0x20 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x20, 4)
        self.boot_header.pmc_cdo_load_address = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # PMC CDO Length at 0x24 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x24, 4)
        self.boot_header.pmc_cdo_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Total PMC CDO Length at 0x28 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x28, 4)
        self.boot_header.total_pmc_cdo_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # PLM Length at 0x2C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x2C, 4)
        self.boot_header.plm_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Total PLM Length at 0x30 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x30, 4)
        self.boot_header.total_plm_length = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Boot Header Attributes at 0x34 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x34, 4)
        self.boot_header.boot_header_attributes = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Grey/Black Key at 0x38-0x57 (32 bytes)
        self._safe_seek(self.image_path_offset + 0x38, 32)
        self.boot_header.grey_black_key = self.image_fd.read(32)
        
        # Grey/Black IV at 0x58-0x63 (12 bytes)
        self._safe_seek(self.image_path_offset + 0x58, 12)
        self.boot_header.grey_black_iv = self.image_fd.read(12)
        
        # Secure Header IV at 0x64-0x6F (12 bytes)
        self._safe_seek(self.image_path_offset + 0x64, 12)
        self.boot_header.secure_header_iv = self.image_fd.read(12)
        
        # PUF Shutter Value at 0x70 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x70, 4)
        self.boot_header.puf_shutter_value = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Ring Oscillator Config Value at 0x74 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x74, 4)
        self.boot_header.ring_oscillator_config = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Partition Revocation ID at 0x78 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x78, 4)
        self.boot_header.partition_revocation_id = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # User Data at 0x7C-0x27F (516 bytes)
        self._safe_seek(self.image_path_offset + 0x7C, 516)
        self.boot_header.user_data = self.image_fd.read(516)
        
        # Authentication Header-1 at 0x280 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x280, 4)
        self.boot_header.auth_header_1 = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Hash Block Size-1 at 0x284 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x284, 4)
        self.boot_header.hash_block_size_1 = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Total PPK Size-1 at 0x288 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x288, 4)
        self.boot_header.total_ppk_size_1 = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Actual PPK Size-1 at 0x28C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x28C, 4)
        self.boot_header.actual_ppk_size_1 = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Total PDI Signature Size-1 at 0x290 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x290, 4)
        self.boot_header.total_pdi_signature_size_1 = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Actual PDI Signature Size-1 at 0x294 (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x294, 4)
        self.boot_header.actual_pdi_signature_size_1 = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # ROM Reserved at 0x298-0x2CF (56 bytes)
        self._safe_seek(self.image_path_offset + 0x298, 56)
        self.boot_header.rom_reserved = self.image_fd.read(56)
        
        # PLM Reserved at 0x2D0-0x333 (100 bytes)
        self._safe_seek(self.image_path_offset + 0x2D0, 100)
        self.boot_header.plm_reserved = self.image_fd.read(100)
        
        # Register Init at 0x334-0xB33 (2048 bytes)
        self._safe_seek(self.image_path_offset + 0x334, 2048)
        self.boot_header.register_init = self.image_fd.read(2048)
        
        # PUF Helper Data at 0xB34-0x113B (1544 bytes)
        self._safe_seek(self.image_path_offset + 0xB34, 1544)
        self.boot_header.puf_helper_data = self.image_fd.read(1544)
        
        # Boot Header Checksum at 0x113C (4 bytes, little-endian)
        self._safe_seek(self.image_path_offset + 0x113C, 4)
        self.boot_header.boot_header_checksum = struct.unpack('<I', self.image_fd.read(4))[0]
        
        # Set backward compatibility aliases
        self.boot_header.pmc_data_load_address = self.boot_header.pmc_cdo_load_address
        self.boot_header.pmc_data_length = self.boot_header.pmc_cdo_length
        self.boot_header.total_pmc_data_length = self.boot_header.total_pmc_cdo_length
        self.boot_header.black_key = self.boot_header.grey_black_key
        self.boot_header.black_iv = self.boot_header.grey_black_iv
        
        # Verify boot header checksum
        if not self.boot_header.verify_checksum():
            computed = self.boot_header.compute_checksum()
            expected = self.boot_header.boot_header_checksum
            raise ValueError(
                f"Boot header checksum verification failed: computed=0x{computed:08X}, expected=0x{expected:08X}"
            )

        # Load Image Header Table from meta_header_offset
        # For Gen 2, meta_header_offset is derived from PLM source offset + total PLM length + total PMC CDO length
        # Or we can calculate it from the plm_source_offset
        meta_header_offset = self.boot_header.plm_source_offset + self.boot_header.total_plm_length + self.boot_header.total_pmc_cdo_length
        self.boot_header.meta_header_offset = meta_header_offset
        
        if meta_header_offset is not None and meta_header_offset != 0:
            iht_offset = meta_header_offset
            
            # Seek to Image Header Table offset
            self._safe_seek(self.image_path_offset + iht_offset, Versal2VE2VMImageHeaderTable.IMAGE_HEADER_TABLE_SIZE)
            
            # Version at 0x00 (4 bytes)
            self.image_header_table.version = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Validate version
            if not self.image_header_table.is_version_valid():
                raise ValueError(f"Invalid Image Header Table version: 0x{self.image_header_table.version:08X}")
            
            # Total Image Count at 0x04 (4 bytes, little-endian)
            self.image_header_table.total_image_count = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Image Header Offset at 0x08 (4 bytes, little-endian, word offset)
            self.image_header_table.image_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Total Partition Count at 0x0C (4 bytes, little-endian)
            self.image_header_table.total_partition_count = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Partition Header Offset at 0x10 (4 bytes, little-endian, word offset)
            self.image_header_table.partition_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Secondary Boot Device Address at 0x14 (4 bytes, little-endian)
            self.image_header_table.secondary_boot_device_address = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # ID Code at 0x18 (4 bytes, little-endian)
            self.image_header_table.id_code = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Attributes at 0x1C (4 bytes, little-endian)
            self.image_header_table.attributes = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # PDI ID at 0x20 (4 bytes, little-endian)
            self.image_header_table.pdi_id = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Reserved at 0x24 (4 bytes) - parent_id in bootgen
            _ = self.image_fd.read(4)
            
            # Identification String at 0x28 (4 bytes, little-endian)
            self.image_header_table.identification_string = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Headers Size at 0x2C (4 bytes, little-endian)
            self.image_header_table.headers_size = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Total Meta Header Length at 0x30 (4 bytes, little-endian)
            self.image_header_table.total_meta_header_length = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Meta Header IV at 0x34-0x3F (12 bytes)
            self.image_header_table.meta_header_iv = self.image_fd.read(12)
            
            # Encryption Status at 0x40 (4 bytes, little-endian)
            self.image_header_table.encryption_status = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Extended ID Code at 0x44 (4 bytes, little-endian)
            self.image_header_table.extended_id_code = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Meta Header AC Offset at 0x48 (4 bytes, little-endian, word offset)
            self.image_header_table.hash_block_ac_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # KEK IV at 0x4C-0x57 (12 bytes)
            self.image_header_table.kek_iv = self.image_fd.read(12)
            
            # Optional Data Length at 0x58 (4 bytes, little-endian, words)
            self.image_header_table.optional_data_length = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Auth Header at 0x5C (4 bytes, little-endian)
            self.image_header_table.auth_header = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Hash Block Length at 0x60 (4 bytes, little-endian, words)
            self.image_header_table.hash_block_length = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Hash Block Offset at 0x64 (4 bytes, little-endian, words)
            self.image_header_table.hash_block_offset = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Total PPK Size at 0x68 (4 bytes, little-endian)
            self.image_header_table.total_ppk_size = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Actual PPK Size at 0x6C (4 bytes, little-endian)
            self.image_header_table.actual_ppk_size = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Total Hash Block Signature Size at 0x70 (4 bytes, little-endian)
            self.image_header_table.total_hash_block_signature_size = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Actual Hash Block Signature Size at 0x74 (4 bytes, little-endian)
            self.image_header_table.actual_hash_block_signature_size = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Reserved at 0x78 (4 bytes) - skip
            _ = self.image_fd.read(4)
            
            # Checksum at 0x7C (4 bytes, little-endian)
            self.image_header_table.checksum = struct.unpack('<I', self.image_fd.read(4))[0]
            
            # Parse optional data if present
            if self.image_header_table.optional_data_length and self.image_header_table.optional_data_length > 0:
                self._parse_optional_data()

        # Load Image Headers and corresponding Partition Headers
        if self.image_header_table.image_header_offset is not None:
            current_offset = self.image_header_table.image_header_offset * 4  # Word to byte offset
            
            # Get the actual image header size from headers_size field
            ih_size_words = self.image_header_table.get_ih_size()
            ih_size_bytes = ih_size_words * 4 if ih_size_words > 0 else Versal2VE2VMImageHeader.IMAGE_HEADER_SIZE
            
            for _ in range(self.image_header_table.total_image_count or 0):
                if current_offset == 0:
                    break
                
                # Create a new image header instance
                image_header = Versal2VE2VMImageHeader()
                
                # Seek to current image header offset
                self._safe_seek(self.image_path_offset + current_offset, ih_size_bytes)
                
                # First Partition Header Offset at 0x00 (4 bytes, little-endian, word offset)
                image_header.first_partition_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Partition Count at 0x04 (4 bytes, little-endian)
                image_header.partition_count = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Revoke ID at 0x08 (4 bytes, little-endian)
                image_header.meta_header_revoke_id = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Attributes at 0x0C (4 bytes, little-endian)
                image_header.attributes = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Image Name at 0x10-0x1F (16 bytes, null-terminated)
                image_name_bytes = self.image_fd.read(16)
                # Find null terminator
                null_idx = image_name_bytes.find(b'\x00')
                if null_idx >= 0:
                    image_header.image_name = image_name_bytes[:null_idx].decode('ascii', errors='ignore')
                else:
                    image_header.image_name = image_name_bytes.decode('ascii', errors='ignore')
                
                # Image/Node ID at 0x20 (4 bytes, little-endian)
                image_header.image_node_id = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Unique ID at 0x24 (4 bytes, little-endian)
                image_header.unique_id = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Parent Unique ID at 0x28 (4 bytes, little-endian)
                image_header.parent_unique_id = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Function ID at 0x2C (4 bytes, little-endian)
                image_header.function_id = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # DDR Low Address at 0x30 (4 bytes, little-endian)
                image_header.ddr_low_address = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # DDR High Address at 0x34 (4 bytes, little-endian)
                image_header.ddr_high_address = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # PCR Number and Measurement Index at 0x38 (4 bytes, little-endian)
                pcr_measurement = struct.unpack('<I', self.image_fd.read(4))[0]
                image_header.pcr_number = (pcr_measurement >> 16) & 0xFFFF
                image_header.measurement_index = pcr_measurement & 0xFFFF
                
                # Checksum at 0x3C (4 bytes, little-endian)
                image_header.checksum = struct.unpack('<I', self.image_fd.read(4))[0]
                
                # Load all corresponding partition headers for this image
                partition_headers = []
                partition_header_offset = image_header.first_partition_header_offset * 4  # Convert word to byte offset
                partitions_to_read = image_header.partition_count or 1
                
                # Get the actual partition header size from headers_size field
                ph_size_words = self.image_header_table.get_ph_size()
                ph_size_bytes = ph_size_words * 4 if ph_size_words > 0 else Versal2VE2VMPartitionHeader.PARTITION_HEADER_SIZE
                
                for _ in range(partitions_to_read):
                    if partition_header_offset == 0:
                        break
                    
                    partition_header = Versal2VE2VMPartitionHeader()
                    
                    # Seek to partition header location
                    self._safe_seek(self.image_path_offset + partition_header_offset, ph_size_bytes)
                    
                    # Encrypted Partition Length at 0x00 (4 bytes, little-endian)
                    partition_header.encrypted_partition_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Unencrypted Partition Length at 0x04 (4 bytes, little-endian)
                    partition_header.unencrypted_partition_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Total Partition Word Length at 0x08 (4 bytes, little-endian)
                    partition_header.total_partition_word_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Next Partition Header Offset at 0x0C (4 bytes, little-endian, word offset)
                    partition_header.next_partition_header_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Execution Address LO at 0x10 (4 bytes, little-endian)
                    partition_header.destination_execution_address_lo = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Execution Address HI at 0x14 (4 bytes, little-endian)
                    partition_header.destination_execution_address_hi = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Load Address LO at 0x18 (4 bytes, little-endian)
                    partition_header.destination_load_address_lo = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Destination Load Address HI at 0x1C (4 bytes, little-endian)
                    partition_header.destination_load_address_hi = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Data Word Offset at 0x20 (4 bytes, little-endian)
                    partition_header.data_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Attribute Bits at 0x24 (4 bytes, little-endian)
                    partition_header.attribute_bits = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Section Count at 0x28 (4 bytes, little-endian)
                    partition_header.section_count = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Checksum Word Offset at 0x2C (4 bytes, little-endian)
                    partition_header.checksum_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Partition ID at 0x30 (4 bytes, little-endian)
                    partition_header.partition_id = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Hash Block AC Word Offset at 0x34 (4 bytes, little-endian)
                    partition_header.hash_block_ac_word_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # IV at 0x38-0x43 (12 bytes)
                    partition_header.iv = self.image_fd.read(12)
                    
                    # Encryption Key Select at 0x44 (4 bytes, little-endian)
                    partition_header.encryption_key_select = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # KEK IV at 0x48-0x53 (12 bytes)
                    partition_header.kek_iv = self.image_fd.read(12)
                    
                    # Partition Revoke ID at 0x54 (4 bytes, little-endian)
                    partition_header.partition_revoke_id = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Measured Boot Address at 0x58 (4 bytes, little-endian)
                    partition_header.measured_boot_address = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Auth Header at 0x5C (4 bytes, little-endian)
                    partition_header.auth_header = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Hash Block Length at 0x60 (4 bytes, little-endian, words)
                    partition_header.hash_block_length = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Hash Block Offset at 0x64 (4 bytes, little-endian, words)
                    partition_header.hash_block_offset = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Total PPK Size at 0x68 (4 bytes, little-endian)
                    partition_header.total_ppk_size = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Actual PPK Size at 0x6C (4 bytes, little-endian)
                    partition_header.actual_ppk_size = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Total Hash Block Signature Size at 0x70 (4 bytes, little-endian)
                    partition_header.total_hash_block_signature_size = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Actual Hash Block Signature Size at 0x74 (4 bytes, little-endian)
                    partition_header.actual_hash_block_signature_size = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Reserved at 0x78 (4 bytes) - skip
                    _ = self.image_fd.read(4)
                    
                    # Header Checksum at 0x7C (4 bytes, little-endian)
                    partition_header.header_checksum = struct.unpack('<I', self.image_fd.read(4))[0]
                    
                    # Verify partition header checksum
                    if not partition_header.verify_checksum():
                        raise ValueError(f"Partition header checksum verification failed for '{image_header.image_name}'")
                    
                    # Check for encrypted partition
                    if partition_header.get_encryption_present():
                        raise ValueError(f"Partition '{image_header.image_name}' is encrypted. Encrypted partitions are not supported.")
                    
                    partition_headers.append(partition_header)
                    
                    # Move to next partition header for this image
                    partition_header_offset = partition_header.next_partition_header_offset * 4  # Word to byte offset
                
                # Add tuple to list (image_header, list of partition_headers)
                self.image.append((image_header, partition_headers))
                
                # Move to next image header
                current_offset += ih_size_bytes

        return

    def debug(self):
        """Output debug information about the loaded Versal Gen 2 boot image (PDI)."""
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        print("Versal Gen 2 Boot Image (PDI) Header:")
        print(f"  SelectMAP Bus Width: {self.boot_header.smap_bus_width.hex() if self.boot_header.smap_bus_width else 'Not read'}")
        print(f"  QSPI Bus Width: {hex(self.boot_header.qspi_bus_width) if self.boot_header.qspi_bus_width is not None else 'Not read'}")
        print(f"  Image Identification: {hex(self.boot_header.image_identification) if self.boot_header.image_identification is not None else 'Not read'}")
        print(f"  Encryption Key Source: {hex(self.boot_header.encryption_key_source) if self.boot_header.encryption_key_source is not None else 'Not read'}")
        print(f"  PLM Source Offset: {hex(self.boot_header.plm_source_offset) if self.boot_header.plm_source_offset is not None else 'Not read'}")
        print(f"  PMC CDO Load Address: {hex(self.boot_header.pmc_cdo_load_address) if self.boot_header.pmc_cdo_load_address is not None else 'Not read'}")
        print(f"  PMC CDO Length: {self.boot_header.pmc_cdo_length if self.boot_header.pmc_cdo_length is not None else 'Not read'}")
        print(f"  Total PMC CDO Length: {self.boot_header.total_pmc_cdo_length if self.boot_header.total_pmc_cdo_length is not None else 'Not read'}")
        print(f"  PLM Length: {self.boot_header.plm_length if self.boot_header.plm_length is not None else 'Not read'}")
        print(f"  Total PLM Length: {self.boot_header.total_plm_length if self.boot_header.total_plm_length is not None else 'Not read'}")
        print(f"  Boot Header Attributes: {hex(self.boot_header.boot_header_attributes) if self.boot_header.boot_header_attributes is not None else 'Not read'}")
        if self.boot_header.boot_header_attributes is not None:
            print(f"    CDI Generation: {self.boot_header.get_cdi_generation().name}")
            print(f"    Signed Image: {self.boot_header.get_signed_image().name}")
            print(f"    PUF Mode: {self.boot_header.get_puf_mode().name}")
            print(f"    BH Authentication: {self.boot_header.get_bh_authentication().name}")
            print(f"    DPA Counter Measure: {self.boot_header.get_dpa_cm().name}")
            print(f"    BI Integrity: {self.boot_header.get_bi_integrity().name}")
            print(f"    PUF HD Location: {self.boot_header.get_puf_hd_location().name}")
        print(f"  Grey/Black Key: {self.boot_header.grey_black_key.hex() if self.boot_header.grey_black_key else 'Not read'}")
        print(f"  Grey/Black IV: {self.boot_header.grey_black_iv.hex() if self.boot_header.grey_black_iv else 'Not read'}")
        print(f"  Secure Header IV: {self.boot_header.secure_header_iv.hex() if self.boot_header.secure_header_iv else 'Not read'}")
        print(f"  PUF Shutter Value: {hex(self.boot_header.puf_shutter_value) if self.boot_header.puf_shutter_value is not None else 'Not read'}")
        print(f"  Ring Oscillator Config: {hex(self.boot_header.ring_oscillator_config) if self.boot_header.ring_oscillator_config is not None else 'Not read'}")
        print(f"  Partition Revocation ID: {self.boot_header.partition_revocation_id if self.boot_header.partition_revocation_id is not None else 'Not read'}")
        print(f"  Meta Header Offset: {hex(self.boot_header.meta_header_offset) if self.boot_header.meta_header_offset is not None else 'Not read'}")
        print(f"  Boot Header Checksum: {hex(self.boot_header.boot_header_checksum) if self.boot_header.boot_header_checksum is not None else 'Not read'}")
        
        if self.boot_header.meta_header_offset:
            print("\nVersal Gen 2 Image Header Table:")
            print(f"  Version: {hex(self.image_header_table.version) if self.image_header_table.version else 'Not read'}")
            print(f"  Total Image Count: {self.image_header_table.total_image_count if self.image_header_table.total_image_count is not None else 'Not read'}")
            print(f"  Image Header Offset: {hex(self.image_header_table.image_header_offset) if self.image_header_table.image_header_offset is not None else 'Not read'} (words)")
            print(f"  Total Partition Count: {self.image_header_table.total_partition_count if self.image_header_table.total_partition_count is not None else 'Not read'}")
            print(f"  Partition Header Offset: {hex(self.image_header_table.partition_header_offset) if self.image_header_table.partition_header_offset is not None else 'Not read'} (words)")
            print(f"  Secondary Boot Device Address: {hex(self.image_header_table.secondary_boot_device_address) if self.image_header_table.secondary_boot_device_address is not None else 'Not read'}")
            print(f"  ID Code: {hex(self.image_header_table.id_code) if self.image_header_table.id_code is not None else 'Not read'}")
            print(f"  Attributes: {hex(self.image_header_table.attributes) if self.image_header_table.attributes is not None else 'Not read'}")
            if self.image_header_table.attributes is not None:
                print(f"    Secondary Boot Device: {self.image_header_table.get_secondary_boot_device().name}")
                print(f"    DPACM Enabled: {self.image_header_table.get_dpacm_enabled()}")
                print(f"    PUF HD in Boot Header: {self.image_header_table.get_puf_hd_in_bh()}")
            print(f"  PDI ID: {hex(self.image_header_table.pdi_id) if self.image_header_table.pdi_id is not None else 'Not read'}")
            print(f"  Extended ID Code: {hex(self.image_header_table.extended_id_code) if self.image_header_table.extended_id_code is not None else 'Not read'}")
            if self.image_header_table.identification_string is not None:
                id_bytes = self.image_header_table.identification_string.to_bytes(4, 'little')
                id_str = id_bytes.decode('ascii', errors='ignore')
                print(f"  Identification String: {id_str} ({hex(self.image_header_table.identification_string)})")
            print(f"  Headers Size: {hex(self.image_header_table.headers_size) if self.image_header_table.headers_size is not None else 'Not read'}")
            if self.image_header_table.headers_size is not None:
                print(f"    IHT Size: {self.image_header_table.get_iht_size()} words")
                print(f"    IH Size: {self.image_header_table.get_ih_size()} words")
                print(f"    PH Size: {self.image_header_table.get_ph_size()} words")
            print(f"  Total Meta Header Length: {self.image_header_table.total_meta_header_length if self.image_header_table.total_meta_header_length is not None else 'Not read'} (words)")
            print(f"  Meta Header IV: {self.image_header_table.meta_header_iv.hex() if self.image_header_table.meta_header_iv else 'Not read'}")
            print(f"  Encryption Status: {hex(self.image_header_table.encryption_status) if self.image_header_table.encryption_status is not None else 'Not read'}")
            print(f"  Hash Block AC Offset: {hex(self.image_header_table.hash_block_ac_offset) if self.image_header_table.hash_block_ac_offset is not None else 'Not read'} (words)")
            print(f"  KEK IV: {self.image_header_table.kek_iv.hex() if self.image_header_table.kek_iv else 'Not read'}")
            print(f"  Optional Data Length: {self.image_header_table.optional_data_length if self.image_header_table.optional_data_length is not None else 'Not read'} (words)")
            print(f"  Auth Header: {hex(self.image_header_table.auth_header) if self.image_header_table.auth_header is not None else 'Not read'}")
            print(f"  Hash Block Length: {self.image_header_table.hash_block_length if self.image_header_table.hash_block_length is not None else 'Not read'} (words)")
            print(f"  Hash Block Offset: {hex(self.image_header_table.hash_block_offset) if self.image_header_table.hash_block_offset is not None else 'Not read'} (words)")
            print(f"  Total PPK Size: {self.image_header_table.total_ppk_size if self.image_header_table.total_ppk_size is not None else 'Not read'}")
            print(f"  Actual PPK Size: {self.image_header_table.actual_ppk_size if self.image_header_table.actual_ppk_size is not None else 'Not read'}")
            print(f"  Total Hash Block Signature Size: {self.image_header_table.total_hash_block_signature_size if self.image_header_table.total_hash_block_signature_size is not None else 'Not read'}")
            print(f"  Actual Hash Block Signature Size: {self.image_header_table.actual_hash_block_signature_size if self.image_header_table.actual_hash_block_signature_size is not None else 'Not read'}")
            print(f"  Checksum: {hex(self.image_header_table.checksum) if self.image_header_table.checksum is not None else 'Not read'}")
        
        # Display Optional Data Entries
        if self.optional_data:
            print(f"\nVersal Gen 2 (VE/VM) Optional Data Entries (Total: {len(self.optional_data)}):")
            for idx, opt_data in enumerate(self.optional_data):
                print(f"\n  Optional Data Entry #{idx}:")
                print(f"    Data ID: {hex(opt_data.data_id)} ({opt_data.get_data_id_name()})")
                print(f"    Size: {opt_data.size_words} words ({opt_data.size_words * 4} bytes including header and checksum)")
                data_size = len(opt_data.data) if opt_data.data else 0
                print(f"    Data Size: {data_size} bytes (excluding header and checksum)")
                if opt_data.data:
                    # Display data as hex with spaces, limit to first 64 bytes for readability
                    data_to_show = opt_data.data[:64]
                    hex_str = ' '.join(f'{b:02x}' for b in data_to_show)
                    if len(opt_data.data) <= 64:
                        print(f"    Data: {hex_str}")
                    else:
                        print(f"    Data: {hex_str} ... ({len(opt_data.data)} bytes total)")
                else:
                    print(f"    Data: (empty)")
                if opt_data.checksum is not None:
                    checksum_valid = opt_data.verify_checksum()
                    validity_str = "VALID" if checksum_valid else "INVALID"
                    print(f"    Checksum: {hex(opt_data.checksum)} ({validity_str})")
                else:
                    print(f"    Checksum: Not present")
        
        # Display Image Headers
        if self.image:
            print(f"\nVersal Gen 2 Image Headers (Total: {len(self.image)}):")
            for idx, (image_header, partition_headers) in enumerate(self.image):
                if idx == 0:
                    print(f"\n  Bootloader Image Header:")
                else:
                    print(f"\n  Image Header #{idx}:")
                print(f"    First Partition Header: {hex(image_header.first_partition_header_offset) if image_header.first_partition_header_offset is not None else 'Not read'} (words)")
                print(f"    Partition Count: {image_header.partition_count if image_header.partition_count is not None else 'Not read'}")
                print(f"    Meta Header Revoke ID: {image_header.meta_header_revoke_id if image_header.meta_header_revoke_id is not None else 'Not read'}")
                print(f"    Attributes: {hex(image_header.attributes) if image_header.attributes is not None else 'Not read'}")
                if image_header.attributes is not None:
                    print(f"      Image Owner: {image_header.get_image_owner().name}")
                    print(f"      Copy to Memory: {image_header.get_copy_to_memory()}")
                    print(f"      Delay Load: {image_header.get_delay_load()}")
                    print(f"      Delay Handoff: {image_header.get_delay_handoff()}")
                print(f"    Image Name: '{image_header.image_name if image_header.image_name else 'Not read'}'")
                print(f"    Image/Node ID: {hex(image_header.image_node_id) if image_header.image_node_id is not None else 'Not read'}")
                print(f"    Unique ID: {hex(image_header.unique_id) if image_header.unique_id is not None else 'Not read'}")
                print(f"    Parent Unique ID: {hex(image_header.parent_unique_id) if image_header.parent_unique_id is not None else 'Not read'}")
                print(f"    Function ID: {hex(image_header.function_id) if image_header.function_id is not None else 'Not read'}")
                if image_header.ddr_low_address is not None and image_header.ddr_high_address is not None:
                    ddr_addr = image_header.get_ddr_address()
                    print(f"    DDR Address: {hex(ddr_addr)}")
                print(f"    Checksum: {hex(image_header.checksum) if image_header.checksum is not None else 'Not read'}")
                
                # Display all Partition Headers for this image
                for part_idx, partition_header in enumerate(partition_headers):
                    if idx == 0:
                        print(f"\n  Bootloader Partition Header:")
                    else:
                        print(f"\n  Partition Header #{idx}.{part_idx}:")
                    print(f"    Encrypted Partition Length: {partition_header.encrypted_partition_length if partition_header.encrypted_partition_length is not None else 'Not read'}")
                    print(f"    Unencrypted Partition Length: {partition_header.unencrypted_partition_length if partition_header.unencrypted_partition_length is not None else 'Not read'}")
                    print(f"    Total Partition Word Length: {partition_header.total_partition_word_length if partition_header.total_partition_word_length is not None else 'Not read'}")
                    print(f"    Next Partition Header Offset: {hex(partition_header.next_partition_header_offset) if partition_header.next_partition_header_offset is not None else 'Not read'} (words)")
                    # Display 64-bit addresses combined
                    if partition_header.destination_execution_address_hi is not None and partition_header.destination_execution_address_lo is not None:
                        exec_addr = partition_header.get_destination_execution_address()
                        print(f"    Destination Execution Address: {hex(exec_addr)}")
                    else:
                        print(f"    Destination Execution Address: Not read")
                    if partition_header.destination_load_address_hi is not None and partition_header.destination_load_address_lo is not None:
                        load_addr = partition_header.get_destination_load_address()
                        print(f"    Destination Load Address: {hex(load_addr)}")
                    else:
                        print(f"    Destination Load Address: Not read")
                    print(f"    Data Word Offset: {hex(partition_header.data_word_offset) if partition_header.data_word_offset is not None else 'Not read'}")
                    print(f"    Attribute Bits: {hex(partition_header.attribute_bits) if partition_header.attribute_bits is not None else 'Not read'}")
                    print(f"      Partition Type: {partition_header.get_partition_type().name}")
                    print(f"      Trustzone: {partition_header.get_trustzone().name}")
                    print(f"      Exception Level: {partition_header.get_exception_level().name}")
                    print(f"      A78 Exec State: {partition_header.get_a78_exec_state().name}")
                    print(f"      Destination CPU: {partition_header.get_destination_cpu().name}")
                    print(f"      Destination Cluster: {partition_header.get_destination_cluster().name}")
                    print(f"      Cluster Lockstep: {partition_header.get_cluster_lockstep().name}")
                    print(f"      Checksum Type: {partition_header.get_checksum_type().name}")
                    print(f"      Owner: {partition_header.get_partition_owner().name}")
                    print(f"      Endianness: {partition_header.get_endianness().name}")
                    print(f"      Vector Location: {partition_header.get_vector_location().name}")
                    print(f"      DPACM Enabled: {partition_header.get_dpacm_enabled()}")
                    print(f"    Section Count: {partition_header.section_count if partition_header.section_count is not None else 'Not read'}")
                    print(f"    Checksum Word Offset: {hex(partition_header.checksum_word_offset) if partition_header.checksum_word_offset is not None else 'Not read'}")
                    print(f"    Partition ID: {partition_header.partition_id if partition_header.partition_id is not None else 'Not read'}")
                    print(f"    Hash Block AC Word Offset: {hex(partition_header.hash_block_ac_word_offset) if partition_header.hash_block_ac_word_offset is not None else 'Not read'}")
                    print(f"    IV: {partition_header.iv.hex() if partition_header.iv else 'Not read'}")
                    print(f"    Encryption Key Select: {hex(partition_header.encryption_key_select) if partition_header.encryption_key_select is not None else 'Not read'}")
                    print(f"    KEK IV: {partition_header.kek_iv.hex() if partition_header.kek_iv else 'Not read'}")
                    print(f"    Partition Revoke ID: {partition_header.partition_revoke_id if partition_header.partition_revoke_id is not None else 'Not read'}")
                    print(f"    Measured Boot Address: {hex(partition_header.measured_boot_address) if partition_header.measured_boot_address is not None else 'Not read'}")
                    print(f"    Auth Header: {hex(partition_header.auth_header) if partition_header.auth_header is not None else 'Not read'}")
                    print(f"    Hash Block Length: {partition_header.hash_block_length if partition_header.hash_block_length is not None else 'Not read'} (words)")
                    print(f"    Hash Block Offset: {hex(partition_header.hash_block_offset) if partition_header.hash_block_offset is not None else 'Not read'} (words)")
                    print(f"    Total PPK Size: {partition_header.total_ppk_size if partition_header.total_ppk_size is not None else 'Not read'}")
                    print(f"    Actual PPK Size: {partition_header.actual_ppk_size if partition_header.actual_ppk_size is not None else 'Not read'}")
                    print(f"    Total Hash Block Signature Size: {partition_header.total_hash_block_signature_size if partition_header.total_hash_block_signature_size is not None else 'Not read'}")
                    print(f"    Actual Hash Block Signature Size: {partition_header.actual_hash_block_signature_size if partition_header.actual_hash_block_signature_size is not None else 'Not read'}")
                    print(f"    Header Checksum: {hex(partition_header.header_checksum) if partition_header.header_checksum is not None else 'Not read'}")

    def _get_first_stage_loader_length(self, data_byte_offset: int, default_length: int) -> int:
        """Return the length for PLM partition, or default_length otherwise.
        
        For Versal Gen 2 (VE/VM), the PLM (Platform Loader and Manager) partition length
        is stored in the boot header. We use total_plm_length (with auth overhead) which
        will be stripped down during extraction with hash removal.
        
        Args:
            data_byte_offset: The data offset of the current partition.
            default_length: The default length from the partition header.
        
        Returns:
            The total PLM length if this is the PLM partition, otherwise default_length.
        """
        # Check if this matches PLM source offset from boot header
        # Note: Partition header has wrong offset (0x1140) but boot header has correct offset (0x1280)
        # So we check against plm_source_offset
        if self.boot_header.plm_source_offset is not None:
            # For the first partition of pmc_subsys image, use total_plm_length
            if len(self.image) > 0:
                img_header, part_headers = self.image[0]
                if img_header.image_name.replace('\x00', '') == 'pmc_subsys' and len(part_headers) > 0:
                    # This is the pmc_subsys partition - use total_plm_length
                    return self.boot_header.total_plm_length
        return default_length

    def _get_hash_size(self, auth_header: int) -> int:
        """Get hash size in bytes based on authentication header.
        
        Args:
            auth_header: The authentication header value.
        
        Returns:
            Hash size in bytes (32 for SHA2/SHA256, 48 for SHA3/SHA384, 0 for none).
        """
        hash_type = (auth_header & 0xF0) >> 4
        if hash_type == 0:
            return 32  # SHA2_LENGTH_BYTES (SHA256)
        elif hash_type == 1:
            return 48  # SHA3_LENGTH_BYTES (SHA384)
        return 0

    def _get_chunk_size(self, partition_name: str, is_bootloader: bool) -> int:
        """Get secure chunk size for partition.
        
        Args:
            partition_name: Name of the partition.
            is_bootloader: Whether this is a bootloader partition (PLM/PMC_CDO).
        
        Returns:
            Chunk size in bytes (16KB for bootloader, 32KB for others).
        """
        # Check if this is PLM or PMC_CDO
        if partition_name in ['pmc_subsys', 'plm', 'pmc_cdo'] or is_bootloader:
            return 0x4000  # SECURE_16K_CHUNK = 16KB
        return 0x8000  # SECURE_32K_CHUNK = 32KB

    def _write_partition_with_hash_removal(self, output_fd, length: int, 
                                           auth_header: int, partition_name: str = '',
                                           is_bootloader: bool = False) -> None:
        """Write partition data with authentication hash removal.
        
        This method matches bootgen's DumpPlainPartition behavior by removing
        authentication/integrity hashes embedded in chunks (except the last chunk).
        
        Args:
            output_fd: Output file descriptor to write to.
            length: Total length of partition data to read.
            auth_header: Authentication header value.
            partition_name: Name of the partition.
            is_bootloader: Whether this is a bootloader partition.
        """
        # Get hash size from auth_header (bits 4-7)
        # Hash is present if hash_size > 0, regardless of authentication type
        hash_size = self._get_hash_size(auth_header)
        
        # If no hash, just write normally
        if hash_size == 0:
            bytes_remaining = length
            chunk_size = 4096
            while bytes_remaining > 0:
                read_size = min(chunk_size, bytes_remaining)
                chunk = self.image_fd.read(read_size)
                output_fd.write(chunk)
                bytes_remaining -= read_size
            return
        
        # Hash present - need to remove hashes from chunks
        secure_chunk_size = self._get_chunk_size(partition_name, is_bootloader)
        
        bytes_remaining = length
        while bytes_remaining > 0:
            current_chunk_size = min(secure_chunk_size, bytes_remaining)
            chunk_data = self.image_fd.read(current_chunk_size)
            
            # Check if this is the last chunk by seeing if more bytes remain after this one
            # This matches bootgen's logic: "if (i + chunksize < inputSize)"
            is_last_chunk = (current_chunk_size >= bytes_remaining)
            
            if not is_last_chunk:
                # Not last chunk - remove hash from the end
                bytes_to_write = current_chunk_size - hash_size
                output_fd.write(chunk_data[:bytes_to_write])
            else:
                # Last chunk - write all data
                output_fd.write(chunk_data)
            
            bytes_remaining -= current_chunk_size

    def extract(self, image_index: int, partition_index: int = 0, filename: Optional[str] = None) -> bool:
        """Extract a partition from the boot image with authentication-aware hash removal.
        
        This overrides the base class to implement bootgen-compatible extraction
        that removes authentication hashes from chunks.
        
        Args:
            image_index: The image index to extract from.
            partition_index: The partition index within the image to extract (default: 0).
            filename: Optional filename to write the extracted partition to.
        
        Returns:
            True if the extraction was successful, False otherwise.
        """
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        if image_index < 0 or image_index >= len(self.image):
            raise IndexError(f"Image index {image_index} out of range (0-{len(self.image) - 1})")
        
        image_header, partition_headers = self.image[image_index]
        
        if partition_index < 0 or partition_index >= len(partition_headers):
            raise IndexError(f"Partition index {partition_index} out of range (0-{len(partition_headers) - 1})")
        
        partition_header = partition_headers[partition_index]
        
        # Determine output filename
        output_filename = filename if filename else image_header.image_name
        if not output_filename:
            raise ValueError("No filename provided and image has no image name")
        output_filename = output_filename.replace('\x00', '')
        
        # For sub-partitions (partition_index > 0), append the partition index to the filename
        if partition_index > 0 and not filename:
            if '.' in output_filename:
                name, ext = output_filename.rsplit('.', 1)
                output_filename = f"{name}.{partition_index}.{ext}"
            else:
                output_filename = f"{output_filename}.{partition_index}"
        
        # Get byte offset and length
        data_byte_offset = partition_header.get_data_offset_bytes()
        data_length = partition_header.get_unencrypted_partition_length_bytes()
        
        # Determine partition name and if it's bootloader (PLM)
        partition_name = image_header.image_name.replace('\x00', '')
        is_plm = (image_index == 0 and partition_index == 0 and partition_name == 'pmc_subsys')
        
        # For PLM partition, use boot header's total_plm_length instead of partition header length
        if is_plm and self.boot_header.total_plm_length is not None:
            data_length = self.boot_header.total_plm_length
            # Also use the boot header's source offset
            data_byte_offset = self.boot_header.plm_source_offset
        
        # Get authentication header
        auth_header = 0
        # For PLM, use boot header's auth_header_1; for other partitions, use partition header
        if is_plm:
            auth_header = self.boot_header.auth_header_1 if self.boot_header.auth_header_1 is not None else 0
        elif partition_header.auth_header is not None and partition_header.auth_header != 0:
            auth_header = partition_header.auth_header
        elif self.boot_header.auth_header_1 is not None:
            auth_header = self.boot_header.auth_header_1
        
        # Seek and extract with hash removal
        self._safe_seek(self.image_path_offset + data_byte_offset, data_length)
        
        with open(output_filename, 'wb') as output_fd:
            self._write_partition_with_hash_removal(output_fd, data_length, auth_header, 
                                                    partition_name, is_plm)
        
        return True

    def dump_hash_block_0(self, filename: Optional[str] = None) -> bool:
        """Dump Hash Block 0 to a binary file (HashBlock0.bin).
        
        Hash Block 0 is located immediately after the boot header at offset
        0x1140. The size of Hash Block 0 is specified in the boot header's
        hash_block_size_1 field at offset 0x284.
        
        Args:
            filename: Optional filename to write the hash block to.
                     If not provided, uses "HashBlock0.bin".
        
        Returns:
            True if the dump was successful, False otherwise.
        
        Raises:
            RuntimeError: If no image data has been parsed.
            ValueError: If Hash Block 0 is not available in the PDI.
        """
        if self.boot_header is None:
            raise RuntimeError("No boot header available. Call parse() first.")
        
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        # Check if Hash Block 0 is available
        if (self.boot_header.hash_block_size_1 is None or 
            self.boot_header.hash_block_size_1 == 0):
            return False  # Hash Block 0 not present
        
        # Determine the output filename
        if filename is None:
            filename = "HashBlock0.bin"
        
        # Hash Block 0 is located right after the boot header
        hash_block_offset = Versal2VE2VMBootHeader.BOOT_HEADER_SIZE  # 0x1140
        hash_block_length = self.boot_header.hash_block_size_1
        
        # Seek to the Hash Block 0 location
        self._safe_seek(self.image_path_offset + hash_block_offset, hash_block_length)
        
        # Read and write in chunks
        chunk_size = 4096
        bytes_remaining = hash_block_length
        with open(filename, 'wb') as output_fd:
            while bytes_remaining > 0:
                read_size = min(chunk_size, bytes_remaining)
                chunk = self.image_fd.read(read_size)
                output_fd.write(chunk)
                bytes_remaining -= read_size
        
        return True

    def dump_pmc_cdo(self, filename: Optional[str] = None) -> bool:
        """Dump the PMC CDO data to a binary file (pmc_cdo.bin).
        
        The PMC CDO (Configuration Data Object) is located immediately after
        the PLM data in the boot image. This method extracts the PMC CDO data
        based on the boot header fields, with authentication-aware hash removal.
        
        The PMC CDO is located at:
            offset = plm_source_offset + plm_length
            size = total_pmc_cdo_length
        
        Args:
            filename: Optional filename to write the PMC CDO to.
                     If not provided, uses "pmc_cdo.bin".
        
        Returns:
            True if the dump was successful, False otherwise.
        
        Raises:
            RuntimeError: If no image data has been parsed.
            ValueError: If the PMC CDO data is not available in the PDI.
        """
        if self.boot_header is None:
            raise RuntimeError("No boot header available. Call parse() first.")
        
        if not self.image_fd:
            raise RuntimeError("No image data loaded. Call open() first.")
        
        # Check if PMC CDO data is available
        if (self.boot_header.total_pmc_cdo_length is None or 
            self.boot_header.total_pmc_cdo_length == 0):
            raise ValueError("PMC_CDO partition is not available in the PDI")
        
        # Determine the output filename
        if filename is None:
            filename = "pmc_cdo.bin"
        
        # Calculate offset: PMC CDO is right after PLM data
        pmc_cdo_offset = self.boot_header.plm_source_offset + self.boot_header.total_plm_length
        pmc_cdo_length = self.boot_header.total_pmc_cdo_length
        
        # Get authentication header from boot header
        auth_header = self.boot_header.auth_header_1 if self.boot_header.auth_header_1 is not None else 0
        
        # Seek to the PMC CDO location
        self._safe_seek(self.image_path_offset + pmc_cdo_offset, pmc_cdo_length)
        
        # Write with authentication-aware hash removal
        with open(filename, 'wb') as output_fd:
            self._write_partition_with_hash_removal(output_fd, pmc_cdo_length, auth_header,
                                                    'pmc_cdo', is_bootloader=True)
        
        return True

