"""A class for reading Amiga executables and object files in Hunk format"""

import os
import struct
import StringIO
from types import *
from Hunk import *

class HunkReader:
  """Load Amiga executable Hunk structures"""

  def __init__(self):
    self.hunks = []
    self.error_string = None
    self.type = None
    self.header = None
    self.segments = []
    self.overlay = None
    self.overlay_headers = None
    self.overlay_segments = None
    self.libs = None
    self.units = None

  def get_struct_summary(self, obj):
    if type(obj) == ListType:
      result = []
      for a in obj:
        v = self.get_struct_summary(a)
        if v != None:
          result.append(v)
      return "[" + ",".join(result) + "]"
    elif type(obj) == DictType:
      if obj.has_key('type_name'):
        type_name = obj['type_name']
        return type_name.replace('HUNK_','')
      else:
        result = []
        for k in obj.keys():
          v = self.get_struct_summary(obj[k])
          if v != None:
            result.append(k + ":" + v)
        return '{' + ",".join(result) + '}'
    else:
      return None

  def get_long(self, data):
    return struct.unpack(">I",data)[0]

  def read_long(self, f):
    data = f.read(4)
    if len(data) == 0:
      return -1
    elif len(data) != 4:
      return -(len(data)+1)
    return struct.unpack(">I",data)[0]

  def read_word(self, f):
    data = f.read(2)
    if len(data) == 0:
      return -1
    elif len(data) != 2:
      return -(len(data)+1)
    return struct.unpack(">H",data)[0]

  def read_name(self, f):
    num_longs = self.read_long(f)
    if num_longs < 0:
      return -1,None
    elif num_longs == 0:
      return 0,""
    else:
      return self.read_name_size(f, num_longs)

  def read_tag(self, f):
    data = f.read(4)
    if len(data) == 0:
      return -1;
    elif len(data) != 4:
      return -(len(data)+1)
    return data

  def read_name_size(self, f, num_longs):
    size = (num_longs & 0xffffff) * 4
    data = f.read(size)
    if len(data) < size:
      return -1,None
    endpos = data.find('\0')
    if endpos == -1:
      return size,data
    elif endpos == 0:
      return 0,""
    else:
      return size,data[:endpos]

  def get_index_name(self, strtab, offset):
    end = strtab.find('\0',offset)
    if end == -1:
      return strtab[offset:]
    else:
      return strtab[offset:end]

  def is_valid_first_hunk_type(self, hunk_type):
    return hunk_type == HUNK_HEADER or hunk_type == HUNK_LIB or hunk_type == HUNK_UNIT

  def parse_header(self, f, hunk):
    names = []
    hunk['names'] = names
    while True:
      l,s = self.read_name(f)
      if l < 0:
        self.error_string = "Error parsing HUNK_HEADER names"
        return RESULT_INVALID_HUNK_FILE
      elif l == 0:
        break
      names.append(s)

    # table size and hunk range
    table_size = self.read_long(f)
    first_hunk = self.read_long(f)
    last_hunk  = self.read_long(f)
    if table_size < 0 or first_hunk < 0 or last_hunk < 0:
      self.error_string = "HUNK_HEADER invalid table_size or first_hunk or last_hunk"
      return RESULT_INVALID_HUNK_FILE

    hunk['table_size'] = table_size
    hunk['first_hunk'] = first_hunk
    hunk['last_hunk'] = last_hunk

    # determine number of hunks in size table
    num_hunks = last_hunk - first_hunk + 1
    hunk_table = []
    for a in xrange(num_hunks):
      hunk_info = {}
      hunk_size = self.read_long(f)
      if hunk_size < 0:
        self.error_string = "HUNK_HEADER contains invalid hunk_size"
        return RESULT_INVALID_HUNK_FILE
      hunk_bytes = hunk_size & ~HUNKF_ALL
      hunk_bytes *= 4 # longs to bytes
      hunk_info['size'] = hunk_bytes
      self.set_mem_flags(hunk_info, hunk_size & HUNKF_ALL, 30)
      hunk_table.append(hunk_info)
    hunk['hunks'] = hunk_table
    return RESULT_OK

  def parse_code_or_data(self, f, hunk):
    num_longs = self.read_long(f)
    if num_longs < 0:
      self.error_string = "%s has invalid size" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE

    # read in hunk data
    size = num_longs * 4

    hunk['size'] = size & ~HUNKF_ALL
    flags = size & HUNKF_ALL
    self.set_mem_flags(hunk, flags, 30)
    hunk['data_file_offset'] = f.tell()
    data = f.read(hunk['size'])
    hunk['data'] = data
    return RESULT_OK

  def parse_bss(self, f, hunk):
    num_longs = self.read_long(f)
    if num_longs < 0:
      self.error_string = "%s has invalid size" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE

    # read in hunk data
    size = num_longs * 4

    hunk['size'] = size & ~HUNKF_ALL
    flags = size & HUNKF_ALL
    self.set_mem_flags(hunk, flags, 30)
    return RESULT_OK

  def parse_reloc(self, f, hunk):
    num_relocs = 1
    reloc = {}
    hunk['reloc'] = reloc
    while num_relocs != 0:
      num_relocs = self.read_long(f)
      if num_relocs < 0:
        self.error_string = "%s has invalid number of relocations" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE
      elif num_relocs == 0:
        # last relocation found
        break

      # build reloc map
      hunk_num = self.read_long(f)
      if hunk_num < 0:
        self.error_string = "%s has invalid hunk num" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE

      offsets = []
      for a in xrange(num_relocs & 0xffff):
        offset = self.read_long(f)
        if offset < 0:
          self.error_string = "%s has invalid relocation #%d offset %d (num_relocs=%d hunk_num=%d, offset=%d)" \
            % (hunk['type_name'],a,offset,num_relocs,hunk_num,f.tell())
          return RESULT_INVALID_HUNK_FILE
        offsets.append(offset)
      reloc[hunk_num] = offsets
    return RESULT_OK

  def parse_reloc_short(self, f, hunk):
    num_relocs = 1
    reloc = {}
    hunk['reloc'] = reloc
    total_words = 0
    while num_relocs != 0:
      num_relocs = self.read_word(f)
      if num_relocs < 0:
        self.error_string = "%s has invalid number of relocations" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE
      elif num_relocs == 0:
        # last relocation found
        total_words += 1
        break

      # build reloc map
      hunk_num = self.read_word(f)
      if hunk_num < 0:
        self.error_string = "%s has invalid hunk num" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE

      offsets = []
      count = num_relocs & 0xffff
      total_words += count + 2
      for a in xrange(count):
        offset = self.read_word(f)
        if offset < 0:
          self.error_string = "%s has invalid relocation #%d offset %d (num_relocs=%d hunk_num=%d, offset=%d)" \
            % (hunk['type_name'],a,offset,num_relocs,hunk_num,f.tell())
          return RESULT_INVALID_HUNK_FILE
        offsets.append(offset)
      reloc[hunk_num] = offsets

    # padding
    if total_words & 1 == 1:
      self.read_word(f)
    return RESULT_OK

  def parse_symbol(self, f, hunk):
    name_len = 1
    symbols = []
    hunk['symbols'] = symbols
    while name_len > 0:
      (name_len, name) = self.read_name(f)
      if name_len < 0:
        self.error_string = "%s has invalid symbol name" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE
      elif name_len == 0:
        # last name occurred
        break
      value = self.read_long(f)
      if value < 0:
        self.error_string = "%s has invalid symbol value" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE
      symbols.append( (name,value) )
    return RESULT_OK

  def parse_debug(self, f, hunk):
    num_longs = self.read_long(f)
    if num_longs < 0:
      self.error_string = "%s has invalid size" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE
    size = num_longs * 4

    offset = self.read_long(f)
    hunk['debug_offset'] = offset;
    tag = self.read_tag(f)
    hunk['debug_type'] = tag;
    size -= 8

    if tag == 'LINE':
      # parse LINE: source line -> code offset mapping
      l = self.read_long(f)
      size -= l * 4 + 4;
      l,n = self.read_name_size(f,l)
      src_map = []
      hunk['src_file'] = n
      hunk['src_map'] = src_map
      while size > 0:
        line_no = self.read_long(f)
        offset = self.read_long(f)
        size -= 8
        src_map.append([line_no,offset])
    else:
      # read unknown DEBUG hunk
      hunk['data'] = f.read(size)
    return RESULT_OK

  def find_first_code_hunk(self):
    for hunk in self.hunks:
      if hunk['type'] == HUNK_CODE:
        return hunk
    return None

  def parse_overlay(self, f, hunk):
    # read size of overlay hunk
    ov_size = self.read_long(f)
    if ov_size < 0:
      self.error_string = "%s has invalid size" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE

    # read data of overlay
    byte_size = (ov_size + 1) *4
    ov_data = f.read(byte_size)
    hunk['ov_data'] = ov_data

    # check: first get header hunk
    hdr_hunk = self.hunks[0]
    if hdr_hunk['type'] != HUNK_HEADER:
      self.error_string = "%s has no header hunk" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE

    # first find the code segment of the overlay manager
    overlay_mgr_hunk = self.find_first_code_hunk()
    if overlay_mgr_hunk == None:
      self.error_string = "%s has no overlay manager hunk" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE

    # check overlay manager
    overlay_mgr_data = overlay_mgr_hunk['data']
    magic = self.get_long(overlay_mgr_data[4:8])
    if magic != 0xabcd:
      self.error_string = "no valid overlay manager magic found"
      return RESULT_INVALID_HUNK_FILE

    # check for standard overlay manager
    magic2 = self.get_long(overlay_mgr_data[24:28])
    magic3 = self.get_long(overlay_mgr_data[28:32])
    magic4 = self.get_long(overlay_mgr_data[32:36])
    std_overlay = (magic2 == 0x5ba0) and (magic3 == 0x074f7665) and (magic4 == 0x726c6179)
    hunk['ov_std'] = std_overlay

    return RESULT_OK

  def parse_lib(self, f, hunk):
    lib_size = self.read_long(f)
    hunk['lib_file_offset'] = f.tell()
    return RESULT_OK,lib_size * 4

  def parse_index(self, f, hunk):
    index_size = self.read_long(f)
    total_size = index_size * 4

    # first read string table
    strtab_size = self.read_word(f)
    strtab = f.read(strtab_size)
    total_size -= strtab_size + 2

    # read units
    units = []
    hunk['units'] = units
    unit_no = 0
    while total_size > 2:
      # read name of unit
      name_offset = self.read_word(f)
      total_size -= 2
      if name_offset == 0:
        break

      unit = {}
      units.append(unit)
      unit['unit_no'] = unit_no
      unit_no += 1

      # generate unit name
      unit['name'] = self.get_index_name(strtab, name_offset)

      # hunks in unit
      hunk_begin = self.read_word(f)
      num_hunks = self.read_word(f)
      total_size -= 4
      unit['hunk_begin_offset'] = hunk_begin

      # for all hunks in unit
      ihunks = []
      unit['hunk_infos'] = ihunks
      for a in xrange(num_hunks):
        ihunk = {}
        ihunks.append(ihunk)

        # get hunk info
        name_offset = self.read_word(f)
        hunk_size   = self.read_word(f)
        hunk_type   = self.read_word(f)
        total_size -= 6
        ihunk['name'] = self.get_index_name(strtab, name_offset)
        ihunk['size'] = hunk_size
        ihunk['type'] = hunk_type & 0x3fff
        self.set_mem_flags(ihunk,hunk_type & 0xc000,14)
        ihunk['type_name'] = hunk_names[hunk_type & 0x3fff]

        # get references
        num_refs = self.read_word(f)
        total_size -= 2
        if num_refs > 0:
          refs = []
          ihunk['refs'] = refs
          for b in xrange(num_refs):
            ref = {}
            name_offset = self.read_word(f)
            total_size -= 2
            name = self.get_index_name(strtab, name_offset)
            if name == '':
              # 16 bit refs point to the previous zero byte before the string entry...
              name = self.get_index_name(strtab, name_offset+1)
              ref['bits'] = 16
            else:
              ref['bits'] = 32
            ref['name'] = name
            refs.append(ref)

        # get definitions
        num_defs = self.read_word(f)
        total_size -= 2
        if num_defs > 0:
          defs = []
          ihunk['defs'] = defs
          for b in xrange(num_defs):
            name_offset = self.read_word(f)
            def_value = self.read_word(f)
            def_type_flags = self.read_word(f)
            def_type = def_type_flags & 0x3fff
            def_flags = def_type_flags & 0xc000
            total_size -= 6
            name = self.get_index_name(strtab, name_offset)
            d = { 'name':name, 'value':def_value,'type':def_type}
            self.set_mem_flags(d,def_flags,14)
            defs.append(d)

    # align hunk
    if total_size == 2:
      self.read_word(f)
    elif total_size != 0:
      self.error_string = "%s has invalid padding" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE
    return RESULT_OK

  def parse_ext(self, f, hunk):
    ext_def = []
    ext_ref = []
    ext_common = []
    hunk['ext_def'] = ext_def
    hunk['ext_ref'] = ext_ref
    hunk['ext_common'] = ext_common
    ext_type_size = 1
    while ext_type_size > 0:
      # ext type | size
      ext_type_size = self.read_long(f)
      if ext_type_size < 0:
        self.error_string = "%s has invalid size" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE
      ext_type = ext_type_size >> EXT_TYPE_SHIFT
      ext_size = ext_type_size & EXT_TYPE_SIZE_MASK

      # ext name
      l,ext_name = self.read_name_size(f, ext_size)
      if l < 0:
        self.error_string = "%s has invalid name" % (hunk['type_name'])
        return RESULT_INVALID_HUNK_FILE
      elif l == 0:
        break

      # create local ext object
      ext = { 'type' : ext_type, 'name' : ext_name }

      # check and setup type name
      if not ext_names.has_key(ext_type):
        self.error_string = "%s has unspported ext entry %d" % (hunk['type_name'],ext_type)
        return RESULT_INVALID_HUNK_FILE
      ext['type_name'] = ext_names[ext_type]

      # ext common
      if ext_type == EXT_ABSCOMMON or ext_type == EXT_RELCOMMON:
        ext['common_size'] = self.read_long(f)
        ext_common.append(ext)
      # ext def
      elif ext_type == EXT_DEF or ext_type == EXT_ABS or ext_type == EXT_RES:
        ext['def'] = self.read_long(f)
        ext_def.append(ext)
      # ext ref
      else:
        num_refs = self.read_long(f)
        if num_refs == 0:
          num_refs = 1
        refs = []
        for a in xrange(num_refs):
          ref = self.read_long(f)
          refs.append(ref)
        ext['refs'] = refs
        ext_ref.append(ext)

    return RESULT_OK

  def parse_unit_or_name(self, f, hunk):
    l,n = self.read_name(f)
    if l < 0:
      self.error_string = "%s has invalid name" % (hunk['type_name'])
      return RESULT_INVALID_HUNK_FILE
    elif l > 0:
      hunk['name'] = n
    else:
      hunk['name'] = ""
    return RESULT_OK

  def set_mem_flags(self, hunk, flags, shift):
    f = flags >> shift
    if f & 1 == 1:
      hunk['memf'] = 'chip'
    elif f & 2 == 2:
      hunk['memf'] = 'fast'
    else:
      hunk['memf'] = ''

  # ----- public functions -----

  """Read a hunk file and build internal hunk structure
     Return status and set self.error_string on failure
  """
  def read_file(self, hfile, v37_compat=None):
    with open(hfile, "rb") as f:
      return self.read_file_obj(hfile, f, v37_compat)

  """Read a hunk from memory"""
  def read_mem(self, name, data, v37_compat=None):
    fobj = StringIO.StringIO(data)
    return self.read_file_obj(name, fobj, v37_compat)

  def read_file_obj(self, hfile, f, v37_compat):
    self.hunks = []
    is_first_hunk = True
    was_end = False
    was_potentail_v37_hunk = False
    was_overlay = False
    self.error_string = None
    lib_size = 0
    last_file_offset = 0

    while True:
      hunk_file_offset = f.tell()

      # read hunk type
      hunk_raw_type = self.read_long(f)
      if hunk_raw_type == -1 or hunk_raw_type == -2: # tolerate extra byte at end
        if is_first_hunk:
          self.error_string = "No valid hunk file: '%s' is empty" % (hfile)
          return RESULT_NO_HUNK_FILE
        else:
          # eof
          break
      elif hunk_raw_type < 0:
        if is_first_hunk:
          self.error_string = "No valid hunk file: '%s' is too short" % (hfile)
          return RESULT_NO_HUNK_FILE
        else:
          self.error_string = "Error reading hunk type @%08x" % (f.tell())
          return RESULT_INVALID_HUNK_FILE

      hunk_type = hunk_raw_type & HUNK_TYPE_MASK
      hunk_flags = hunk_raw_type & HUNK_FLAGS_MASK

      # check range of hunk type
      if not hunk_names.has_key(hunk_type):
        # no hunk file?
        if is_first_hunk:
          self.error_string = "No hunk file: '%s' type was %d" % (hfile, hunk_type)
          return RESULT_NO_HUNK_FILE
        elif was_end:
          # garbage after an end tag is ignored
          return RESULT_OK
        elif was_potentail_v37_hunk:
          # auto fix v37 -> reread whole file
          f.seek(0)
          return self.read_file_obj(hfile, f, True)
        elif was_overlay:
          # seems to be a custom overlay -> read to end of file
          ov_custom_data = f.read()
          self.hunks[-1]['custom_data'] = ov_custom_data
          return RESULT_OK
        else:
          self.error_string = "Invalid hunk type %d/%x found at @%08x" % (hunk_type,hunk_type,f.tell())
          return RESULT_INVALID_HUNK_FILE
      else:
        # check for valid first hunk type
        if is_first_hunk and not self.is_valid_first_hunk_type(hunk_type):
          self.error_string = "No hunk file: '%s' first hunk type was %d" % (hfile, hunk_type)
          return RESULT_NO_HUNK_FILE

        is_first_hunk = False
        was_end = False
        was_potentail_v37_hunk = False
        was_overlay = False

        hunk = { 'type' : hunk_type, 'hunk_file_offset' : hunk_file_offset }
        self.hunks.append(hunk)
        hunk['type_name'] = hunk_names[hunk_type]
        self.set_mem_flags(hunk, hunk_flags, 30)

        # account for lib
        last_hunk_size = hunk_file_offset - last_file_offset
        if lib_size > 0:
          lib_size -= last_hunk_size
        if lib_size > 0:
          hunk['in_lib'] = True

        # V37 fix?
        if hunk_type == HUNK_DREL32:
          # try to fix automatically...
          if v37_compat == None:
            was_potentail_v37_hunk = True
          # fix was forced
          elif v37_compat:
            hunk_type = HUNK_RELOC32SHORT
            hunk['fixes'] = 'v37'

        # ----- HUNK_HEADER -----
        if hunk_type == HUNK_HEADER:
          result = self.parse_header(f,hunk)
        # ----- HUNK_CODE/HUNK_DATA ------
        elif hunk_type == HUNK_CODE or hunk_type == HUNK_DATA or hunk_type == HUNK_PPC_CODE:
          result = self.parse_code_or_data(f,hunk)
        # ---- HUNK_BSS ----
        elif hunk_type == HUNK_BSS:
          result = self.parse_bss(f,hunk)
        # ----- HUNK_<reloc> -----
        elif hunk_type == HUNK_RELRELOC32 or hunk_type == HUNK_ABSRELOC16 \
          or hunk_type == HUNK_RELRELOC8 or hunk_type == HUNK_RELRELOC16 or hunk_type == HUNK_ABSRELOC32 \
          or hunk_type == HUNK_DREL32 or hunk_type == HUNK_DREL16 or hunk_type == HUNK_DREL8 \
          or hunk_type == HUNK_RELRELOC26:
          result = self.parse_reloc(f,hunk)
          # auto fix v37 bug?
          if hunk_type == HUNK_DREL32 and result != RESULT_OK and v37_compat == None:
            f.seek(0)
            return self.read_file_obj(hfile, f, True)
        # ---- HUNK_<reloc short> -----
        elif hunk_type == HUNK_RELOC32SHORT:
          result = self.parse_reloc_short(f,hunk)
        # ----- HUNK_SYMBOL -----
        elif hunk_type == HUNK_SYMBOL:
          result = self.parse_symbol(f,hunk)
        # ----- HUNK_DEBUG -----
        elif hunk_type == HUNK_DEBUG:
          result = self.parse_debug(f,hunk)
        # ----- HUNK_END -----
        elif hunk_type == HUNK_END:
          was_end = True
          result = RESULT_OK
        # ----- HUNK_OVERLAY -----
        elif hunk_type == HUNK_OVERLAY:
          result = self.parse_overlay(f,hunk)
          was_overlay = True
        # ----- HUNK_BREAK -----
        elif hunk_type == HUNK_BREAK:
          result = RESULT_OK
        # ----- HUNK_LIB -----
        elif hunk_type == HUNK_LIB:
          result,lib_size = self.parse_lib(f,hunk)
          lib_size += 8 # add size of HUNK_LIB itself
        # ----- HUNK_INDEX -----
        elif hunk_type == HUNK_INDEX:
          result = self.parse_index(f,hunk)
        # ----- HUNK_EXT -----
        elif hunk_type == HUNK_EXT:
          result = self.parse_ext(f,hunk)
        # ----- HUNK_UNIT -----
        elif hunk_type == HUNK_UNIT or hunk_type == HUNK_NAME:
          result = self.parse_unit_or_name(f,hunk)
        # ----- oops! unsupported hunk -----
        else:
          self.error_string = "unsupported hunk %d" % (hunk_type)
          return RESULT_UNSUPPORTED_HUNKS

        # a parse error occurred
        if result != RESULT_OK:
          return result

        last_file_offset = hunk_file_offset
    return RESULT_OK

  """Return a list with all the hunk type names that were found
  """
  def get_hunk_summary(self):
    return self.get_struct_summary(self.hunks)

  # ---------- Build Segments from Hunks ----------

  def build_loadseg(self):
    in_header = True
    seek_begin = False
    segment = None
    segment_list = self.segments
    for e in self.hunks:
      hunk_type = e['type']

      # check for end of header
      if in_header and hunk_type in loadseg_valid_begin_hunks:
        in_header = False
        seek_begin = True

      if in_header:
        if hunk_type == HUNK_HEADER:
          # we are in an overlay!
          if self.overlay != None:
            segment_list = []
            self.overlay_segments.append(segment_list)
            self.overlay_headers.append(e)
          else:
            # set load_seg() header
            self.header = e

          # start a new segment
          segment = []

          # setup hunk counter
          hunk_no = e['first_hunk']

        # we allow a debug hunk in header for SAS compatibility
        elif hunk_type == HUNK_DEBUG:
          segment.append(e)
        else:
          self.error_string = "Expected header in loadseg: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False

      elif seek_begin:
        # a new hunk shall begin
        if hunk_type in loadseg_valid_begin_hunks:
          segment = [e]
          segment_list.append(segment)
          seek_header = False
          seek_begin = False
          e['hunk_no'] = hunk_no
          e['alloc_size'] = self.header['hunks'][hunk_no]['size']
          hunk_no += 1
        # add an extra overlay "hunk"
        elif hunk_type == HUNK_OVERLAY:
          # assume hunk to be empty
          if self.overlay != None:
            self.error_string = "Multiple overlay in loadseg: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
            return False
          self.overlay = e
          self.overlay_headers = []
          self.overlay_segments = []
          in_header = True
        # break
        elif hunk_type == HUNK_BREAK:
          # assume hunk to be empty
          in_header = True
        # broken hunk: multiple END or other hunks
        elif hunk_type in [HUNK_END, HUNK_NAME, HUNK_DEBUG]:
          pass
        else:
          self.error_string = "Expected hunk start in loadseg: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False

      else:
        # an extra block in hunk or end is expected
        if hunk_type == HUNK_END:
          seek_begin = True
        # contents of hunk
        elif hunk_type in loadseg_valid_extra_hunks or hunk_type == HUNK_DREL32:
          segment.append(e)
        # broken hunk file without END tag
        elif hunk_type in loadseg_valid_begin_hunks:
          segment = [e]
          segment_list.append(segment)
          seek_header = False
          seek_begin = False
          e['hunk_no'] = hunk_no
          e['alloc_size'] = self.header['hunks'][hunk_no]['size']
          hunk_no += 1
        # unecpected hunk?!
        else:
          self.error_string = "Unexpected hunk extra in loadseg: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False
    return True

  def build_unit(self):
    force_unit = True
    in_hunk = False
    name = None
    segment = None
    unit = None
    self.units = []
    unit_no = 0
    for e in self.hunks:
      hunk_type = e['type']

      # optional unit as first entry
      if hunk_type == HUNK_UNIT:
        unit = {}
        unit['name'] = e['name']
        unit['unit_no'] = unit_no
        unit['segments'] = []
        unit['unit'] = e
        unit_no += 1
        self.units.append(unit)
        force_unit = False
        hunk_no = 0
      elif force_unit:
        self.error_string = "Expected name hunk in unit: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
        return False
      elif not in_hunk:
        # begin a named hunk
        if hunk_type == HUNK_NAME:
            name = e['name']
        # main hunk block
        elif hunk_type in unit_valid_main_hunks:
          segment = [e]
          unit['segments'].append(segment)
          # give main block the NAME
          if name != None:
            e['name'] = name
            name = None
          e['hunk_no'] = hunk_no
          hunk_no += 1
          in_hunk = True
        # broken hunk: ignore multi ENDs
        elif hunk_type == HUNK_END:
          pass
        else:
          self.error_string = "Expected main hunk in unit: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False
      else:
        # a hunk is finished
        if hunk_type == HUNK_END:
          in_hunk = False
        # contents of hunk
        elif hunk_type in unit_valid_extra_hunks:
          segment.append(e)
        # unecpected hunk?!
        else:
          self.error_string = "Unexpected hunk in unit: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False

    return True

  def build_lib(self):
    self.libs = []
    lib_segments = []
    seek_lib = True
    seek_main = False
    for e in self.hunks:
      hunk_type = e['type']

      # seeking for a LIB hunk
      if seek_lib:
        if hunk_type == HUNK_LIB:
          segment_list = []
          lib_segments.append(segment_list)
          seek_lib = False
          seek_main = True
          hunk_no = 0

          # get start address of lib hunk in file
          lib_file_offset = e['lib_file_offset']
        else:
          self.error_string = "Expected lib hunk in lib: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False
      elif seek_main:
        # end of lib? -> index!
        if hunk_type == HUNK_INDEX:
          seek_main = False
          seek_lib = True
          lib_units = []
          if not self.resolve_index_hunks(e, segment_list, lib_units):
            self.error_string = "Error resolving index hunks!"
            return False
          lib = {}
          lib['units'] = lib_units
          lib['lib_no'] = len(self.libs)
          lib['index'] = e
          self.libs.append(lib)
        # start of a hunk
        elif hunk_type in unit_valid_main_hunks:
          segment = [e]
          e['hunk_no'] = hunk_no
          hunk_no += 1
          segment_list.append(segment)
          seek_main = False

          # calc relative lib address
          hunk_lib_offset = e['hunk_file_offset'] - lib_file_offset
          e['hunk_lib_offset'] = hunk_lib_offset
        else:
          self.error_string = "Expected main hunk in lib: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False
      else:
        # end hunk
        if hunk_type == HUNK_END:
          seek_main = True
        # extra contents
        elif hunk_type in unit_valid_extra_hunks:
          segment.append(e)
        else:
          self.error_string = "Unexpected hunk in lib: %s %d/%x" % (e['type_name'], hunk_type, hunk_type)
          return False

    return True

  """Resolve hunks referenced in the index"""
  def resolve_index_hunks(self, index, segment_list, lib_units):
    units = index['units']
    no = 0
    for unit in units:
      lib_unit = {}
      unit_segments = []
      lib_unit['segments'] = unit_segments
      lib_unit['name'] = unit['name']
      lib_unit['unit_no'] = no
      lib_unit['index_unit'] = unit
      lib_units.append(lib_unit)
      no += 1

      # try to find segment with start offset
      hunk_offset = unit['hunk_begin_offset']
      found = False
      for segment in segment_list:
        hunk_no = segment[0]['hunk_no']
        lib_off = segment[0]['hunk_lib_offset'] / 4 # is in longwords
        if lib_off == hunk_offset:
          # found segment
          num_segs = len(unit['hunk_infos'])
          for i in xrange(num_segs):
            info = unit['hunk_infos'][i]
            seg = segment_list[hunk_no+i]
            unit_segments.append(seg)
            # renumber hunk
            seg[0]['hunk_no'] = i
            seg[0]['name'] = info['name']
            seg[0]['index_hunk'] = info
          found = True

      if not found:
        return False
    return True

  """From the hunk list build a set of segments that form the actual binary"""
  def build_segments(self):
    self.segments = []
    if len(self.hunks) == 0:
      self.type = TYPE_UNKNOWN
      return False

    # determine type of file from first hunk
    first_hunk_type = self.hunks[0]['type']
    if first_hunk_type == HUNK_HEADER:
      self.type = TYPE_LOADSEG
      return self.build_loadseg()
    elif first_hunk_type == HUNK_UNIT:
      self.type = TYPE_UNIT
      return self.build_unit()
    elif first_hunk_type == HUNK_LIB:
      self.type = TYPE_LIB
      return self.build_lib()
    else:
      self.type = TYPE_UNKNOWN
      return False

  """Return a summary of the created segment structure"""
  def get_segment_summary(self):
    return self.get_struct_summary(self.segments)

  def get_overlay_segment_summary(self):
    if self.overlay_segments != None:
      return self.get_struct_summary(self.overlay_segments)
    else:
      return None

  def get_libs_summary(self):
    if self.libs != None:
      return self.get_struct_summary(self.libs)
    else:
      return None

  def get_units_summary(self):
    if self.units != None:
      return self.get_struct_summary(self.units)
    else:
      return None
