text: #!/usr/bin/env python3
  
  # Copyright (c) Facebook, Inc. and its affiliates.
  #
  # This source code is licensed under the MIT license found in the
  # LICENSE file in the root directory of this source tree.
  
  
  import bisect
  import copy
  import enum
  import numbers
  import operator
  import optparse
  import os
  import re
  import string
  import sys
  from io import BytesIO
  
  import file_extract
  from file_extract import AutoParser
  
  
  def get_uleb128_byte_size(value):
      byte_size = 1
      while value >= 0x80:
          byte_size += 1
          value >>= 7
      return byte_size
  
  
  def get_uleb128p1_byte_size(value):
      return get_uleb128_byte_size(value + 1)
  
  
  # ----------------------------------------------------------------------
  # Constants
  # ----------------------------------------------------------------------
  MAGIC = b"dex\n"
  ENDIAN_CONSTANT = 0x12345678
  REVERSE_ENDIAN_CONSTANT = 0x78563412
  NO_INDEX = 0xFFFFFFFF
  INT4_MIN = -8
  INT4_MAX = 7
  INT8_MIN = -128
  INT8_MAX = 127
  INT16_MIN = -32768
  INT16_MAX = 32767
  INT24_MIN = -8388608
  INT24_MAX = 8388607
  INT32_MIN = -2147483648
  INT32_MAX = 2147483647
  UINT4_MAX = 15
  UINT8_MAX = 255
  UINT16_MAX = 65535
  UINT32_MAX = 4294967295
  
  
  class AccessFlags(enum.IntFlag):
      PUBLIC = 0x1
      PRIVATE = 0x2
      PROTECTED = 0x4
      STATIC = 0x8
      FINAL = 0x10
      SYNCHRONIZED = 0x20
      VOLATILE = 0x40
      BRIDGE = 0x40
      TRANSIENT = 0x80
      VARARGS = 0x80
      NATIVE = 0x100
      INTERFACE = 0x200
      ABSTRACT = 0x400
      STRICT = 0x800
      SYNTHETIC = 0x1000
      ANNOTATION = 0x2000
      ENUM = 0x4000
      CONSTRUCTOR = 0x10000
      DECLARED_SYNCHRONIZED = 0x20000
  
      @classmethod
      def _missing_(cls, value):
          if isinstance(value, file_extract.FileExtract):
              return cls(value.get_uint8())
          else:
              return super()._missing_(value)
  
      def __str__(self):
          return ", ".join(flag.name for flag in AccessFlags if flag & self.value)
  
  
  class ValueFormat(enum.Enum):
      BYTE = 0x00
      SHORT = 0x02
      CHAR = 0x03
      INT = 0x04
      LONG = 0x06
      FLOAT = 0x10
      DOUBLE = 0x11
      METHOD_TYPE = 0x15
      METHOD_HANDLE = 0x16
      STRING = 0x17
      TYPE = 0x18
      FIELD = 0x19
      METHOD = 0x1A
      ENUM = 0x1B
      ARRAY = 0x1C
      ANNOTATION = 0x1D
      NULL = 0x1E
      BOOLEAN = 0x1F
  
      @classmethod
      def _missing_(cls, value):
          if isinstance(value, file_extract.FileExtract):
              return cls(value.get_uint16())
          else:
              return super()._missing_(value)
  
  
  class TypeCode(enum.Enum):
      HEADER_ITEM = 0x0000
      STRING_ID_ITEM = 0x0001
      TYPE_ID_ITEM = 0x0002
      PROTO_ID_ITEM = 0x0003
      FIELD_ID_ITEM = 0x0004
      METHOD_ID_ITEM = 0x0005
      CLASS_DEF_ITEM = 0x0006
      CALL_SITE_ID_ITEM = 0x0007
      METHOD_HANDLE_ITEM = 0x0008
      MAP_LIST = 0x1000
      TYPE_LIST = 0x1001
      ANNOTATION_SET_REF_LIST = 0x1002
      ANNOTATION_SET_ITEM = 0x1003
      CLASS_DATA_ITEM = 0x2000
      CODE_ITEM = 0x2001
      STRING_DATA_ITEM = 0x2002
      DEBUG_INFO_ITEM = 0x2003
      ANNOTATION_ITEM = 0x2004
      ENCODED_ARRAY_ITEM = 0x2005
      ANNOTATIONS_DIRECTORY_ITEM = 0x2006
  
      @classmethod
      def _missing_(cls, value):
          if isinstance(value, file_extract.FileExtract):
              return cls(value.get_uint16())
          else:
              return super()._missing_(value)
  
      def dump(self, prefix=None, f=sys.stdout, print_name=True, parent_path=None):
          f.write(self.name)
  
      @staticmethod
      def max_width():
          return max(len(bin(flag.value)) for flag in TypeCode)
  
  
  class MethodHandleTypeCode(enum.Enum):
      STATIC_PUT = 0x00
      STATIC_GET = 0x01
      INSTANCE_PUT = 0x02
      INSTANCE_GET = 0x03
      INVOKE_STATIC = 0x04
      INVOKE_INSTANCE = 0x05
  
      @classmethod
      def _missing_(cls, value):
          if isinstance(value, file_extract.FileExtract):
              return cls(value.get_uint16())
          else:
              return super()._missing_(value)
  
  
  PRINTABLE = string.ascii_letters + string.digits + string.punctuation + " "
  
  
  def escape(c):
      global PRINTABLE
      if c in PRINTABLE:
          return c
      c = ord(c)
      if c <= 0xFF:
          return "\\x" + "%02.2x" % (c)
      elif c <= "\uffff":
          return "\\u" + "%04.4x" % (c)
      else:
          return "\\U" + "%08.8x" % (c)
  
  
  def print_string(s, f):
      f.write('"')
      f.write("".join(escape(c) for c in s))
      f.write('"')
  
  
  def print_version(version, f):
      if len(version) == 3:
          f.write("%u.%u.%u\n" % (version[0], version[1], version[2]))
  
  
  def print_hex_bytes(data, f):
      for byte in data:
          f.write("%2.2x" % (byte))
      f.write("\n")
  
  
  def print_endian(value, f):
      f.write("%#8.8x" % (value))
      if value == ENDIAN_CONSTANT:
          f.write(" (ENDIAN_CONSTANT)")
      elif value == REVERSE_ENDIAN_CONSTANT:
          f.write(" (REVERSE_ENDIAN_CONSTANT)")
      f.write("\n")
  
  
  def is_zero(value):
      if value == 0:
          return None
      return "value should be zero, bit is %s" % (str(value))
  
  
  def is_dex_magic(magic):
      if magic == MAGIC:
          return None
      return "value should be %s but is %s" % (MAGIC, magic)
  
  
  def hex_escape(s):
      return "".join(escape(c) for c in s)
  
  
  # ----------------------------------------------------------------------
  # encoded_field
  # ----------------------------------------------------------------------
  class encoded_field(AutoParser):
      items = [
          {"type": "uleb", "name": "field_idx", "format": "%u"},
          {"type": "uleb", "name": "access_flags", "format": "0x%8.8x"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      @classmethod
      def fixup_indexes(cls, items):
          for i in range(1, len(items)):
              items[i].field_idx += items[i - 1].field_idx
  
      @classmethod
      def get_table_header(self):
          return "FIELD FLAGS\n"
  
      def get_dump_flat(self):
          return True
  
      def get_access_flags(self):
          return AccessFlags(self.access_flags)
  
  
  # ----------------------------------------------------------------------
  # encoded_method
  # ----------------------------------------------------------------------
  
  
  class encoded_method(AutoParser):
      items = [
          {"type": "uleb", "name": "method_idx", "format": "%u"},
          {"type": "uleb", "name": "access_flags", "format": "0x%8.8x"},
          {"type": "uleb", "name": "code_off", "format": "0x%8.8x"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      @classmethod
      def fixup_indexes(cls, items):
          for i in range(1, len(items)):
              items[i].method_idx += items[i - 1].method_idx
  
      @classmethod
      def get_table_header(self):
          return "METHOD FLAGS\n"
  
      def get_dump_flat(self):
          return True
  
      def get_access_flags(self):
          return AccessFlags(self.access_flags)
  
  
  # ----------------------------------------------------------------------
  # class_data_item
  # ----------------------------------------------------------------------
  
  
  class class_data_item(AutoParser):
      items = [
          {"type": "uleb", "name": "static_fields_size"},
          {"type": "uleb", "name": "instance_fields_size"},
          {"type": "uleb", "name": "direct_methods_size"},
          {"type": "uleb", "name": "virtual_methods_size"},
          {
              "class": encoded_field,
              "name": "static_fields",
              "attr_count": "static_fields_size",
              "flat": True,
          },
          {
              "class": encoded_field,
              "name": "instance_fields",
              "attr_count": "instance_fields_size",
              "flat": True,
          },
          {
              "class": encoded_method,
              "name": "direct_methods",
              "attr_count": "direct_methods_size",
              "flat": True,
          },
          {
              "class": encoded_method,
              "name": "virtual_methods",
              "attr_count": "virtual_methods_size",
              "flat": True,
          },
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
          encoded_field.fixup_indexes(self.static_fields)
          encoded_field.fixup_indexes(self.instance_fields)
          encoded_method.fixup_indexes(self.direct_methods)
          encoded_method.fixup_indexes(self.virtual_methods)
  
      @classmethod
      def create_empty(cls):
          data = file_extract.FileExtract(BytesIO(b"\0\0\0\0"), "=")
          return class_data_item(data)
  
  
  # ----------------------------------------------------------------------
  # class_def_item
  # ----------------------------------------------------------------------
  
  
  class class_def_item(AutoParser):
      items = [
          {"type": "u32", "name": "class_idx", "align": 4},
          {"type": "u32", "name": "access_flags"},
          {"type": "u32", "name": "superclass_idx"},
          {"type": "u32", "name": "interfaces_off"},
          {"type": "u32", "name": "source_file_idx"},
          {"type": "u32", "name": "annotations_off"},
          {"type": "u32", "name": "class_data_off"},
          {"type": "u32", "name": "static_values_off"},
          {
              "class": class_data_item,
              "name": "class_data",
              "attr_offset": "class_data_off",
              "condition": lambda item, data: item.class_data_off != 0,
              "dump": False,
              "default": class_data_item.create_empty(),
          },
      ]
  
      def __init__(self, data, context):
          AutoParser.__init__(self, self.items, data, context)
          self.data = data
          self.interface_ids = None
  
      @classmethod
      def get_table_header(self):
          return (
              "CLASS      ACCESS     SUPERCLASS INTERFACES SOURCE"
              "     ANNOTATION CLASS_DATA STATIC_VALUES\n"
          )
  
      def get_dump_flat(self):
          return True
  
      def find_encoded_method_by_code_off(self, code_off):
          for encoded_method in self.class_data.direct_methods:
              if encoded_method.code_off == code_off:
                  return encoded_method
          for encoded_method in self.class_data.virtual_methods:
              if encoded_method.code_off == code_off:
                  return encoded_method
          return None
  
      def get_interface_ids(self):
          if self.interface_ids is not None:
              return self.interface_ids
          elif self.interfaces_off > 0:
              self.data.push_offset_and_seek(self.interfaces_off)
              self.interface_ids = type_list(self.data).list
              self.data.pop_offset_and_seek()
          else:
              self.interface_ids = []
          return self.interface_ids
  
      def get_access_flags(self):
          return AccessFlags(self.access_flags)
  
  
  # ----------------------------------------------------------------------
  # try_item
  # ----------------------------------------------------------------------
  
  
  class try_item(AutoParser):
      items = [
          {"type": "u32", "name": "start_addr"},
          {"type": "u16", "name": "insn_count"},
          {"type": "u16", "name": "handler_off"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # encoded_type_addr_pair
  # ----------------------------------------------------------------------
  class encoded_type_addr_pair(AutoParser):
      items = [
          {"type": "uleb", "name": "type_idx", "format": "%#8.8x"},
          {"type": "uleb", "name": "addr", "format": "%#8.8x"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # encoded_catch_handler
  # ----------------------------------------------------------------------
  
  
  class encoded_catch_handler(AutoParser):
      items = [
          {"type": "sleb", "name": "size"},
          {
              "class": encoded_type_addr_pair,
              "name": "handlers",
              "attr_count": "size",
              "attr_count_fixup": abs,
          },
          {
              "type": "uleb",
              "name": "catch_all_addr",
              "default": 0,
              "condition": lambda item, data: item.size <= 0,
          },
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # encoded_catch_handler_list
  # ----------------------------------------------------------------------
  
  
  class encoded_catch_handler_list(AutoParser):
      items = [
          {"type": "uleb", "name": "size"},
          {"class": encoded_catch_handler, "name": "list", "attr_count": "size"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_dump_flat(self):
          return True
  
  
  def print_instructions(insns, prefix, flat, f, context=None):
      f.write("\n")
      code_units = CodeUnits(insns)
      dex_inst = DexInstruction()
      while code_units.index_is_valid():
          dex_inst.decode(code_units)
          if prefix:
              f.write(prefix)
          f.write("    ")
          dex_inst.dump(context=context)
  
  
  class DBG(enum.IntEnum):
      END_SEQUENCE = 0x00
      ADVANCE_PC = 0x01
      ADVANCE_LINE = 0x02
      START_LOCAL = 0x03
      START_LOCAL_EXTENDED = 0x04
      END_LOCAL = 0x05
      RESTART_LOCAL = 0x06
      SET_PROLOGUE_END = 0x07
      SET_EPILOGUE_BEGIN = 0x08
      SET_FILE = 0x09
  
      @staticmethod
      def is_special_opcode(value):
          return value >= 0x0A and value <= 0xFF
  
      @classmethod
      def _missing_(cls, value):
          if isinstance(value, file_extract.FileExtract):
              return cls(value.get_uint8())
          elif DBG.is_special_opcode(value):
              return value
          else:
              return super()._missing_(value)
  
      def dump(self, prefix=None, f=sys.stdout, print_name=True, parent_path=None):
          f.write(self.name)
  
  
  class debug_info_op(AutoParser):
      items = [
          {"class": DBG, "name": "op"},
          {
              "switch": "op",
              "cases": {
                  DBG.ADVANCE_PC: [{"type": "uleb", "name": "addr_offset"}],
                  DBG.ADVANCE_LINE: [{"type": "sleb", "name": "line_offset"}],
                  DBG.START_LOCAL: [
                      {"type": "uleb", "name": "register_num"},
                      {"type": "ulebp1", "name": "name_idx"},
                      {"type": "ulebp1", "name": "type_idx"},
                  ],
                  DBG.START_LOCAL_EXTENDED: [
                      {"type": "uleb", "name": "register_num"},
                      {"type": "ulebp1", "name": "name_idx"},
                      {"type": "ulebp1", "name": "type_idx"},
                      {"type": "ulebp1", "name": "sig_idx"},
                  ],
                  DBG.END_LOCAL: [{"type": "uleb", "name": "register_num"}],
                  DBG.RESTART_LOCAL: [{"type": "uleb", "name": "register_num"}],
                  DBG.SET_FILE: [{"type": "ulebp1", "name": "name_idx"}],
                  "default": [],
              },
          },
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
          if DBG.is_special_opcode(self.op):
              line_base = -4
              line_range = 15
              adjusted_opcode = self.op - 0x0A
              self.line_offset = line_base + (adjusted_opcode % line_range)
              self.addr_offset = int(adjusted_opcode / line_range)
          self.byte_size = data.tell() - self.get_offset()
  
      def get_dump_flat(self):
          return True
  
      def get_byte_size(self):
          return self.byte_size
  
      def dump_opcode(self, f=sys.stdout):
          if isinstance(self.op, DBG):
              f.write(self.op.name)
          else:
              assert DBG.is_special_opcode(self.op)
              f.write(f"SPECIAL_OPCODE({self.op})")
          if self.op == DBG.ADVANCE_PC:
              f.write("(%u)" % self.addr_offset)
          elif self.op == DBG.ADVANCE_LINE:
              f.write("(%u)" % self.line_offset)
          elif self.op == DBG.START_LOCAL:
              f.write("(register_num=%u, name_idx=" % self.register_num)
              if self.name_idx < 0:
                  f.write("NO_INDEX")
              else:
                  f.write("%u" % (self.name_idx))
              f.write(", type_idx=")
              if self.type_idx < 0:
                  f.write("NO_INDEX)")
              else:
                  f.write("%u)" % (self.type_idx))
          elif self.op == DBG.START_LOCAL_EXTENDED:
              f.write("(register_num=%u, name_idx=" % self.register_num)
              if self.name_idx < 0:
                  f.write("NO_INDEX")
              else:
                  f.write("%u" % (self.name_idx))
              f.write(", type_idx=")
              if self.type_idx < 0:
                  f.write("NO_INDEX")
              else:
                  f.write("%u" % (self.type_idx))
              f.write(", sig_idx=")
              if self.type_idx < 0:
                  f.write("NO_INDEX)")
              else:
                  f.write("%u)" % (self.type_idx))
          elif self.op == DBG.END_LOCAL or self.op == DBG.RESTART_LOCAL:
              f.write("(register_num=%u)" % self.register_num)
          elif self.op == DBG.SET_FILE:
              f.write("(name_idx=%u)" % self.name_idx)
          elif DBG.is_special_opcode(self.op):
              f.write(
                  " (addr_offset=%u, line_offset=%i)"
                  % (self.addr_offset, self.line_offset)
              )
  
  
  class debug_info_item(AutoParser):
      items = [
          {"type": "uleb", "name": "line_start"},
          {"type": "uleb", "name": "parameters_size"},
          {"type": "ulebp1", "name": "parameter_names", "attr_count": "parameters_size"},
      ]
  
      class row(object):
          def __init__(self):
              self.address = 0
              self.line = 0
              self.source_file = -1
              self.prologue_end = False
              self.epilogue_begin = False
  
          def dump(self, f=sys.stdout):
              f.write("0x%4.4x %5u %5u " % (self.address, self.line, self.source_file))
              if self.prologue_end or self.epilogue_begin:
                  if self.prologue_end:
                      f.write("P ")
                  else:
                      f.write("  ")
                  if self.epilogue_begin:
                      f.write("E")
              f.write("\n")
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
          self.data = data
          self.ops = None
          self.line_table = None
          self.debug_info_offset = data.tell()
  
      def check_encoding(self, dex_method, f=sys.stdout):
          bytes_saved = 0
          ops = self.get_ops()
          if len(ops) == 1:
              op = ops[0]
              if op.op == DBG.END_SEQUENCE:
                  bytes_saved += get_uleb128_byte_size(
                      self.line_start
                  ) + get_uleb128p1_byte_size(self.parameters_size)
  
                  for parameter_name in self.parameter_names:
                      bytes_saved += get_uleb128p1_byte_size(parameter_name)
                  bytes_saved += 1
                  f.write(
                      "warning: %s debug info contains only a single "
                      % (dex_method.get_qualified_name())
                  )
                  f.write("%s, all debug info can be removed " % (op.op))
                  f.write("(%u bytes)\n" % (bytes_saved))
                  return bytes_saved
          # Dex files built for release don't need any the following
          # debug info ops
          for op in ops:
              size = op.get_byte_size()
              if op.op == DBG.SET_PROLOGUE_END:
                  f.write(
                      "warning: %s %s can be removed (%u byte)\n"
                      % (dex_method.get_qualified_name(), op.op, size)
                  )
                  bytes_saved += size
              elif op.op == DBG.SET_EPILOGUE_BEGIN:
                  f.write(
                      "warning: %s %s can be removed (%u byte)\n"
                      % (dex_method.get_qualified_name(), op.op, size)
                  )
                  bytes_saved += size
              elif op.op == DBG.START_LOCAL:
                  f.write(
                      "warning: %s %s can be removed (%u bytes)\n"
                      % (dex_method.get_qualified_name(), op.op, size)
                  )
                  bytes_saved += size
              elif op.op == DBG.START_LOCAL_EXTENDED:
                  f.write(
                      "warning: %s %s can be removed (%u bytes)\n"
                      % (dex_method.get_qualified_name(), op.op, size)
                  )
                  bytes_saved += size
              elif op.op == DBG.END_LOCAL:
                  f.write(
                      "warning: %s %s can be removed (%u bytes)\n"
                      % (dex_method.get_qualified_name(), op.op, size)
                  )
                  bytes_saved += size
              elif op.op == DBG.RESTART_LOCAL:
                  f.write(
                      "warning: %s %s can be removed (%u bytes)\n"
                      % (dex_method.get_qualified_name(), op.op, size)
                  )
                  bytes_saved += size
          return bytes_saved
  
      def get_line_table(self):
          if self.line_table is None:
              line_table = []
              ops = self.get_ops()
              row = debug_info_item.row()
              for op_args in ops:
                  op = op_args.op
                  if op == DBG.END_SEQUENCE:
                      break
                  if op == DBG.ADVANCE_PC:
                      row.address += op_args.addr_offset
                  elif op == DBG.ADVANCE_LINE:
                      row.line += op_args.line_offset
                  elif op == DBG.START_LOCAL:
                      pass
                  elif op == DBG.START_LOCAL_EXTENDED:
                      pass
                  elif op == DBG.END_LOCAL:
                      pass
                  elif op == DBG.RESTART_LOCAL:
                      pass
                  elif op == DBG.SET_PROLOGUE_END:
                      row.prologue_end = True
                  elif op == DBG.SET_EPILOGUE_BEGIN:
                      row.epilogue_begin = True
                  elif op == DBG.SET_FILE:
                      row.source_file = op_args.name_idx
                  else:
                      row.line += op_args.line_offset
                      row.address += op_args.addr_offset
                      line_table.append(copy.copy(row))
                      row.prologue_end = False
                      row.epilogue_begin = False
              self.line_table = line_table
          return self.line_table
  
      def get_ops(self, reset_offset=True):
          if self.ops is None:
              data = self.data
              if reset_offset:
                  data.push_offset_and_seek(self.debug_info_offset)
              else:
                  data.seek(self.debug_info_offset)
              self.ops = []
              while True:
                  op = debug_info_op(data)
                  self.ops.append(op)
                  if op.op == DBG.END_SEQUENCE:
                      break
              if reset_offset:
                  data.pop_offset_and_seek()
          return self.ops
  
      def dump_debug_info(self, f=sys.stdout, prefix=None, reset_offset=True):
          ops = self.get_ops(reset_offset=reset_offset)
          if prefix:
              f.write(prefix)
          f.write("    ")
          f.write(
              "line_start={}({}) param_size={}({}) param_name=[{}]\n".format(
                  self.line_start,
                  get_uleb128_byte_size(self.line_start),
                  self.parameters_size,
                  get_uleb128_byte_size(self.parameters_size),
                  ", ".join(map(lambda x: str(x), self.parameter_names)),
              )
          )
          for op in ops:
              if prefix:
                  f.write(prefix)
              f.write("    ")
              op.dump_opcode(f=f)
              f.write("\n")
  
  
  # ----------------------------------------------------------------------
  # code_item
  # ----------------------------------------------------------------------
  class code_item(AutoParser):
      items = [
          {"type": "u16", "name": "registers_size", "align": 4},
          {"type": "u16", "name": "ins_size"},
          {"type": "u16", "name": "outs_size"},
          {"type": "u16", "name": "tries_size"},
          {"type": "u32", "name": "debug_info_off"},
          {"type": "u32", "name": "insns_size", "format": "%u"},
          {
              "type": "u16",
              "name": "insns",
              "attr_count": "insns_size",
              "dump_list": print_instructions,
          },
          {
              "type": "u16",
              "condition": lambda item, data: item.tries_size != 0
              and item.insns_size & 1,
          },
          {
              "class": try_item,
              "name": "tries",
              "attr_count": "tries_size",
              "condition": lambda item, data: item.tries_size != 0,
              "default": None,
          },
          {
              "class": encoded_catch_handler_list,
              "name": "handlers",
              "condition": lambda item, data: item.tries_size != 0,
              "default": None,
          },
      ]
  
      def __init__(self, data, context):
          AutoParser.__init__(self, self.items, data, context)
          self.debug_info = None
          self.data = data
          # Convert insns from a list to a tuple to avoid mutation and also to
          # allow self.insns to be hashed.
          self.insns = tuple(self.insns)
  
      def get_debug_info(self):
          if self.debug_info is None and self.debug_info_off > 0:
              data = self.data
              data.push_offset_and_seek(self.debug_info_off)
              self.debug_info = debug_info_item(data)
              data.pop_offset_and_seek()
          return self.debug_info
  
      def dump(self, f, prefix, verbose=False):
          if verbose:
              AutoParser.dump(self, f=f, prefix=prefix)
          else:
              print_instructions(self.insns, prefix, False, f, self.context)
  
  
  class encoded_value:
      def __init__(self, data):
          arg_type = data.get_uint8()
          value_arg = arg_type >> 5
          self.value_type = ValueFormat(arg_type & 0x1F)
          self.value = None
          size = value_arg + 1
          if self.value_type == ValueFormat.BYTE:
              if value_arg != 0:
                  raise ValueError("VALUE_BYTE value_arg != 0 (%u)" % (value_arg))
              self.value = data.get_sint8()
          elif self.value_type == ValueFormat.SHORT:
              self.value = data.get_sint_size(size)
          elif self.value_type == ValueFormat.CHAR:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.INT:
              self.value = data.get_sint_size(size)
          elif self.value_type == ValueFormat.LONG:
              self.value = data.get_sint_size(size)
          elif self.value_type == ValueFormat.FLOAT:
              raise ValueError("VALUE_FLOAT not supported yet")
          elif self.value_type == ValueFormat.DOUBLE:
              raise ValueError("VALUE_DOUBLE not supported yet")
          elif self.value_type == ValueFormat.METHOD_TYPE:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.METHOD_HANDLE:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.STRING:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.TYPE:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.FIELD:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.METHOD:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.ENUM:
              self.value = data.get_uint_size(size)
          elif self.value_type == ValueFormat.ARRAY:
              if value_arg != 0:
                  raise ValueError("VALUE_ARRAY value_arg != 0 (%u)" % (value_arg))
              raise ValueError("VALUE_ARRAY not supported yet")
              # encoded_array: an array of values, in the format specified by
              # "encoded_array format". The size of the value is implicit in
              # the encoding.
          elif self.value_type == ValueFormat.ANNOTATION:
              if value_arg != 0:
                  raise ValueError("VALUE_ANNOTATION value_arg != 0 (%u)" % (value_arg))
              # encoded_annotation: a sub-annotation, in the format specified by
              # "encoded_annotation format" below. The size of the value is
              # implicit in the encoding.
          elif self.value_type == ValueFormat.NULL:
              if value_arg != 0:
                  raise ValueError("VALUE_ARRAY value_arg != 0 (%u)" % (value_arg))
              self.value = 0
          elif self.value_type == ValueFormat.BOOLEAN:
              if size == 0:
                  self.value = False
              else:
                  self.value = data.get_uint8() != 0
  
  
  # ----------------------------------------------------------------------
  # encoded_array
  # ----------------------------------------------------------------------
  
  
  class encoded_array(AutoParser):
      items = [
          {"type": "uleb", "name": "size"},
          {"class": encoded_value, "name": "values", "attr_count": "size"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
  
  class encoded_array_item(AutoParser):
      items = [{"class": encoded_array, "name": "value"}]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
  
  # ----------------------------------------------------------------------
  # field_id_item
  # ----------------------------------------------------------------------
  
  
  class field_id_item(AutoParser):
      items = [
          {"type": "u16", "name": "class_idx", "align": 4},
          {"type": "u16", "name": "type_idx"},
          {"type": "u32", "name": "name_idx"},
      ]
  
      def __init__(self, data, context):
          AutoParser.__init__(self, self.items, data, context)
  
      @classmethod
      def get_table_header(self):
          return "CLASS  TYPE   NAME\n"
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # header_item
  # ----------------------------------------------------------------------
  
  
  class header_item(AutoParser):
      items = [
          {"type": "cstr[4]", "name": "magic", "validate": is_dex_magic},
          {"type": "u8[3]", "name": "version", "dump": print_version},
          {"type": "u8", "validate": is_zero},  # NULL byte
          {"type": "u32", "name": "checksum"},
          {"type": "u8[20]", "name": "signature", "dump": print_hex_bytes},
          {"type": "u32", "name": "file_size"},
          {"type": "u32", "name": "header_size"},
          {"type": "u32", "name": "endian_tag", "type": "u32", "dump": print_endian},
          {"type": "u32", "name": "link_size"},
          {"type": "u32", "name": "link_off"},
          {"type": "u32", "name": "map_off"},
          {"type": "u32", "name": "string_ids_size"},
          {"type": "u32", "name": "string_ids_off"},
          {"type": "u32", "name": "type_ids_size"},
          {"type": "u32", "name": "type_ids_off"},
          {"type": "u32", "name": "proto_ids_size"},
          {"type": "u32", "name": "proto_ids_off"},
          {"type": "u32", "name": "field_ids_size"},
          {"type": "u32", "name": "field_ids_off"},
          {"type": "u32", "name": "method_ids_size"},
          {"type": "u32", "name": "method_ids_off"},
          {"type": "u32", "name": "class_defs_size"},
          {"type": "u32", "name": "class_defs_off"},
          {"type": "u32", "name": "data_size"},
          {"type": "u32", "name": "data_off"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_dump_header(self):
          return "DEX header:"
  
  
  # ----------------------------------------------------------------------
  # map_item
  # ----------------------------------------------------------------------
  
  
  class map_item(AutoParser):
      items = [
          {"class": TypeCode, "name": "type", "dump_width": TypeCode.max_width()},
          {"type": "u16"},
          {"type": "u32", "name": "size"},
          {"type": "u32", "name": "offset"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_list_header_lines(self):
          return ["      TYPE                            SIZE       OFFSET\n"]
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # map_list
  # ----------------------------------------------------------------------
  
  
  class map_list(AutoParser):
      items = [
          {"type": "u32", "name": "size", "align": 4, "dump": False},
          {"class": map_item, "name": "list", "attr_count": "size", "flat": True},
      ]
  
      def get_dump_header(self):
          return "map_list:"
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
  
  # ----------------------------------------------------------------------
  # method_handle_item
  # ----------------------------------------------------------------------
  
  
  class method_handle_item(AutoParser):
      items = [
          {"class": MethodHandleTypeCode, "name": "method_handle_type", "align": 4},
          {"type": "u16"},
          {"type": "u16", "name": "field_or_method_id"},
          {"type": "u16"},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
  
  # ----------------------------------------------------------------------
  # method_id_item
  # ----------------------------------------------------------------------
  
  
  class method_id_item(AutoParser):
      items = [
          {"type": "u16", "name": "class_idx", "align": 4},
          {"type": "u16", "name": "proto_idx"},
          {"type": "u32", "name": "name_idx"},
      ]
  
      def __init__(self, data, context):
          AutoParser.__init__(self, self.items, data, context)
  
      @classmethod
      def get_table_header(self):
          return "CLASS  PROTO  NAME\n"
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # proto_id_item
  # ----------------------------------------------------------------------
  
  
  class proto_id_item(AutoParser):
      items = [
          {"type": "u32", "name": "shorty_idx", "align": 4},
          {"type": "u32", "name": "return_type_idx"},
          {"type": "u32", "name": "parameters_off"},
      ]
  
      def __init__(self, data, context):
          AutoParser.__init__(self, self.items, data, context)
          self.parameters = None
  
      def get_dump_flat(self):
          return True
  
      @classmethod
      def get_table_header(self):
          return "SHORTY_IDX RETURN     PARAMETERS\n"
  
      def get_parameters(self):
          if self.parameters_off != 0 and self.parameters is None:
              # Get the data from our dex.File object
              data = self.context.data
              data.push_offset_and_seek(self.parameters_off)
              self.parameters = type_list(data)
              data.pop_offset_and_seek()
          return self.parameters
  
  
  # ----------------------------------------------------------------------
  # string_data_item
  # ----------------------------------------------------------------------
  
  
  class string_data_item(AutoParser):
      items = [
          {"type": "uleb", "name": "utf16_size", "format": "%3u"},
          {"type": "cstr", "name": "data", "dump": print_string},
      ]
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
      def get_dump_flat(self):
          return True
  
  
  # ----------------------------------------------------------------------
  # type_list
  # ----------------------------------------------------------------------
  class type_list(AutoParser):
      items = [
          {"type": "u32", "name": "size", "align": 4},
          {"type": "u16", "name": "list", "attr_count": "size"},
      ]
  
      def get_dump_header(self):
          return "type_list:"
  
      def __init__(self, data):
          AutoParser.__init__(self, self.items, data)
  
  
  class Progard:
      """Parses a proguard map file and does name lookups."""
  
      def __init__(self, path):
          self.path = path
          self.classes_dict = {}
          class_dict = None
          regex = re.compile(r"\s+([0-9]+:[0-9]+:)?(.*) -> (.*)$")
          with open(path, "r") as f:
              for line in f:
                  line = line.rstrip("\n")
                  if line:
                      if line[0].isspace():
                          match = regex.match(line)
                          if match:
                              old = match.group(2)
                              new = match.group(3)
                              # print('other old = "%s"' % (old))
                              # print('other new = "%s"' % (new))
                              class_dict[new] = old
                      else:
                          (old, new) = line.split(" -> ")
                          # print('class old = "%s"' % (old))
                          # print('class new = "%s"' % (new))
                          class_dict = {}
                          self.classes_dict[new] = (old, class_dict)
  
      def lookup_class(self, new_class):
          """Translate a new class name to the old class name."""
          if new_class in self.classes_dict:
              (old_class, class_dict) = self.classes_dict[new_class]
              if old_class is not None:
                  return old_class
          return None
  
      def lookup_method(self, new_class, new_method):
          """Translate a new class name and a new method into the old class
          name and the old method name."""
          if new_class in self.classes_dict:
              (old_class, class_dict) = self.classes_dict[new_class]
              if new_method in class_dict:
                  return class_dict[new_method]
          return None
  
  
  class DexMethod:
      """Encapsulates a method within a DEX file."""
  
      def __init__(self, dex_class, encoded_method, is_virtual):
          self.dex_class = dex_class
          self.encoded_method = encoded_method
          self.method_id = None
          self.is_virtual = is_virtual
          self.code_item = None
          self.insns = None
          self.name_in_file = None
          self.name = None
  
      def get_qualified_name(self):
          class_name = self.get_class().get_name()
          method_name = self.get_name()
          if class_name[-1] != ";":
              return class_name + ":" + method_name
          else:
              return class_name + method_name
  
      def get_method_id(self):
          """Get the method_id_item for this method."""
          if self.method_id is None:
              self.method_id = self.get_dex().get_method_id(self.encoded_method)
          return self.method_id
  
      def get_method_index(self):
          """Get the method index into the method_ids array in the DEX file."""
          return self.encoded_method.method_idx
  
      def get_code_offset(self):
          """Get the code offset for this method."""
          return self.encoded_method.code_off
  
      def get_code_item_index(self):
          """Get the index into the code_items array in the dex file for the
          code for this method, or -1 if there is no code for this method."""
          code_item = self.get_code_item()
          if code_item:
              return self.get_dex().get_code_item_index_from_code_off(
                  code_item.get_offset()
              )
          return -1
  
      def get_dex(self):
          return self.dex_class.get_dex()
  
      def get_name_in_file(self):
          """Returns the name of the method as it is known in the current DEX
          file (no proguard remapping)"""
          if self.name_in_file is None:
              self.name_in_file = self.get_dex().get_string(self.get_method_id().name_idx)
          return self.name_in_file
  
      def get_name(self):
          if self.name is None:
              cls_mangled = self.get_class().get_mangled_name()
              name_in_file = self.get_name_in_file()
              if cls_mangled and name_in_file:
                  self.name = self.get_dex().demangle_class_method_name(
                      cls_mangled, name_in_file
                  )
                  if self.name is None:
                      self.name = name_in_file
          return self.name
  
      def get_internal_pretty_method_str_rep(self):
          # What it looks like boolean com.facebook.common.jit.common.JitDisabledChecker.testCompileMethod(int)
          dex = self.get_dex()
          proto_id = dex.get_proto_id(self.get_method_id().proto_idx)
          if proto_id is None:
              return None
          return_type = dex.get_typename(proto_id.return_type_idx)
          param = "("
          if proto_id.parameters_off != 0:
              # type is type_list
              param_list = proto_id.get_parameters().list
              not_first = False
              for type_idx in param_list:
                  if not_first:
                      param += ", "
                  param += dex.get_typename(type_idx)
          param += ")"
  
          cur_class_name = self.get_class().get_name()
          cur_method_name = self.get_name()
  
          return return_type + " " + cur_class_name + "." + cur_method_name + param
  
      def get_pretty_proto(self):
          dex = self.get_dex()
          proto_id = dex.get_proto_id(self.get_method_id().proto_idx)
          if proto_id is None:
              return None
          return_type = dex.get_raw_typename(proto_id.return_type_idx)
          param = "("
          if proto_id.parameters_off != 0:
              # type is type_list
              param_list = proto_id.get_parameters().list
              for type_idx in param_list:
                  param += dex.get_raw_typename(type_idx)
          param += ")"
          return param + return_type
  
      def get_class(self):
          return self.dex_class
  
      def get_code_item(self):
          if self.code_item is None:
              if self.encoded_method.code_off != 0:
                  self.code_item = self.get_dex().find_code_item(
                      self.encoded_method.code_off
                  )
          return self.code_item
  
      def get_instruction_count(self):
          code_item = self.get_code_item()
          if code_item:
              return len(code_item.insns)
          return 0
  
      def get_code_byte_size(self):
          code_item = self.get_code_item()
          if code_item:
              return len(code_item.insns) * 2
          return 0
  
      def get_instructions(self):
          if self.insns is None:
              self.insns = []
              code_item = self.get_code_item()
              if code_item:
                  code_units = CodeUnits(code_item.insns)
                  while code_units.index_is_valid():
                      insn = DexInstruction()
                      insn.decode(code_units)
                      self.insns.append(insn)
          return self.insns
  
      def is_abstract(self):
          return bool(self.encoded_method.get_access_flags() & AccessFlags.ABSTRACT)
  
      def is_native(self):
          return bool(self.encoded_method.get_access_flags() & AccessFlags.NATIVE)
  
      def is_synthetic(self):
          return bool(self.encoded_method.get_access_flags() & AccessFlags.SYNTHETIC)
  
      def dump(self, options, f=sys.stdout):
          dex = self.get_dex()
          method_id = dex.get_method_id(self.encoded_method.method_idx)
          f.write(
              "method: %s %s.%s:%s (%s)\n"
              % (
                  "virtual" if self.is_virtual else "direct",
                  self.get_class().get_name(),
                  self.get_name(),
                  dex.get_proto_string(method_id.proto_idx) if method_id else "",
                  str(self.encoded_method.get_access_flags()),
              )
          )
          if options.verbose:
              self.encoded_method.dump(f=f, prefix="    encoded_method.", flat=False)
              if method_id:
                  method_id.dump(f=f, prefix="    method_id.", flat=False)
                  proto_id = dex.get_proto_id(method_id.proto_idx)
                  if proto_id:
                      proto_id.dump(f=f, prefix="    proto_id.", flat=False)
              f.write("\n")
          code_item_idx = dex.get_code_item_index_from_code_off(
              self.encoded_method.code_off
          )
          if code_item_idx >= 0:
              code_item = dex.get_code_items()[code_item_idx]
              if options.verbose:
                  f.write(
                      "    code_item[%u] @ %#8.8x:"
                      % (code_item_idx, code_item.get_offset())
                  )
              code_item.dump(f=f, prefix="        ", verbose=options.verbose)
          if options.dump_debug_info:
              self.dump_debug_info(f=f, prefix="    ")
  
      def dump_code(self, f=sys.stdout):
          insns = self.get_instructions()
          for insn in insns:
              insn.dump(f=f)
  
      def get_debug_info(self):
          code_item = self.get_code_item()
          if code_item:
              return code_item.get_debug_info()
          return None
  
      def dump_debug_info(self, f=sys.stdout, prefix=None):
          debug_info = self.get_debug_info()
          if prefix:
              f.write(prefix)
          if debug_info:
              f.write("debug info @ %#8.8x:\n" % (debug_info.get_offset()))
              debug_info.dump_debug_info(f=f, prefix=prefix)
              f.write("\n")
          else:
              f.write("no debug info\n")
  
      def check_debug_info_encoding(self):
          debug_info = self.get_debug_info()
          if debug_info:
              return debug_info.check_encoding(self)
  
      def get_line_number(self):
          debug_info = self.get_debug_info()
          if debug_info:
              return debug_info.line_start
          return 0
  
  
  class DexClass:
      """Encapsulates a class within a DEX file."""
  
      def __init__(self, dex, class_def):
          self.dex = dex
          self.class_def = class_def
          self.methods = None
          self.mangled = None
          self.demangled = None
          self.method_mapping = None
  
      def dump(self, options, f=sys.stdout):
          dex = self.get_dex()
          class_def_offset = self.class_def.get_offset()
          class_def_idx = dex.get_class_def_index_from_offset(class_def_offset)
          class_data = self.class_def.class_data
          f.write("\nclass: %s // @%#8.8x" % (self.get_name(), class_def_offset))
          f.write(
              "\n\tAccess flags: (%s)\n\tSuperclass: %s\n\tInterfaces: (%s)"
              % (
                  str(self.class_def.get_access_flags()),
                  dex.get_typename(self.class_def.superclass_idx),
                  ", ".join(
                      [
                          self.dex.get_typename(interface)
                          for interface in self.class_def.get_interface_ids()
                      ]
                  ),
              )
          )
          field_ids = dex.get_field_ids()
  
          def field_to_string(field):
              field_item = field_ids[field.field_idx]
              return "(%s) %s:%s" % (
                  str(field.get_access_flags()),
                  dex.get_string(field_item.name_idx),
                  dex.get_typename(field_item.type_idx),
              )
  
          f.write(
              "\n\tStatic fields:\n\t\t%s"
              % "\n\t\t".join(
                  [
                      field_to_string(class_data.static_fields[i])
                      for i in range(class_data.static_fields_size)
                  ]
              )
          )
          f.write(
              "\n\tInstance fields:\n\t\t%s\n"
              % "\n\t\t".join(
                  [
                      field_to_string(class_data.instance_fields[i])
                      for i in range(class_data.instance_fields_size)
                  ]
              )
          )
          if options.verbose:
              f.write("    class_def[%u] @ %#8.8x:\n" % (class_def_idx, class_def_offset))
              self.class_def.dump(f=f, flat=False, prefix="        ")
              f.write(
                  "    class_data_item @ %#8.8x:\n"
                  % (self.class_def.class_data.get_offset())
              )
              self.class_def.class_data.dump(f=f, flat=False, prefix="        ")
              f.write("\n")
  
      def get_type_index(self):
          """Get type ID index (class_idx) for this class."""
          return self.class_def.class_idx
  
      def is_abstract(self):
          return bool(self.class_def.get_access_flags() & AccessFlags.ABSTRACT)
  
      def get_mangled_name(self):
          if self.mangled is None:
              dex = self.get_dex()
              self.mangled = dex.get_typename(self.class_def.class_idx)
          return self.mangled
  
      def get_name(self):
          """Get the demangled name for a class if we have a proguard file or
          return the mangled name if we don't have a proguard file."""
          if self.demangled is None:
              mangled = self.get_mangled_name()
              if mangled:
                  self.demangled = self.get_dex().demangle_class_name(mangled)
                  if self.demangled is None:
                      self.demangled = mangled
          return self.demangled
  
      def get_dex(self):
          return self.dex
  
      def get_methods(self):
          if self.methods is None:
              class_data = self.class_def.class_data
              self.methods = sorted(
                  [DexMethod(self, m, False) for m in class_data.direct_methods]
                  + [DexMethod(self, m, True) for m in class_data.virtual_methods],
                  key=lambda method: method.get_line_number(),
              )
          return self.methods
  
      def get_method_mapping(self):
          if self.method_mapping is None:
              self.method_mapping = {}
  
              def insert_method(encoded_method, virtual):
                  method = DexMethod(self, encoded_method, virtual)
                  name = method.get_name_in_file()
                  proto = method.get_pretty_proto()
                  name_mapping = self.method_mapping.setdefault(name, {})
                  if proto in name_mapping:
                      raise Exception(
                          "Unexpected duplicate method found: {}".format(method)
                      )
                  name_mapping[proto] = method
  
              for encoded_method in self.class_def.class_data.direct_methods:
                  insert_method(encoded_method, False)
              for encoded_method in self.class_def.class_data.virtual_methods:
                  insert_method(encoded_method, True)
          return self.method_mapping
  
      def find_method(self, method_name, proto):
          methods = self.get_method_mapping()
          if method_name not in methods:
              return None
          proto_map = methods[method_name]
          return proto_map.get(proto, None)
  
      def get_line_number(self):
          method_line_numbers = [
              method.get_line_number()
              for method in self.get_methods()
              if not method.is_abstract() and method.get_line_number() > 0
          ]
          if len(method_line_numbers) == 0:
              return 0
          return min(method_line_numbers)
  
  
  def demangle_classname(mangled):
      if mangled and len(mangled) > 2 and mangled[0] == "L" and mangled[-1] == ";":
          return mangled[1:-1].replace("/", ".") + ":"
      # Already demangled
      return mangled
  
  
  def mangle_classname(demangled):
      if (
          demangled
          and len(demangled) > 2
          and (demangled[0] != "L" or demangled[-1] != ";")
      ):
          return "L" + demangled.replace(".", "/") + ";"
      # Already demangled
      return demangled
  
  
  class File:
      """Represents a DEX (Dalvik Executable) file"""
  
      def __init__(self, path, proguard_path):
          self.path = path
          self.proguard = None
          if proguard_path and os.path.exists(proguard_path):
              self.proguard = Progard(proguard_path)
          self.data = file_extract.FileExtract(open(self.path, "rb"), "=", 4)
          self.header = header_item(self.data)
          self.map_list = None
          self.string_ids = None
          self.type_ids = None
          self.proto_ids = None
          self.field_ids = None
          self.method_ids = None
          self.class_defs = None
          self.classes = None
          self.call_site_ids = None
          self.method_handle_items = None
          self.code_items = None
          self.code_off_to_code_item_idx = {}
          self.strings = None
          self.call_sites = None
          self.dex_classes = {}
          self.debug_info_items = None
          self.debug_info_items_total_size = None
  
      def demangle_class_name(self, cls_mangled):
          """Given a mangled type name as it would appear in a DEX file like
          "LX/JxK;", return the demangled version if we have a proguard file,
          otherwise return the original class typename"""
          if self.proguard:
              cls_demangled = demangle_classname(cls_mangled)
              if cls_demangled:
                  return self.proguard.lookup_class(cls_demangled)
          return None
  
      def demangle_class_method_name(self, cls_mangled, method_name):
          if self.proguard:
              cls_demangled = demangle_classname(cls_mangled)
              if cls_demangled:
                  return self.proguard.lookup_method(cls_demangled, method_name)
          return None
  
      def get_map_list(self):
          if self.map_list is None:
              self.data.push_offset_and_seek(self.header.map_off)
              self.map_list = map_list(self.data)
              self.data.pop_offset_and_seek()
          return self.map_list
  
      def get_map_tuple(self, type_code):
          map_list = self.get_map_list()
          for item in map_list.list:
              if item.type == type_code:
                  return (item.size, item.offset)
          return (0, 0)
  
      def get_debug_info_items_and_total_size(self):
          if self.debug_info_items is None:
              (size, offset) = self.get_map_tuple(TypeCode.DEBUG_INFO_ITEM)
              if size == 0 or offset == 0:
                  return (None, None)
              self.data.push_offset_and_seek(offset)
              self.debug_info_items = []
              for _ in range(size):
                  item = debug_info_item(self.data)
                  item.get_ops(reset_offset=False)
                  self.debug_info_items.append(item)
              self.debug_info_items_total_size = self.data.tell() - offset
              self.data.pop_offset_and_seek()
          return (self.debug_info_items, self.debug_info_items_total_size)
  
      def find_class(self, class_ref):
          class_idx = class_ref
          if isinstance(class_ref, str):
              # Make sure the string is in 'L' <classname-with-slashes> ';'
              class_mangled = mangle_classname(class_ref)
              class_str_idx = self.find_string_idx(class_mangled)
              if class_str_idx >= 0:
                  class_idx = self.find_type_idx(class_str_idx)
          if isinstance(class_idx, numbers.Integral):
              classes = self.get_classes()
              for cls in classes:
                  if cls.class_def.class_idx == class_idx:
                      return cls
          return None
  
      def find_string_idx(self, match_s):
          strings = self.get_strings()
          for (i, s) in enumerate(strings):
              if match_s == s.data:
                  return i
          return -1
  
      def get_string(self, index):
          strings = self.get_strings()
          if index < len(strings):
              return file_extract.hex_escape(strings[index].data)
          return None
  
      def get_raw_typename(self, type_id):
          types = self.get_type_ids()
          if type_id < len(types):
              return self.get_string(types[type_id])
          return None
  
      def get_typename(self, type_id):
          raw_typename = self.get_raw_typename(type_id)
          if raw_typename is None:
              return None
          array_level = 0
          for c in raw_typename:
              if c == "[":
                  array_level += 1
              else:
                  break
          raw_prefix = raw_typename[array_level]
          raw_base_typename = raw_typename[array_level + 1 :]
          if raw_prefix == "L":
              assert raw_base_typename[-1] == ";"
              return raw_base_typename[:-1].replace("/", ".") + "[]" * array_level
          prefix_to_typename = {
              "V": "void",
              "Z": "boolean",
              "B": "byte",
              "S": "short",
              "C": "char",
              "I": "int",
              "J": "long",
              "F": "float",
              "D": "double",
          }
          return prefix_to_typename[raw_prefix] + "[]" * array_level
  
      def get_string_ids(self):
          if self.string_ids is None:
              self.string_ids = []
              self.data.push_offset_and_seek(self.header.string_ids_off)
              for _ in range(self.header.string_ids_size):
                  self.string_ids.append(self.data.get_uint32())
              self.data.pop_offset_and_seek()
          return self.string_ids
  
      def get_type_ids(self):
          if self.type_ids is None:
              self.type_ids = []
              self.data.push_offset_and_seek(self.header.type_ids_off)
              for _ in range(self.header.type_ids_size):
                  self.type_ids.append(self.data.get_uint32())
              self.data.pop_offset_and_seek()
          return self.type_ids
  
      def get_proto_ids(self):
          if self.proto_ids is None:
              self.proto_ids = []
              self.data.push_offset_and_seek(self.header.proto_ids_off)
              for _ in range(self.header.proto_ids_size):
                  self.proto_ids.append(proto_id_item(self.data, self))
              self.data.pop_offset_and_seek()
          return self.proto_ids
  
      def get_proto_id(self, proto_idx):
          proto_ids = self.get_proto_ids()
          if proto_idx >= 0 and proto_idx < len(proto_ids):
              return proto_ids[proto_idx]
          return None
  
      def get_proto_shorty(self, proto_idx):
          id = self.get_proto_id(proto_idx)
          return self.get_string(id.shorty_idx)
  
      def get_proto_string(self, proto_idx):
          proto = self.get_proto_id(proto_idx)
          return_type = self.get_typename(proto.return_type_idx)
          params = proto.get_parameters()
          return "(%s)%s" % (
              ", ".join(
                  [self.get_typename(type_idx) for type_idx in params.list]
                  if params
                  else []
              ),
              return_type,
          )
  
      def get_field_ids(self):
          if self.field_ids is None:
              self.field_ids = []
              self.data.push_offset_and_seek(self.header.field_ids_off)
              for _ in range(self.header.field_ids_size):
                  self.field_ids.append(field_id_item(self.data, self))
              self.data.pop_offset_and_seek()
          return self.field_ids
  
      def get_method_ids(self):
          if self.method_ids is None:
              self.method_ids = []
              self.data.push_offset_and_seek(self.header.method_ids_off)
              for _ in range(self.header.method_ids_size):
                  self.method_ids.append(method_id_item(self.data, self))
              self.data.pop_offset_and_seek()
          return self.method_ids
  
      def find_method_ids(self, method_name, class_ref=None):
          dex_class = None
          if class_ref is not None:
              dex_class = self.find_class(class_ref)
          matches = []  # Return a list of matching methods
          method_ids = self.get_method_ids()
          if not method_ids:
              return matches
          name_idx = self.find_string_idx(method_name)
          if name_idx <= 0:
              return matches
          for method_id in method_ids:
              if method_id.name_idx == name_idx:
                  if dex_class:
                      if method_id.class_idx != dex_class.class_def.class_idx:
                          continue
                  matches.append(method_id)
          return matches
  
      def find_method_id_by_code_offset(self, code_off):
          class_defs = self.get_class_defs()
          for class_def in class_defs:
              method_id = class_def.find_encoded_method_by_code_off(code_off)
              if method_id:
                  return method_id
          return None
  
      def get_method_id(self, method_ref):
          """method_ref can be one of:
             - a encoded_method object
             - integer method index"""
          method_ids = self.get_method_ids()
          if method_ids:
              if isinstance(method_ref, encoded_method):
                  if method_ref.method_idx < len(method_ids):
                      return method_ids[method_ref.method_idx]
              elif isinstance(method_ref, numbers.Integral):
                  if method_ref < len(method_ids):
                      return method_ids[method_ref]
              else:
                  raise ValueError("invalid method_ref type %s" % (type(method_ref)))
          return None
  
      # def get_call_site(self, idx):
      #     call_site_ids = self.get_call_site_ids()
      #     if idx >= len(call_site_ids):
      #         return None
      #     if self.call_sites[idx] is None:
      #         self.data.push_offset_and_seek(call_site_ids[idx])
      #         self.call_sites[idx] = call_site_item(self.data)
      #         self.data.pop_offset_and_seek()
      #     return self.call_sites[idx]
  
      def get_call_site_ids(self):
          if self.call_site_ids is None:
              self.call_site_ids = []
              self.call_sites = []
              (size, offset) = self.get_map_tuple(TypeCode.CALL_SITE_ID_ITEM)
              self.data.push_offset_and_seek(offset)
              for _ in range(size):
                  self.call_site_ids.append(self.data.get_uint32())
                  self.call_sites.append(None)
              self.data.pop_offset_and_seek()
          return self.call_site_ids
  
      def get_method_handle_items(self):
          if self.method_handle_items is None:
              self.method_handle_items = []
              (size, offset) = self.get_map_tuple(TypeCode.METHOD_HANDLE_ITEM)
              self.data.push_offset_and_seek(offset)
              for _ in range(size):
                  self.method_handle_items.append(method_handle_item(self.data))
              self.data.pop_offset_and_seek()
          return self.method_handle_items
  
      def get_code_items(self):
          if self.code_items is None:
              self.code_items = []
              (size, offset) = self.get_map_tuple(TypeCode.CODE_ITEM)
              self.data.push_offset_and_seek(offset)
              for i in range(size):
                  self.data.align_to(4)
                  item = code_item(self.data, self)
                  self.code_items.append(item)
                  self.code_off_to_code_item_idx[item.get_offset()] = i
              self.data.pop_offset_and_seek()
          return self.code_items
  
      def report_code_duplication(self):
          code_to_code_items = {}
          code_items = self.get_code_items()
          if code_items:
              for code_item in code_items:
                  key = code_item.insns
                  if key in code_to_code_items:
                      code_to_code_items[key].append(code_item)
                  else:
                      code_to_code_items[key] = [code_item]
              for key in code_to_code_items:
                  code_items = code_to_code_items[key]
                  if len(code_items) > 1:
                      print("-" * 72)
                      print("The following methods have the same code:")
                      for code_item in code_items:
                          method = self.find_method_from_code_off(code_item.get_offset())
                          if method.is_virtual:
                              print("virtual", end=" ")
                          else:
                              print("direct", end=" ")
                          print(method.get_qualified_name())
                      # Dump the code once for all methods
                      method.dump_code()
  
      def get_class_def_index_from_offset(self, class_def_offset):
          class_defs = self.get_class_defs()
          for (i, class_def) in enumerate(class_defs):
              if class_def.get_offset() == class_def_offset:
                  return i
          return -1
  
      def get_code_item_index_from_code_off(self, code_off):
          # Make sure the code items are created
          self.get_code_items()
          if code_off in self.code_off_to_code_item_idx:
              return self.code_off_to_code_item_idx[code_off]
          return -1
  
      def find_code_item(self, code_off):
          code_item_idx = self.get_code_item_index_from_code_off(code_off)
          if code_item_idx >= 0:
              return self.get_code_items()[code_item_idx]
          else:
              raise ValueError("invalid code item offset %#8.8x" % code_off)
  
      def find_method_from_code_off(self, code_off):
          if code_off == 0:
              return None
          for cls in self.get_classes():
              for method in cls.get_methods():
                  if method.get_code_offset() == code_off:
                      return method
          return None
  
      def get_class_defs(self):
          if self.class_defs is None:
              self.class_defs = []
              self.data.push_offset_and_seek(self.header.class_defs_off)
              for _ in range(self.header.class_defs_size):
                  class_def = class_def_item(self.data, self)
                  self.class_defs.append(class_def)
              self.data.pop_offset_and_seek()
          return self.class_defs
  
      def get_classes(self):
          if self.classes is None:
              self.classes = sorted(
                  (DexClass(self, class_def) for class_def in self.get_class_defs()),
                  key=lambda cls: cls.get_line_number(),
              )
              self.data.pop_offset_and_seek()
          return self.classes
  
      def get_strings(self):
          if self.strings is None:
              self.strings = []
              for string_id_item in self.get_string_ids():
                  self.data.push_offset_and_seek(string_id_item)
                  self.strings.append(string_data_item(self.data))
              self.data.pop_offset_and_seek()
          return self.strings
  
      def dump_header(self, options, f=sys.stdout):
          self.header.dump(f=f)
          f.write("\n")
  
      def dump_map_list(self, options, f=sys.stdout):
          self.get_map_list().dump(f=f)
          f.write("\n")
  
      def dump_string_ids(self, options, f=sys.stdout):
          string_ids = self.get_string_ids()
          if string_ids:
              f.write("string_ids:\n")
              for (i, item) in enumerate(self.get_strings()):
                  f.write("[%4u] %#8.8x ( " % (i, string_ids[i]))
                  item.dump(f=f)
                  f.write(")\n")
  
      def dump_type_ids(self, options, f=sys.stdout):
          type_ids = self.get_type_ids()
          if type_ids:
              f.write("\ntype_ids:\n      DESCRIPTOR_IDX\n")
              for (i, item) in enumerate(type_ids):
                  f.write('[%4u] %#8.8x ("%s")\n' % (i, item, self.get_string(item)))
  
      def find_type_idx(self, class_str_idx):
          types = self.get_type_ids()
          i = bisect.bisect_left(types, class_str_idx)
          if i != len(types) and types[i] == class_str_idx:
              return i
          return -1
  
      def find_class_def_by_type_index(self, class_idx):
          class_defs = self.get_class_defs()
          for class_def in class_defs:
              if class_def.class_idx == class_idx:
                  return class_def
          return None
  
      def dump_proto_ids(self, options, f=sys.stdout):
          proto_ids = self.get_proto_ids()
          if proto_ids:
              f.write("\nproto_ids:\n")
              f.write(" " * (6 + 1))
              f.write(proto_id_item.get_table_header())
              for (i, item) in enumerate(proto_ids):
                  f.write("[%4u] " % (i))
                  item.dump(f=f, print_name=False)
                  f.write("%s\n" % self.get_proto_string(i))
  
      def dump_field_ids(self, options, f=sys.stdout):
          field_ids = self.get_field_ids()
          if field_ids:
              f.write("\nfield_ids:\n")
              f.write(" " * (6 + 1))
              f.write(field_id_item.get_table_header())
              for (i, item) in enumerate(field_ids):
                  f.write("[%4u] " % (i))
                  item.dump(f=f, print_name=False)
                  f.write(
                      " %s.%s:%s\n"
                      % (
                          self.get_typename(item.class_idx),
                          self.get_string(item.name_idx),
                          self.get_typename(item.type_idx),
                      )
                  )
  
      def dump_class_method_ids(self, options, f=sys.stdout):
          method_ids = self.get_method_ids()
          if not method_ids:
              return
          f.write("\nmethod_ids:\n")
          f.write(" " * (6 + 1))
          f.write(method_id_item.get_table_header())
          for dex_class in self.get_classes():
              f.write("\nclass %s\n" % dex_class.get_name())
              for dex_method in dex_class.get_methods():
                  method_idx = dex_method.encoded_method.method_idx
                  method_item = method_ids[method_idx]
                  f.write("[%4u] " % method_idx)
                  method_item.dump(f=f, print_name=False)
                  f.write(
                      " %s.%s:%s (%s)\n"
                      % (
                          self.get_typename(method_item.class_idx),
                          self.get_string(method_item.name_idx),
                          self.get_proto_string(method_item.proto_idx),
                          str(dex_method.encoded_method.get_access_flags()),
                      )
                  )
  
      def dump_class_defs(self, options, f=sys.stdout):
          class_defs = self.get_class_defs()
          if class_defs:
              f.write("\nclass_defs:\n")
              f.write(" " * (6 + 1))
              f.write(class_def_item.get_table_header())
              for (i, item) in enumerate(class_defs):
                  f.write("[%4u] " % (i))
                  item.dump(f=f, print_name=False)
                  f.write(
                      "   (%s, %s, (%s))"
                      % (
                          self.get_typename(item.class_idx),
                          self.get_typename(item.superclass_idx),
                          str(item.get_access_flags()),
                      )
                  )
                  f.write("\n")
  
      def dump_call_site_ids(self, options, f=sys.stdout):
          call_site_ids = self.get_call_site_ids()
          if call_site_ids:
              f.write("\ncall_site_ids:\n")
              f.write(" " * (6 + 1))
              for (i, item) in enumerate(call_site_ids):
                  f.write("[%4u] %#8.8x\n" % (i, item))
  
      def dump_method_handle_items(self, options, f=sys.stdout):
          method_handle_items = self.get_method_handle_items()
          if method_handle_items:
              f.write("\nmethod_handle_items:\n")
              f.write(" " * (6 + 1))
              for (i, item) in enumerate(method_handle_items):
                  f.write("[%4u] " % (i))
                  item.dump(f=f)
                  f.write("\n")
  
      def dump_code(self, options, f=sys.stdout):
          classes = self.get_classes()
          if classes:
              for cls in classes:
                  if options.skip_abstract and cls.is_abstract():
                      continue
                  cls.dump(options, f=f)
                  methods = cls.get_methods()
                  if options.dump_code or options.dump_debug_info or options.dump_all:
                      for method in methods:
                          method.dump(options, f=f)
                  f.write("\n")
  
      def dump_code_items(self, options, f=sys.stdout):
          code_items = self.get_code_items()
          if code_items:
              for (i, code_item) in enumerate(code_items):
                  f.write("code_item[%u]:\n" % (i))
                  code_item.dump(f=f)
  
      def dump_debug_info_items(self, options, f=sys.stdout):
          (debug_info_items, size) = self.get_debug_info_items_and_total_size()
          if debug_info_items:
              for item in debug_info_items:
                  item.dump_debug_info(f=f)
              f.write("Total TYPE_DEBUG_INFO_ITEM size: {}\n\n".format(size))
  
      def dump(self, options, f=sys.stdout):
          self.dump_header(options, f)
          f.write("\n")
          self.dump_map_list(options, f)
          self.dump_debug_info_items(options, f)
          self.dump_string_ids(options, f)
          self.dump_type_ids(options, f)
          self.dump_proto_ids(options, f)
          self.dump_field_ids(options, f)
          self.dump_method_ids(options, f)
          self.dump_class_defs(options, f)
          self.dump_call_site_ids(options, f)
          self.dump_method_handle_items(options, f)
          self.dump_code(options, f)
          self.dump_code_items(options, f)
  
  
  def sign_extending(value, bit_width):
      # is the highest bit (sign) set? (x>>(b-1)) would be faster
      if value & (1 << (bit_width - 1)):
          return value - (1 << bit_width)  # 2s complement
      return value
  
  
  def get_signed_hex_offset_as_str(signed_offset, width):
      if signed_offset < 0:
          s = "-"
          offset = abs(signed_offset)
      else:
          s = "+"
          offset = signed_offset
      if width == 2:
          s += "%2.2x" % (offset & 0xFF)
      elif width == 4:
          s += "%4.4x" % (offset & 0xFFFF)
      elif width == 8:
          s += "%8.8x" % (offset & 0xFFFFFFFF)
      else:
          raise ValueError("only sizes of 2 4 or 8 are supported")
      return s
  
  
  class Opcode(object):
      def __init__(self, inst):
          self.inst = inst
  
      def check_encoding(self, f=sys.stdout):
          """Verify that this instruction can't be encoded more efficiently"""
          return 0  # Return zero to indicate we can't save any bytes
  
      def new_encoding(self, f=sys.stdout):
          """Look for bytes we can save by making new opcodes that are encoded
          as unsigned, or other optimizations"""
          return 0  # Return zero to indicate we can't save any bytes
  
      def get_op(self):
          return self.inst.get_op()
  
      def get_name(self):
          op = self.get_op()
          return self.ops[op]
  
      def get_num_code_units(self):
          return self.num_code_units
  
      def regs_are_sequential(self):
          if len(self.regs) <= 1:
              return True
          prev_reg = self.regs[0]
          for i in range(1, len(self.regs)):
              curr_reg = self.regs[i]
              if prev_reg + 1 != curr_reg:
                  return False
          return True
  
  
  class Opcode00(Opcode):
      ops = {0x00: "nop"}
      num_code_units = 1
      max_regs = 0
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.nature = inst.get_AA()
          if self.nature == 0:
              pass  # NOP
          elif self.nature == 1:
              self.size = code_units.get_code_unit()
              self.first_key = code_units.get_int()
              self.targets = []
              for _ in range(self.size):
                  self.targets.append(code_units.get_int())
          elif self.nature == 2:
              self.size = code_units.get_code_unit()
              self.keys = []
              self.targets = []
              for _ in range(self.size):
                  self.keys.append(code_units.get_int())
              for _ in range(self.size):
                  self.targets.append(code_units.get_int())
          elif self.nature == 3:
              self.element_width = code_units.get_code_unit()
              self.size = code_units.get_uint()
              num_code_units = int((self.size * self.element_width + 1) / 2)
              encoder = file_extract.FileEncode(BytesIO(), "little", 4)
              for _ in range(num_code_units):
                  encoder.put_uint16(code_units.get_code_unit())
              encoder.seek(0)
              self.data = encoder.file.getvalue()
          else:
              raise ValueError("add support for NOP nature %u" % (self.nature))
  
      def get_name(self):
          if self.nature == 0:
              return self.ops[0]
          elif self.nature == 1:
              return "packed-switch-payload"
          elif self.nature == 2:
              return "sparse-switch-payload"
          elif self.nature == 3:
              return "fill-array-data-payload"
          else:
              raise ValueError("add support for NOP nature %u" % (self.nature))
  
      def get_num_code_units(self):
          if self.nature == 0:
              return 1
          elif self.nature == 1:
              op_count = 1
              size_count = 1
              first_key_count = 2
              keys_count = self.size * 2
              return op_count + size_count + first_key_count + keys_count
          elif self.nature == 2:
              op_count = 1
              size_count = 1
              keys_and_targets_count = self.size * 4
              return op_count + size_count + keys_and_targets_count
          elif self.nature == 3:
              op_count = 1
              element_width_count = 2
              return op_count + element_width_count + len(self.data)
          else:
              raise ValueError("add support for NOP nature %u" % (self.nature))
  
      def dump(self, f=sys.stdout, context=None):
          if self.nature == 0:
              f.write("%s" % (self.get_name()))
          elif self.nature == 1:
              f.write("packed-switch-payload\n")
              f.write("INDEX  KEY       TARGET\n===== --------- ---------\n")
              for (i, target) in enumerate(self.targets):
                  f.write("[%4u] %+8.8x %+8.8x\n" % (i, self.first_key + i, target))
          elif self.nature == 2:
              f.write("sparse-switch-payload\n")
              f.write("INDEX  KEY       TARGET\n===== --------- ---------\n")
              for (i, key) in enumerate(self.keys):
                  f.write("[%4u] %+8.8x %+8.8x\n" % (i, key, self.targets[i]))
          elif self.nature == 3:
              f.write(
                  "fill-array-data-payload (elem_width = %u, size = %u)\n"
                  % (self.element_width, self.size)
              )
              file_extract.dump_memory(0, self.data, self.element_width, f)
  
      def emulate(self, emulator):
          pass
  
  
  class Opcode01(Opcode):
      ops = {0x01: "move"}
      num_code_units = 1
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode02(Opcode):
      ops = {0x02: "move/from16"}
      num_code_units = 2
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst[1])
  
      def check_encoding(self, f=sys.stdout):
          if self.regs[0] <= UINT4_MAX and self.regs[1] <= UINT4_MAX:
              f.write('warning: "move/from16" can be encoded as a "move"')
              f.write(" more efficiently as its registers are both <= %u\n" % (UINT4_MAX))
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode03(Opcode):
      ops = {0x03: "move/16"}
      num_code_units = 3
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst[1])
          self.regs.append(inst[2])
  
      def check_encoding(self, f=sys.stdout):
          if self.regs[0] <= UINT4_MAX and self.regs[1] <= UINT4_MAX:
              f.write('warning: "move/16" can be encoded as a "move"')
              f.write(" more efficiently as its registers are both <= %u\n" % (UINT4_MAX))
              return 4
          if self.regs[0] <= UINT8_MAX:
              f.write('warning: "move/16" can be encoded as a "move/from16"')
              f.write(" more efficiently as its first register is <= %u\n" % (UINT8_MAX))
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode04(Opcode):
      ops = {0x04: "move-wide"}
      num_code_units = 1
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode05(Opcode):
      ops = {0x05: "move-wide/from16"}
      num_code_units = 2
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst[1])
  
      def check_encoding(self, f=sys.stdout):
          if self.regs[0] <= UINT4_MAX and self.regs[1] <= UINT4_MAX:
              f.write('warning: "move-wide/from16" can be encoded as a ')
              f.write('"move-wide" more efficiently as its registers are ')
              f.write("both <= %u\n" % (UINT4_MAX))
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode06(Opcode):
      ops = {0x06: "move-wide/16"}
      num_code_units = 3
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst[1])
          self.regs.append(inst[2])
  
      def check_encoding(self, f=sys.stdout):
          if self.regs[0] <= UINT4_MAX and self.regs[1] <= UINT4_MAX:
              f.write('warning: "move-wide/16" can be encoded as a "move-wide" ')
              f.write("more efficiently as its registers are both <= %u\n" % (UINT4_MAX))
              return 4
          if self.regs[0] <= UINT8_MAX:
              f.write('warning: "move-wide/16" can be encoded as a ')
              f.write('"move-wide/from16" more efficiently as its first ')
              f.write("register is <= %u\n" % (UINT8_MAX))
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode07(Opcode):
      ops = {0x07: "move-object"}
      num_code_units = 1
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode08(Opcode):
      ops = {0x08: "move-object/from16 "}
      num_code_units = 2
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst[1])
  
      def check_encoding(self, f=sys.stdout):
          if self.regs[0] <= UINT4_MAX and self.regs[1] <= UINT4_MAX:
              f.write('warning: "move-object/from16" can be encoded as a ')
              f.write('"move-object" more efficiently as its registers are ')
              f.write("both <= %u\n" % (UINT4_MAX))
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode09(Opcode):
      ops = {0x09: "move-object/16"}
      num_code_units = 3
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst[1])
          self.regs.append(inst[2])
  
      def check_encoding(self, f=sys.stdout):
          if self.regs[0] <= UINT4_MAX and self.regs[1] <= UINT4_MAX:
              f.write('warning: "move-object/16" can be encoded as a ')
              f.write('"move-object" more efficiently as its registers ')
              f.write("are both <= %u\n" % (UINT4_MAX))
              return 4
          if self.regs[0] <= UINT8_MAX:
              f.write('warning: "move-object/16" can be encoded as a ')
              f.write('"move-object/from16" more efficiently as its first ')
              f.write("register is <= %u\n" % (UINT8_MAX))
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode0A_0D(Opcode):
      ops = {
          0x0A: "move-result",
          0x0B: "move-result-wide",
          0x0C: "move-result-object",
          0x0D: "move-exception",
      }
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode0E(Opcode):
      ops = {0x0E: "return-void"}
      num_code_units = 1
      max_regs = 0
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s" % (self.get_name()))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode0F(Opcode):
      ops = {0x0F: "return"}
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode10(Opcode):
      ops = {0x10: "return-wide"}
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode11(Opcode):
      ops = {0x11: "return-object"}
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode12(Opcode):
      ops = {0x12: "const/4"}
      num_code_units = 1
      max_regs = 1
      extra_data = "n"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_A()
          self.imm = sign_extending(inst[0] >> 12, 4)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode13(Opcode):
      ops = {0x13: "const/16"}
      num_code_units = 2
      max_regs = 1
      extra_data = "s"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = sign_extending(inst[1], 16)
  
      def check_encoding(self, f=sys.stdout):
          if self.reg <= UINT4_MAX and INT4_MIN <= self.imm and self.imm <= INT4_MAX:
              f.write('warning: "const/16" can be encoded as a "const/4" more ')
              f.write("efficiently as its register is <= %u and " % (UINT4_MAX))
              f.write("(%i <= %i <= %i)\n" % (INT4_MIN, self.imm, INT4_MAX))
              return 2
          return 0
  
      def new_encoding(self, f=sys.stdout):
          if (
              self.reg <= UINT4_MAX
              and self.imm > INT4_MAX
              and self.imm <= (INT4_MAX + UINT4_MAX)
          ):
              f.write('"const/16" could be encoded as a new "const/u4" stores ')
              f.write("a 4 bit unsigned offset from +8 for a constant range ")
              f.write("of [8-24):\n")
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode14(Opcode):
      ops = {0x14: "const"}
      num_code_units = 3
      max_regs = 1
      extra_data = "i"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = inst.get_uint32(1)
  
      def check_encoding(self, f=sys.stdout):
          if self.reg <= UINT8_MAX and INT16_MIN <= self.imm and self.imm <= INT16_MAX:
              f.write('warning: "const" can be encoded as a "const/16" more ')
              f.write("efficiently as its register is < %u " % (UINT8_MAX))
              f.write("and (%i <= %i <= %i)\n" % (INT16_MIN, self.imm, INT16_MAX))
              return 2
          return 0
  
      def new_encoding(self, f=sys.stdout):
          if self.imm > INT16_MAX and self.imm <= (INT16_MAX + UINT16_MAX):
              f.write('"const" could be encoded as a new "const/u16" stores a ')
              f.write("16 bit unsigned offset from 32768 instead of a 16 bit ")
              f.write("signed value\n")
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode15(Opcode):
      ops = {0x15: "const/high16"}
      num_code_units = 2
      max_regs = 1
      extra_data = "h"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = inst[1] << 16
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode16(Opcode):
      ops = {0x16: "const-wide/16"}
      num_code_units = 2
      max_regs = 1
      extra_data = "s"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = sign_extending(inst[1], 16)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode17(Opcode):
      ops = {0x17: "const-wide/32"}
      num_code_units = 3
      max_regs = 1
      extra_data = "i"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = inst.get_sint32(1)
  
      def check_encoding(self, f=sys.stdout):
          if INT16_MIN <= self.imm and self.imm <= INT16_MAX:
              f.write('warning: "const-wide/32" can be encoded as a ')
              f.write(
                  '"const-wide/16" more efficiently as (%i <= %i <= %i)\n'
                  % (UINT8_MAX, INT16_MIN, self.imm, INT16_MAX)
              )
              return 2
          return 0
  
      def new_encoding(self, f=sys.stdout):
          if self.imm > INT16_MAX and self.imm <= (INT16_MAX + UINT16_MAX):
              f.write('"const-wide/32" could be encoded as a new ')
              f.write('"const-wide/u16" stores a 16 bit unsigned offset from ')
              f.write("32768 instead of a 16 bit signed value\n")
              return 2
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode18(Opcode):
      ops = {0x18: "const-wide/64"}
      num_code_units = 5
      max_regs = 1
      extra_data = "l"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = inst.get_uint64(1)
  
      def check_encoding(self, f=sys.stdout):
          if INT16_MIN <= self.imm and self.imm <= INT16_MAX:
              f.write('warning: "const-wide/64" can be encoded as a ')
              f.write(
                  '"const-wide/16" more efficiently as (%i <= %i <= %i)\n'
                  % (INT16_MIN, self.imm, INT16_MAX)
              )
              return 6
          if INT32_MIN <= self.imm and self.imm <= INT32_MAX:
              f.write('warning: "const-wide/64" can be encoded as a ')
              f.write(
                  '"const-wide/32" more efficiently as (%i <= %i <= %i)\n'
                  % (INT32_MIN, self.imm, INT32_MAX)
              )
              return 4
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode19(Opcode):
      ops = {0x19: "const-wide/high16"}
      num_code_units = 2
      max_regs = 1
      extra_data = "h"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.imm = sign_extending(inst[1], 16) << 48
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, #int %i // #%#x" % (self.get_name(), self.reg, self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class Opcode1A(Opcode):
      ops = {0x1A: "const-string"}
      num_code_units = 2
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.string_idx = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, " % (self.get_name(), self.reg))
          if context is not None:
              f.write('"%s" // ' % context.get_string(self.string_idx))
          f.write("string@%4.4x" % self.string_idx)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode1B(Opcode):
      ops = {0x1B: "const-string/jumbo"}
      num_code_units = 3
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.string_idx = inst.get_uint32(1)
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, " % (self.get_name(), self.reg))
          if context is not None:
              f.write('"%s" // ' % context.get_string(self.string_idx))
          f.write("string@%8.8x" % self.string_idx)
  
      def check_encoding(self, f=sys.stdout):
          if self.signed_offset <= UINT16_MAX:
              f.write('warning: "const-string/jumbo" can be encoded as a ')
              f.write('"const-string" more efficiently as its offset is ')
              f.write("<= UINT16_MAX\n")
              return 2
          return 0
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode1C(Opcode):
      ops = {0x1C: "const-class"}
      num_code_units = 2
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.type = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, " % (self.get_name(), self.reg))
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode1D(Opcode):
      ops = {0x1D: "monitor-enter"}
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode1E(Opcode):
      ops = {0x1E: "monitor-exit"}
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode1F(Opcode):
      ops = {0x1F: "check-cast"}
      num_code_units = 2
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.type = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, " % (self.get_name(), self.reg))
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode20(Opcode):
      ops = {0x20: "instance-of"}
      num_code_units = 2
      max_regs = 2
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
          self.type = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u, " % (self.get_name(), self.regs[0], self.regs[1]))
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode21(Opcode):
      ops = {0x21: "array-length"}
      num_code_units = 1
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode22(Opcode):
      ops = {0x22: "new-instance"}
      num_code_units = 2
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.type = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, " % (self.get_name(), self.reg))
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode23(Opcode):
      ops = {0x23: "new-array"}
      num_code_units = 2
      max_regs = 2
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
          self.type = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u " % (self.get_name(), self.regs[0], self.regs[1]))
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode24(Opcode):
      ops = {0x24: "filled-new-array"}
      num_code_units = 3
      max_regs = 5
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          arg_count = inst[0] >> 12
          self.type = inst[1]
          self.regs = []
          regs = inst[2] | ((inst[0] << 8) & 0xF0000)
          for _ in range(arg_count):
              self.regs.append(regs & 0xF)
              regs >>= 4
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s {%s} "
              % (self.get_name(), ", ".join(["v%u" % reg for reg in self.regs]))
          )
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode25(Opcode):
      ops = {0x25: "filled-new-array/range "}
      num_code_units = 3
      max_regs = "r"
      extra_data = "c"
      format = "3rc"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          arg_count = inst.get_AA()
          self.type = inst[1]
          first_reg = inst[2]
          self.regs = []
          for i in range(arg_count):
              self.regs.append(first_reg + i)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s {%s} "
              % (self.get_name(), ", ".join(["v%u" % reg for reg in self.regs]))
          )
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode26(Opcode):
      ops = {0x26: "fill-array-data"}
      num_code_units = 3
      max_regs = 1
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.signed_offset = inst.get_sint32(1)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, %8.8x // %s"
              % (
                  self.get_name(),
                  self.reg,
                  self.inst.code_unit_idx + self.signed_offset,
                  get_signed_hex_offset_as_str(self.signed_offset, 8),
              )
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode27(Opcode):
      ops = {0x27: "throw"}
      num_code_units = 1
      max_regs = 1
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u" % (self.get_name(), self.reg))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode28(Opcode):
      ops = {0x28: "goto"}
      num_code_units = 1
      max_regs = 0
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.signed_offset = inst.get_signed_AA()
  
      def check_encoding(self, f=sys.stdout):
          if self.signed_offset == 0:
              f.write('error: "goto" has a zero offset  (invalid encoding)\n')
          return 0
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s %4.4x // %+i"
              % (
                  self.get_name(),
                  self.inst.code_unit_idx + self.signed_offset,
                  self.signed_offset,
              )
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode29(Opcode):
      ops = {0x29: "goto/16"}
      num_code_units = 2
      max_regs = 0
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.signed_offset = sign_extending(inst[1], 16)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s %4.4x // %+i"
              % (
                  self.get_name(),
                  self.inst.code_unit_idx + self.signed_offset,
                  self.signed_offset,
              )
          )
  
      def check_encoding(self, f=sys.stdout):
          if self.signed_offset == 0:
              f.write('error: "goto/16" has a zero offset (invalid encoding)\n')
          elif INT8_MIN <= self.signed_offset and self.signed_offset <= INT8_MAX:
              f.write('warning: "goto/16" can be encoded as a "goto" more ')
              f.write("efficiently since (INT8_MIN <= offset <= INT8_MAX)\n")
              return 2
          return 0
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode2A(Opcode):
      ops = {0x2A: "goto/32"}
      num_code_units = 3
      max_regs = 0
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.signed_offset = inst.get_sint32(1)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s %4.4x // %+i"
              % (
                  self.get_name(),
                  self.inst.code_unit_idx + self.signed_offset,
                  self.signed_offset,
              )
          )
  
      def check_encoding(self, f=sys.stdout):
          if self.signed_offset == 0:
              return 0
          if INT8_MIN <= self.signed_offset and self.signed_offset <= INT8_MAX:
              f.write('warning: "goto/32" can be encoded as a "goto" more ')
              f.write("efficiently since (INT8_MIN <= offset <= INT8_MAX)\n")
              return 2
          if INT16_MIN <= self.signed_offset and self.signed_offset <= INT16_MAX:
              f.write('warning: "goto/32" can be encoded as a "goto/16" more ')
              f.write("efficiently since (INT16_MIN <= offset <= INT16_MAX)\n")
              return 4
          return 0
  
      def new_encoding(self, f=sys.stdout):
          if INT16_MIN <= self.signed_offset and self.signed_offset <= INT16_MAX:
              return 0
          if INT24_MIN <= self.signed_offset and self.signed_offset <= INT24_MAX:
              f.write('"goto/32" could be encoded as a new "goto/16" where ')
              f.write("that opcode uses the extra 8 bits in the first code ")
              f.write("unit to provide a 24 bit branch range\n")
              return 2
          return 0
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode2B(Opcode):
      ops = {0x2B: "packed-switch"}
      num_code_units = 3
      max_regs = 1
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.branch = inst.get_sint32(1)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, %8.8x // +%8.8x"
              % (
                  self.get_name(),
                  self.reg,
                  self.inst.get_code_unit_index() + self.branch,
                  self.branch,
              )
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode2C(Opcode):
      ops = {0x2C: "sparse-switch"}
      num_code_units = 3
      max_regs = 1
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.branch = inst.get_sint32(1)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, %8.8x // +%8.8x"
              % (
                  self.get_name(),
                  self.reg,
                  self.inst.get_code_unit_index() + self.branch,
                  self.branch,
              )
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode2D_31(Opcode):
      ops = {
          0x2D: "cmpl-float (lt bias)",
          0x2E: "cmpg-float (gt bias)",
          0x2F: "cmpl-double (lt bias)",
          0x30: "cmpg-double (gt bias)",
          0x31: "cmp-long",
      }
      num_code_units = 2
      max_regs = 3
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst.get_uint8_lo(1))
          self.regs.append(inst.get_uint8_hi(1))
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, v%u, v%u"
              % (self.get_name(), self.regs[0], self.regs[1], self.regs[2])
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode32_37(Opcode):
      ops = {
          0x32: "if-eq",
          0x33: "if-ne",
          0x34: "if-lt",
          0x35: "if-ge",
          0x36: "if-gt",
          0x37: "if-le",
      }
      num_code_units = 2
      max_regs = 2
      extra_data = "t"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
          self.signed_offset = sign_extending(inst[1], 16)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, v%u, %4.4x // %i"
              % (
                  self.get_name(),
                  self.regs[0],
                  self.regs[1],
                  self.inst.code_unit_idx + self.signed_offset,
                  self.signed_offset,
              )
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode38_3D(Opcode):
      ops = {
          0x38: "if-eqz",
          0x39: "if-nez",
          0x3A: "if-ltz",
          0x3B: "if-gez",
          0x3C: "if-gtz",
          0x3D: "if-lez",
      }
      num_code_units = 2
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.signed_offset = sign_extending(inst[1], 16)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, %4.4x // %s"
              % (
                  self.get_name(),
                  self.reg,
                  self.signed_offset + self.inst.code_unit_idx,
                  get_signed_hex_offset_as_str(self.signed_offset, 4),
              )
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode44_51(Opcode):
      ops = {
          0x44: "aget",
          0x45: "aget-wide",
          0x46: "aget-object",
          0x47: "aget-boolean",
          0x48: "aget-byte",
          0x49: "aget-char",
          0x4A: "aget-short",
          0x4B: "aput",
          0x4C: "aput-wide",
          0x4D: "aput-object",
          0x4E: "aput-boolean",
          0x4F: "aput-byte",
          0x50: "aput-char",
          0x51: "aput-short",
      }
      num_code_units = 2
      max_regs = 3
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst.get_uint8_lo(1))
          self.regs.append(inst.get_uint8_hi(1))
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, v%u, v%u"
              % (self.get_name(), self.regs[0], self.regs[1], self.regs[2])
          )
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode52_5f(Opcode):
      ops = {
          0x52: "iget",
          0x53: "iget-wide",
          0x54: "iget-object",
          0x55: "iget-boolean",
          0x56: "iget-byte",
          0x57: "iget-char",
          0x58: "iget-short",
          0x59: "iput",
          0x5A: "iput-wide",
          0x5B: "iput-object",
          0x5C: "iput-boolean",
          0x5D: "iput-byte",
          0x5E: "iput-char",
          0x5F: "iput-short",
      }
      num_code_units = 2
      max_regs = 2
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
          self.field_idx = inst[1]
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u, " % (self.get_name(), self.regs[0], self.regs[1]))
          if context is not None:
              field_item = context.get_field_ids()[self.field_idx]
              f.write(
                  "%s.%s:%s // "
                  % (
                      context.get_typename(field_item.class_idx),
                      context.get_string(field_item.name_idx),
                      context.get_typename(field_item.type_idx),
                  )
              )
          f.write("field@%4.4x" % self.field_idx)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode60_6d(Opcode):
      ops = {
          0x60: "sget",
          0x61: "sget-wide",
          0x62: "sget-object",
          0x63: "sget-boolean",
          0x64: "sget-byte",
          0x65: "sget-char",
          0x66: "sget-short",
          0x67: "sput",
          0x68: "sput-wide",
          0x69: "sput-object",
          0x6A: "sput-boolean",
          0x6B: "sput-byte",
          0x6C: "sput-char",
          0x6D: "sput-short",
      }
      num_code_units = 2
      max_regs = 1
      extra_data = "c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.reg = inst.get_AA()
          self.field_idx = inst.get_uint16(1)
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, " % (self.get_name(), self.reg))
          if context is not None:
              field_item = context.get_field_ids()[self.field_idx]
              f.write(
                  "%s.%s:%s // "
                  % (
                      context.get_typename(field_item.class_idx),
                      context.get_string(field_item.name_idx),
                      context.get_typename(field_item.type_idx),
                  )
              )
          f.write("field@%4.4x" % self.field_idx)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  can_use_new_encoding = 0
  cant_use_new_encoding = 0
  
  
  class Opcode6E_72(Opcode):
      ops = {
          0x6E: "invoke-virtual",
          0x6F: "invoke-super",
          0x70: "invoke-direct",
          0x71: "invoke-static",
          0x72: "invoke-interface",
      }
      num_code_units = 3
      max_regs = 5
      extra_data = "c"
      format = "35c"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          arg_count = inst[0] >> 12
          self.method_idx = inst[1]
          self.regs = []
          regs = inst[2] | ((inst[0] << 8) & 0xF0000)
          for _ in range(arg_count):
              self.regs.append(regs & 0xF)
              regs >>= 4
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s {%s} "
              % (self.get_name(), ", ".join(["v%u" % reg for reg in self.regs]))
          )
          if context is not None:
              method_item = context.get_method_ids()[self.method_idx]
              f.write(
                  "%s.%s:%s // "
                  % (
                      context.get_typename(method_item.class_idx),
                      context.get_string(method_item.name_idx),
                      context.get_proto_string(method_item.proto_idx),
                  )
              )
          f.write("method@%4.4x" % self.method_idx)
  
      def new_encoding(self, f=sys.stdout):
          if (
              self.regs_are_sequential()
              and (len(self.regs) == 0 or self.regs[0] <= UINT4_MAX)
              and len(self.regs) <= UINT4_MAX
          ):
              global can_use_new_encoding
              can_use_new_encoding += 1
              name = self.get_name()
              f.write('"%s" can be encoded as "%s/min-range" ' % (name, name))
              f.write("where the first register is contained in the first ")
              f.write("opcode\n")
              return 2
          global cant_use_new_encoding
          cant_use_new_encoding += 1
          return 0
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode74_78(Opcode):
      ops = {
          0x74: "invoke-virtual/range",
          0x75: "invoke-super/range",
          0x76: "invoke-direct/range",
          0x77: "invoke-static/range",
          0x78: "invoke-interface/range",
      }
      num_code_units = 3
      max_regs = "r"
      extra_data = "c"
      format = "3rc"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          arg_count = inst.get_AA()
          self.method_idx = inst[1]
          first_reg = inst[2]
          self.regs = []
          for i in range(arg_count):
              self.regs.append(first_reg + i)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s {%s} "
              % (self.get_name(), ", ".join(["v%u" % reg for reg in self.regs]))
          )
          if context is not None:
              method_item = context.get_method_ids()[self.method_idx]
              f.write(
                  "%s.%s:%s // "
                  % (
                      context.get_typename(method_item.class_idx),
                      context.get_string(method_item.name_idx),
                      context.get_proto_string(method_item.proto_idx),
                  )
              )
          f.write("method@%4.4x" % self.method_idx)
  
      def new_encoding(self, f=sys.stdout):
          if (
              self.regs_are_sequential()
              and (len(self.regs) == 0 or self.regs[0] <= UINT4_MAX)
              and len(self.regs) <= UINT4_MAX
          ):
              name = self.get_name()
              f.write('"%s" can be encoded as a "%s/min-range" ' % (name, name))
              f.write("where the first register is contained in the first ")
              f.write("opcode\n")
              return 2
          return 0
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode7B_8F(Opcode):
      ops = {
          0x7B: "neg-int",
          0x7C: "not-int",
          0x7D: "neg-long",
          0x7E: "not-long",
          0x7F: "neg-float",
          0x80: "neg-double",
          0x81: "int-to-long",
          0x82: "int-to-float",
          0x83: "int-to-double",
          0x84: "long-to-int",
          0x85: "long-to-float",
          0x86: "long-to-double",
          0x87: "float-to-int",
          0x88: "float-to-long",
          0x89: "float-to-double",
          0x8A: "double-to-int",
          0x8B: "double-to-long",
          0x8C: "double-to-float",
          0x8D: "int-to-byte",
          0x8E: "int-to-char",
          0x8F: "int-to-short",
      }
      num_code_units = 1
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class Opcode90_AF(Opcode):
      ops = {
          0x90: "add-int",
          0x91: "sub-int",
          0x92: "mul-int",
          0x93: "div-int",
          0x94: "rem-int",
          0x95: "and-int",
          0x96: "or-int",
          0x97: "xor-int",
          0x98: "shl-int",
          0x99: "shr-int",
          0x9A: "ushr-int",
          0x9B: "add-long",
          0x9C: "sub-long",
          0x9D: "mul-long",
          0x9E: "div-long",
          0x9F: "rem-long",
          0xA0: "and-long",
          0xA1: "or-long",
          0xA2: "xor-long",
          0xA3: "shl-long",
          0xA4: "shr-long",
          0xA5: "ushr-long",
          0xA6: "add-float",
          0xA7: "sub-float",
          0xA8: "mul-float",
          0xA9: "div-float",
          0xAA: "rem-float",
          0xAB: "add-double",
          0xAC: "sub-double",
          0xAD: "mul-double",
          0xAE: "div-double",
          0xAF: "rem-double",
      }
      num_code_units = 2
      max_regs = 3
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst.get_uint8_lo(1))
          self.regs.append(inst.get_uint8_hi(1))
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, v%u, v%u"
              % (self.get_name(), self.regs[0], self.regs[1], self.regs[2])
          )
  
      def opIsCommutative(self):
          """Return True if the operation is commutative"""
          op = self.get_op()
          return (
              op == 0x90  # add-int
              or op == 0x92  # mul-int
              or op == 0x95  # and-int
              or op == 0x96  # or-int
              or op == 0x97  # xor-int
              or op == 0x9B  # add-long
              or op == 0x9D  # mul-long
              or op == 0xA0  # and-long
              or op == 0xA1  # or-long
              or op == 0xA2  # xor-long
              or op == 0xA6  # add-float
              or op == 0xA8  # mul-float
              or op == 0xAB  # add-double
              or op == 0xAD  # mul-double
          )
  
      def check_encoding(self, f=sys.stdout):
          vAA = self.regs[0]
          vBB = self.regs[1]
          vCC = self.regs[2]
          if vAA == vBB and vAA <= UINT4_MAX and vCC <= UINT4_MAX:
              name = self.get_name()
              f.write('warning: "%s" can be encoded more efficiently ' % (name))
              f.write('as "%s/2addr v%u, v%u"\n' % (name, vAA, vCC))
              return 2
          if (
              vAA == vCC
              and vAA <= UINT4_MAX
              and vBB <= UINT4_MAX
              and self.opIsCommutative()
          ):
              name = self.get_name()
              f.write('warning: "%s" is commutative and can be ' % (name))
              f.write(
                  'encoded more efficiently as "%s/2addr v%u, v%u"\n' % (name, vAA, vBB)
              )
              return 2
          return 0  # Return zero to indicate we can't save any bytes
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class OpcodeB0_CF(Opcode):
      ops = {
          0xB0: "add-int/2addr",
          0xB1: "sub-int/2addr",
          0xB2: "mul-int/2addr",
          0xB3: "div-int/2addr",
          0xB4: "rem-int/2addr",
          0xB5: "and-int/2addr",
          0xB6: "or-int/2addr",
          0xB7: "xor-int/2addr",
          0xB8: "shl-int/2addr",
          0xB9: "shr-int/2addr",
          0xBA: "ushr-int/2addr",
          0xBB: "add-long/2addr",
          0xBC: "sub-long/2addr",
          0xBD: "mul-long/2addr",
          0xBE: "div-long/2addr",
          0xBF: "rem-long/2addr",
          0xC0: "and-long/2addr",
          0xC1: "or-long/2addr",
          0xC2: "xor-long/2addr",
          0xC3: "shl-long/2addr",
          0xC4: "shr-long/2addr",
          0xC5: "ushr-long/2addr",
          0xC6: "add-float/2addr",
          0xC7: "sub-float/2addr",
          0xC8: "mul-float/2addr",
          0xC9: "div-float/2addr",
          0xCA: "rem-float/2addr",
          0xCB: "add-double/2addr",
          0xCC: "sub-double/2addr",
          0xCD: "mul-double/2addr",
          0xCE: "div-double/2addr",
          0xCF: "rem-double/2addr ",
      }
      num_code_units = 1
      max_regs = 2
      extra_data = "x"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
  
      def dump(self, f=sys.stdout, context=None):
          f.write("%s v%u, v%u" % (self.get_name(), self.regs[0], self.regs[1]))
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class OpcodeD0_D7(Opcode):
      ops = {
          0xD0: "add-int/lit16",
          0xD1: "rsub-int/lit16",
          0xD2: "mul-int/lit16",
          0xD3: "div-int/lit16",
          0xD4: "rem-int/lit16",
          0xD5: "and-int/lit16",
          0xD6: "or-int/lit16",
          0xD7: "xor-int/lit16",
      }
      num_code_units = 2
      max_regs = 2
      extra_data = "s"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_A())
          self.regs.append(inst.get_B())
          self.imm = sign_extending(inst[1], 16)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, v%u, #int %i // #%#x"
              % (self.get_name(), self.regs[0], self.regs[1], self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class OpcodeD8_E2(Opcode):
      ops = {
          0xD8: "add-int/lit8",
          0xD9: "rsub-int/lit8",
          0xDA: "mul-int/lit8",
          0xDB: "div-int/lit8",
          0xDC: "rem-int/lit8",
          0xDD: "and-int/lit8",
          0xDE: "or-int/lit8",
          0xDF: "xor-int/lit8",
          0xE0: "shl-int/lit8",
          0xE1: "shr-int/lit8",
          0xE2: "ushr-int/lit8",
      }
      num_code_units = 2
      max_regs = 2
      extra_data = "b"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          self.regs = []
          self.regs.append(inst.get_AA())
          self.regs.append(inst.get_uint8_lo(1))
          self.imm = sign_extending(inst.get_uint8_hi(1), 8)
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s v%u, v%u, #int %i // #%#x"
              % (self.get_name(), self.regs[0], self.regs[1], self.imm, self.imm)
          )
  
      def emulate(self, emulator):
          emulator.write_register(self.reg, self.imm)
  
  
  class OpcodeFA(Opcode):
      ops = {0xFA: "invoke-polymorphic"}
      num_code_units = 4
      max_regs = 5
      extra_data = "cc"
  
      def __init__(self, inst, code_units):
          Opcode.__init__(self, inst)
          raise ValueError("debug this when we find one of these")
          arg_count = inst[0] >> 12
          self.method_ref_idx = inst[1]
          self.method_hdl_ref = inst[2]
          self.regs = []
          regs = inst[3] | ((inst[0] << 8) & 0xF0000)
          self.proto = inst[4]
          for _ in range(arg_count):
              self.regs.append(regs & 0xF)
              regs >>= 4
  
      def dump(self, f=sys.stdout, context=None):
          f.write(
              "%s {%s} "
              % (self.get_name(), ", ".join(["v%u" % reg for reg in self.regs]))
          )
          if context is not None:
              f.write("%s // " % context.get_typename(self.type))
          f.write("type@%4.4x" % self.type)
  
      def emulate(self, emulator):
          raise ValueError("emulate not supported")
  
  
  class CodeUnits(Opcode):
      def __init__(self, code_units):
          self.code_units = code_units
          self.idx = 0
  
      def index_is_valid(self):
          return self.idx < len(self.code_units)
  
      def get_index(self):
          return self.idx
  
      def peek_code_unit(self, idx):
          return self.code_units[idx]
  
      def get_int(self):
          return sign_extending(self.get_uint(), 32)
  
      def get_uint(self):
          return self.get_code_unit() | (self.get_code_unit() << 16)
  
      def get_code_unit(self):
          idx = self.idx
          self.idx += 1
          return self.code_units[idx]
  
  
  def swap16(u):
      return ((u >> 8) & 0x00FF) | ((u << 8) & 0xFF00)
  
  
  class DexInstruction(object):
      opcode_defs = []
  
      @classmethod
      def initialize(cls):
          opcode_classes = [
              Opcode00,
              Opcode01,
              Opcode02,
              Opcode03,
              Opcode04,
              Opcode05,
              Opcode06,
              Opcode07,
              Opcode08,
              Opcode09,
              Opcode0A_0D,
              Opcode0E,
              Opcode0F,
              Opcode10,
              Opcode11,
              Opcode12,
              Opcode13,
              Opcode14,
              Opcode15,
              Opcode16,
              Opcode17,
              Opcode18,
              Opcode19,
              Opcode1A,
              Opcode1B,
              Opcode1C,
              Opcode1D,
              Opcode1E,
              Opcode1F,
              Opcode20,
              Opcode21,
              Opcode22,
              Opcode23,
              Opcode24,
              Opcode25,
              Opcode26,
              Opcode27,
              Opcode28,
              Opcode29,
              Opcode2A,
              Opcode2B,
              Opcode2C,
              Opcode2D_31,
              Opcode32_37,
              Opcode38_3D,
              Opcode44_51,
              Opcode52_5f,
              Opcode60_6d,
              Opcode6E_72,
              Opcode74_78,
              Opcode7B_8F,
              Opcode90_AF,
              OpcodeB0_CF,
              OpcodeD0_D7,
              OpcodeD8_E2,
              OpcodeFA,
          ]
          for _ in range(256):
              cls.opcode_defs.append(None)
          for opcode_class in opcode_classes:
              for op in opcode_class.ops:
                  if cls.opcode_defs[op] is None:
                      cls.opcode_defs[op] = opcode_class
                  else:
                      raise ValueError(
                          "registering the same opcode twice: "
                          "%#2.2x in %s" % (op, str(opcode_class))
                      )
  
      def dump(self, f=sys.stdout, suffix="\n", context=None):
          f.write("%4.4x:" % (self.code_unit_idx))
          for code_unit in self.code_units:
              f.write(" %4.4x" % (swap16(code_unit)))
          num_code_units = len(self.code_units)
          if num_code_units < 5:
              pad = 5 - num_code_units
              for _ in range(pad):
                  f.write("     ")
          f.write(" ")
          self.instruction.dump(f=f, context=context)
          if suffix:
              f.write(suffix)
  
      def __init__(self):
          self.code_unit_idx = -1
          self.code_units = None
  
      def check_encoding(self, f=sys.stdout):
          bytes_saved = self.instruction.check_encoding(f)
          if bytes_saved:
              self.dump(f)
          return bytes_saved
  
      def new_encoding(self, f=sys.stdout):
          bytes_saved = self.instruction.new_encoding(f)
          if bytes_saved:
              self.dump(f)
          return bytes_saved
  
      def get_code_unit_index(self):
          return self.code_unit_idx
  
      def decode(self, code_units):
          self.code_unit_idx = code_units.get_index()
          self.code_units = []
          self.code_units.append(code_units.get_code_unit())
          op = self.get_op()
          opcode_class = self.opcode_defs[op]
          if opcode_class is None:
              raise ValueError("unsupported opcode %#4.4x" % (swap16(self[0])))
          for _ in range(1, opcode_class.num_code_units):
              self.code_units.append(code_units.get_code_unit())
          self.instruction = opcode_class(self, code_units)
  
      def get_name(self):
          return self.instruction.get_name()
  
      def get_num_code_units(self):
          return self.instruction.get_num_code_units()
  
      def get_op(self):
          """Return the 1 byte op field that tells us what instruction this is"""
          return self.code_units[0] & 0xFF
  
      def get_A(self):
          """Get the 4 bit value of A"""
          return (self.code_units[0] >> 8) & 0xF
  
      def get_B(self):
          """Get the 4 bit value of B"""
          return (self.code_units[0] >> 12) & 0xF
  
      def get_AA(self):
          """Get the 8 bit value of AA from the byte next to the Op"""
          return self.get_uint8_hi(0)
  
      def get_signed_AA(self):
          return sign_extending(self.get_AA(), 8)
  
      def get_uint8_lo(self, idx):
          return self.code_units[idx] & 0xFF
  
      def get_sint8_lo(self, idx):
          return sign_extending(self.get_uint8_lo(), 8)
  
      def get_uint8_hi(self, idx):
          return (self.code_units[idx] >> 8) & 0xFF
  
      def get_sint8_hi(self, idx):
          return sign_extending(self.get_uint8_hi(), 8)
  
      def get_uint16(self, idx):
          return self.code_units[idx]
  
      def get_sint16(self, idx):
          return sign_extending(self.get_uint16(), 16)
  
      def get_uint32(self, idx):
          return self.code_units[idx + 1] << 16 | self.code_units[idx]
  
      def get_sint32(self, idx):
          return sign_extending(self.get_uint32(idx), 32)
  
      def get_uint64(self, idx):
          return (
              self.code_units[idx + 3] << 48
              | self.code_units[idx + 2] << 32
              | self.code_units[idx + 1] << 16
              | self.code_units[idx]
          )
  
      def get_sint64(self, idx):
          return sign_extending(self.get_uint64(idx), 64)
  
      def __len__(self):
          """Overload the length operator to give out the number of code units"""
          return len(self.code_units)
  
      def __getitem__(self, key):
          """Overload the [] operator to give out code units"""
          return self.code_units[key]
  
      def emulate(self, emulator):
          self.instruction.emulate(emulator)
  
  
  DexInstruction.initialize()
  
  
  def get_percentage(part, total):
      return (float(part) / float(total)) * 100.0
  
  
  def print_code_stats(size, total_size, file_size):
      code_savings = get_percentage(size, total_size)
      file_savings = get_percentage(size, file_size)
      print(
          "error: %u of %u code bytes (%u file bytes) " % (size, total_size, file_size),
          end="",
      )
      print("could be saved by encoding opcodes more efficiently ", end="")
      print(
          "(%2.2f%% code savings, %2.2f%% file savings).\n" % (code_savings, file_savings)
      )
  
  
  def print_debug_stats(size, file_size):
      file_savings = get_percentage(size, file_size)
      print("error: %u debug info bytes of %u file " % (size, file_size), end="")
      print("bytes could be saved by encoding debug info more ", end="")
      print("efficiently (%2.2f%% file savings).\n" % (file_savings))
  
  
  def print_encoding_stats(size, total_size, file_size):
      code_savings = get_percentage(size, total_size)
      file_savings = get_percentage(size, file_size)
      print("%u of %u code bytes could be saved " % (size, total_size), end="")
      print("could be saved by encoding opcodes more efficiently ", end="")
      print(
          "(%2.2f%% code savings, %2.2f%% file savings).\n" % (code_savings, file_savings)
      )
  
  
  class DexEmulator(object):
      def __init__(self):
          self.registers = {}
          self.pc = 0
  
      def read_register(self, reg):
          if reg in self.registers:
              return self.registers[reg]
          raise ValueError("reading register with no value")
  
      def write_register(self, reg, value):
          self.registers[reg] = value
  
      def emulate(self, uint16_array):
          pass
  
  
  def main():
      usage = "Usage: dex.py [options] [dex file(s)]"
      parser = optparse.OptionParser(
          usage=usage, description="A script that parses DEX files."
      )
      parser.add_option(
          "-v",
          "--verbose",
          action="store_true",
          dest="verbose",
          help="display verbose debug info",
          default=False,
      )
      parser.add_option(
          "-C",
          "--color",
          action="store_true",
          dest="color",
          help="Enable colorized output",
          default=False,
      )
      parser.add_option(
          "-a",
          "--all",
          action="store_true",
          dest="dump_all",
          help="Dump all DEX sections.",
          default=False,
      )
      parser.add_option(
          "-H",
          "--header",
          action="store_true",
          dest="dump_header",
          help="Dump the DEX file header.",
          default=False,
      )
      parser.add_option(
          "--map-list",
          action="store_true",
          dest="dump_map_list",
          help="Dump the DEX map list info.",
          default=False,
      )
      parser.add_option(
          "-s",
          "--strings",
          action="store_true",
          dest="dump_strings",
          help="Dump the DEX strings.",
          default=False,
      )
      parser.add_option(
          "-t",
          "--types",
          action="store_true",
          dest="dump_types",
          help="Dump the DEX types.",
          default=False,
      )
      parser.add_option(
          "-p",
          "--protos",
          action="store_true",
          dest="dump_protos",
          help="Dump the DEX protos.",
          default=False,
      )
      parser.add_option(
          "-f",
          "--fields",
          action="store_true",
          dest="dump_fields",
          help="Dump the DEX fields.",
          default=False,
      )
      parser.add_option(
          "-m",
          "--methods",
          action="store_true",
          dest="dump_methods",
          help="Dump the DEX methods.",
          default=False,
      )
      parser.add_option(
          "--method-handles",
          action="store_true",
          dest="dump_method_handles",
          help="Dump the DEX method handles.",
          default=False,
      )
      parser.add_option(
          "--class-list",
          action="store_true",
          dest="dump_class_list",
          help="Dump the list of DEX classes.",
          default=False,
      )
      parser.add_option(
          "--classes",
          action="store_true",
          dest="dump_classes",
          help="Dump the DEX classes.",
          default=False,
      )
      parser.add_option(
          "--class",
          dest="class_filter",
          help="Find a class by name. " + "Accepts `Lpath/to/Class;` or `path.to.Class`",
          default=None,
      )
      parser.add_option(
          "--method",
          dest="method_filter",
          help="Find a method by name. Must be used with --class",
          default=None,
      )
      parser.add_option(
          "--call-sites",
          action="store_true",
          dest="dump_call_sites",
          help="Dump the DEX call sites.",
          default=False,
      )
      parser.add_option(
          "--code",
          action="store_true",
          dest="dump_code",
          help="Dump the DEX code in all class methods.",
          default=False,
      )
      parser.add_option(
          "--code-items",
          action="store_true",
          dest="dump_code_items",
          help="Dump the DEX code items.",
          default=False,
      )
      parser.add_option(
          "--code-duplication",
          action="store_true",
          dest="code_duplication",
          help=("Dump any methods in the DEX file that have the " "same instructions."),
          default=False,
      )
      parser.add_option(
          "--debug-info",
          action="store_true",
          dest="dump_debug_info",
          help="Dump the DEX debug info for each method.",
          default=False,
      )
      parser.add_option(
          "--debug-info-items",
          action="store_true",
          dest="dump_debug_info_items",
          help="Dump the DEX debug info items pointed to in its" + " map_list",
          default=False,
      )
      parser.add_option(
          "--stats",
          action="store_true",
          dest="dump_stats",
          help="Dump the DEX opcode statistics.",
          default=False,
      )
      parser.add_option(
          "--check-encoding",
          action="store_true",
          dest="check_encoding",
          help="Verify opcodes are efficiently encoded.",
          default=False,
      )
      parser.add_option(
          "--new-encoding",
          action="store_true",
          dest="new_encoding",
          help="Report byte savings from potential new encodings.",
          default=False,
      )
      parser.add_option(
          "--proguard",
          dest="proguard",
          help="Specify a progard file to use for demangling.",
          default=None,
      )
      parser.add_option(
          "--skip-abstract",
          action="store_true",
          dest="skip_abstract",
          help="Don't print information coming from abstract"
          " classes when passing --code, --debug or --all.",
          default=False,
      )
      parser.add_option(
          "--counts",
          action="store_true",
          dest="dump_counts",
          help="Dump the DEX opcode counts",
          default=False,
      )
      (options, files) = parser.parse_args()
  
      total_code_bytes_inefficiently_encoded = 0
      total_debug_info_bytes_inefficiently_encoded = 0
      total_new_code_bytes_inefficiently_encoded = 0
      total_opcode_byte_size = 0
      total_file_size = 0
      op_name_to_size = {}
      op_name_to_count = {}
      string_counts = {}
      i = 0
  
      if len(files) == 0:
          print("No input files. {}".format(usage))
          return
  
      for path in files:
          if os.path.splitext(path)[1] == ".apk":
              print("error: dex.py operates on dex files, please unpack your apk")
              return
  
          print("Dex file: %s" % (path))
          file_size = os.path.getsize(path)
          total_file_size += file_size
          dex = File(path, options.proguard)
          if options.class_filter:
              dex_class = dex.find_class(options.class_filter)
              if dex_class:
                  if options.method_filter is None:
                      dex_class.dump(options)
                  for method in dex_class.get_methods():
                      method_name = method.get_name()
                      if options.method_filter:
                          if options.method_filter != method_name:
                              continue
                      method.dump(options)
              else:
                  print(
                      'error: class definition not found for "%s"'
                      % (options.class_filter)
                  )
          if options.dump_classes:
              for dex_class in dex.get_classes():
                  dex_class.dump(options)
                  for method in dex_class.get_methods():
                      method.dump(options)
  
          if options.dump_header or options.dump_all:
              dex.dump_header(options)
          if options.dump_map_list or options.dump_all:
              dex.dump_map_list(options)
          if options.dump_debug_info_items or options.dump_all:
              dex.dump_debug_info_items(options)
          if options.dump_strings or options.dump_all:
              dex.dump_string_ids(options)
          if options.dump_types or options.dump_all:
              dex.dump_type_ids(options)
          if options.dump_protos or options.dump_all:
              dex.dump_proto_ids(options)
          if options.dump_fields or options.dump_all:
              dex.dump_field_ids(options)
          if options.dump_methods or options.dump_all:
              dex.dump_class_method_ids(options)
          if options.dump_class_list or options.dump_all:
              dex.dump_class_defs(options)
          if options.dump_call_sites or options.dump_all:
              dex.dump_call_site_ids(options)
          if options.dump_method_handles or options.dump_all:
              dex.dump_method_handle_items(options)
          if options.dump_code or options.dump_all:
              dex.dump_code(options)
          if options.dump_code_items:
              dex.dump_code_items(options)
          if (
              options.dump_stats
              or options.check_encoding
              or options.new_encoding
              or options.dump_counts
          ):
              if options.dump_stats:
                  for string_item in dex.get_strings():
                      if string_item.data not in string_counts:
                          string_counts[string_item.data] = 0
                      string_counts[string_item.data] += 1
              code_bytes_inefficiently_encoded = 0
              debug_info_bytes_inefficiently_encoded = 0
              new_code_bytes_inefficiently_encoded = 0
              file_opcodes_byte_size = 0
              classes = dex.get_classes()
              used_code_item_indexes = []
              for cls in classes:
                  methods = cls.get_methods()
                  for method in methods:
                      method.dump(options, f=sys.stdout)
                      opcodes_bytes_size = method.get_code_byte_size()
                      file_opcodes_byte_size += opcodes_bytes_size
                      total_opcode_byte_size += opcodes_bytes_size
                      if (
                          options.dump_stats
                          or options.check_encoding
                          or options.new_encoding
                          or options.dump_counts
                      ):
                          for dex_inst in method.get_instructions():
                              if options.dump_stats:
                                  op_name = dex_inst.get_name()
                                  size = dex_inst.get_num_code_units() * 2
                                  if op_name not in op_name_to_size:
                                      op_name_to_size[op_name] = 0
                                  op_name_to_size[op_name] += size
                              if options.dump_counts:
                                  op_name = dex_inst.get_name()
                                  if op_name not in op_name_to_count:
                                      op_name_to_count[op_name] = 0
                                  op_name_to_count[op_name] += 1
                              if options.check_encoding:
                                  code_bytes_inefficiently_encoded += (
                                      dex_inst.check_encoding()
                                  )
                              if options.new_encoding:
                                  new_code_bytes_inefficiently_encoded += (
                                      dex_inst.new_encoding()
                                  )
                          if options.check_encoding:
                              code_item_idx = method.get_code_item_index()
                              if code_item_idx >= 0:
                                  used_code_item_indexes.append(code_item_idx)
                              debug_info = method.get_debug_info()
                              if debug_info:
                                  debug_info_bytes_inefficiently_encoded += (
                                      method.check_debug_info_encoding()
                                  )
              if options.check_encoding:
                  efficiently_encoded = True
                  if code_bytes_inefficiently_encoded > 0:
                      efficiently_encoded = False
                      total_code_bytes_inefficiently_encoded += (
                          code_bytes_inefficiently_encoded
                      )
                      print_code_stats(
                          code_bytes_inefficiently_encoded,
                          file_opcodes_byte_size,
                          file_size,
                      )
                  if debug_info_bytes_inefficiently_encoded > 0:
                      efficiently_encoded = False
                      total_debug_info_bytes_inefficiently_encoded += (
                          debug_info_bytes_inefficiently_encoded
                      )
                      print_debug_stats(debug_info_bytes_inefficiently_encoded, file_size)
                  # Verify that all code items are used.
                  used_code_item_indexes.sort()
                  prev_ci_idx = 0
                  for ci_idx in used_code_item_indexes:
                      if ci_idx != prev_ci_idx:
                          efficiently_encoded = False
                          for idx in range(prev_ci_idx + 1, ci_idx):
                              print(
                                  "code_item[%u] is not used and its "
                                  "code_item can be removed" % (idx)
                              )
                      prev_ci_idx = ci_idx
                  if efficiently_encoded:
                      print("file is efficiently encoded.")
              if options.new_encoding:
                  if new_code_bytes_inefficiently_encoded > 0:
                      total_new_code_bytes_inefficiently_encoded += (
                          new_code_bytes_inefficiently_encoded
                      )
                      print_encoding_stats(
                          new_code_bytes_inefficiently_encoded,
                          file_opcodes_byte_size,
                          file_size,
                      )
                  else:
                      print("file is efficiently encoded.")
          if options.code_duplication:
              dex.report_code_duplication()
  
      if options.dump_stats:
          duped_strings_byte_size = 0
          for s in string_counts:
              count = string_counts[s]
              if count > 1:
                  s_len = len(s)
                  duped_strings_byte_size += (count - 1) * s_len + get_uleb128_byte_size(
                      s_len
                  )
          if duped_strings_byte_size > 0:
              print(
                  "%u bytes in duplicated strings across dex files."
                  % (duped_strings_byte_size)
              )
  
          print("BYTESIZE %AGE  OPCODE")
          print("======== ===== =================================")
          sorted_x = sorted(op_name_to_size.items(), key=operator.itemgetter(1))
          for (op_name, byte_size) in sorted_x:
              percentage = get_percentage(byte_size, total_opcode_byte_size)
              print("%-8u %5.2f %s" % (byte_size, percentage, op_name))
          print("-------- ----- ---------------------------------")
          print("%-8u 100.0" % (total_opcode_byte_size))
  
      if options.dump_counts:
          print("COUNT    OPCODE")
          print("======== =================================")
          for op_name, count in op_name_to_count.items():
              print("%-8u %s" % (count, op_name))
  
      if i > 0:
          if options.check_encoding:
              if total_code_bytes_inefficiently_encoded > 0:
                  print_code_stats(
                      total_code_bytes_inefficiently_encoded,
                      total_opcode_byte_size,
                      total_file_size,
                  )
              if total_debug_info_bytes_inefficiently_encoded > 0:
                  efficiently_encoded = False
                  print_debug_stats(
                      total_debug_info_bytes_inefficiently_encoded, total_file_size
                  )
          if options.new_encoding:
              invoke_kind_percentage = get_percentage(
                  can_use_new_encoding, can_use_new_encoding + cant_use_new_encoding
              )
              print(
                  "%u invoke-kind opcodes could use new encoding"
                  % (can_use_new_encoding),
                  end="",
              )
              print(
                  "%u could not (%2.2f%%)"
                  % (cant_use_new_encoding, invoke_kind_percentage)
              )
              if total_new_code_bytes_inefficiently_encoded > 0:
                  print_encoding_stats(
                      total_new_code_bytes_inefficiently_encoded,
                      total_opcode_byte_size,
                      total_file_size,
                  )
  
  
  if __name__ == "__main__":
      main()
  