///|
pub fn PEImage::parse(data : Bytes) -> Self raise PeError {
  if data.length() < 64 {
    raise PeError(0, "file is too small to contain a DOS header")
  }
  if read_u8(data, 0) != 0x4d || read_u8(data, 1) != 0x5a {
    raise PeError(0, "missing MZ DOS signature")
  }
  let pe_offset = checked_int(read_u32(data, 0x3c), 0x3c, "e_lfanew")
  ensure_range(data, pe_offset, 24)
  if read_u8(data, pe_offset) != 0x50 ||
    read_u8(data, pe_offset + 1) != 0x45 ||
    read_u8(data, pe_offset + 2) != 0 ||
    read_u8(data, pe_offset + 3) != 0 {
    raise PeError(pe_offset, "missing PE signature")
  }
  let coff = pe_offset + 4
  let machine = read_u16(data, coff)
  let number_of_sections = read_u16(data, coff + 2)
  if number_of_sections > 1024 {
    raise PeError(coff + 2, "section count exceeds the safety limit")
  }
  let timestamp = read_u32(data, coff + 4)
  let symbol_table_pointer = read_u32(data, coff + 8)
  let number_of_symbols = read_u32(data, coff + 12)
  let size_of_optional_header = read_u16(data, coff + 16)
  let characteristics = read_u16(data, coff + 18)
  let optional = coff + 20
  ensure_range(data, optional, size_of_optional_header)
  if size_of_optional_header < 2 {
    raise PeError(optional, "optional header is missing")
  }
  let magic = read_u16(data, optional)
  let format = if magic == 0x10b {
    PE32
  } else if magic == 0x20b {
    PE32Plus
  } else {
    raise PeError(optional, "unsupported optional-header magic")
  }
  let minimum_optional = if format is PE32 { 96 } else { 112 }
  if size_of_optional_header < minimum_optional {
    raise PeError(optional, "optional header is truncated")
  }
  let entry_point = read_u32(data, optional + 16)
  let image_base = if format is PE32 {
    read_u32(data, optional + 28)
  } else {
    read_u64(data, optional + 24)
  }
  let section_alignment = checked_int(
    read_u32(data, optional + 32),
    optional + 32,
    "section alignment",
  )
  let file_alignment = checked_int(
    read_u32(data, optional + 36),
    optional + 36,
    "file alignment",
  )
  let size_of_image = checked_int(
    read_u32(data, optional + 56),
    optional + 56,
    "image size",
  )
  let size_of_headers = checked_int(
    read_u32(data, optional + 60),
    optional + 60,
    "header size",
  )
  let checksum = read_u32(data, optional + 64)
  let subsystem = read_u16(data, optional + 68)
  let dll_characteristics = read_u16(data, optional + 70)
  let number_of_directories = if format is PE32 {
    read_u32(data, optional + 92)
  } else {
    read_u32(data, optional + 108)
  }
  let directories_offset = if format is PE32 {
    optional + 96
  } else {
    optional + 112
  }
  let optional_end = optional + size_of_optional_header
  let available_directories = if directories_offset <= optional_end {
    (optional_end - directories_offset) / 8
  } else {
    0
  }
  let directory_limit = if number_of_directories < 16UL {
    number_of_directories.to_int()
  } else {
    16
  }
  let directory_count = if directory_limit < available_directories {
    directory_limit
  } else {
    available_directories
  }
  let directories = []
  for index in 0.. 0 {
      ensure_range(data, raw_offset, raw_size)
    }
    sections.push({
      name,
      virtual_size,
      virtual_address,
      raw_size,
      raw_offset,
      characteristics: section_characteristics,
    })
  }
  {
    data,
    pe_offset,
    format,
    machine,
    number_of_sections,
    timestamp,
    symbol_table_pointer,
    number_of_symbols,
    size_of_optional_header,
    characteristics,
    entry_point,
    image_base,
    section_alignment,
    file_alignment,
    size_of_image,
    size_of_headers,
    checksum,
    subsystem,
    dll_characteristics,
    sections,
    directories,
  }
}

///|
pub fn PEImage::rva_to_offset(self : Self, rva : UInt64) -> Int32 raise PeError {
  if rva < self.size_of_headers.to_uint64() {
    return checked_int(rva, 0, "RVA")
  }
  for section in self.sections {
    let span = if section.virtual_size > section.raw_size {
      section.virtual_size
    } else {
      section.raw_size
    }
    let start = section.virtual_address
    let end = start + span.to_uint64()
    if rva >= start && rva < end {
      let delta = rva - start
      if delta >= section.raw_size.to_uint64() {
        raise PeError(
          section.raw_offset,
          "RVA points to uninitialized section data",
        )
      }
      return section.raw_offset + checked_int(delta, 0, "RVA delta")
    }
  }
  raise PeError(0, "RVA is not mapped by any section")
}

///|
pub fn PEImage::read_c_string(
  self : Self,
  rva : UInt64,
  max_length : Int32,
) -> String raise PeError {
  let offset = self.rva_to_offset(rva)
  read_ascii_z(self.data, offset, max_length)
}

///|
fn directory_name(index : Int32) -> String {
  match index {
    0 => "export"
    1 => "import"
    2 => "resource"
    3 => "exception"
    4 => "certificate"
    5 => "base-reloc"
    6 => "debug"
    7 => "architecture"
    8 => "global-pointer"
    9 => "tls"
    10 => "load-config"
    11 => "bound-import"
    12 => "iat"
    13 => "delay-import"
    14 => "clr-runtime"
    _ => "reserved"
  }
}