
#<pycode(py_idd)>
NO_PROCESS = 0xFFFFFFFF
NO_THREAD  = 0

import types
import _ida_idaapi
import _ida_dbg
import _ida_typeinf
import _ida_name
import _ida_bytes
import _ida_ida
import ida_idaapi
import ida_typeinf

dbg_can_query = _ida_dbg.dbg_can_query

# -----------------------------------------------------------------------
class Appcall_array__(object):
    """This class is used with Appcall.array() method"""
    def __init__(self, tp):
        self.__type = tp

    def pack(self, L):
        """Packs a list or tuple into a byref buffer"""
        t = type(L)
        if not (t == list or t == tuple):
            raise ValueError("Either a list or a tuple must be passed")
        self.__size = len(L)
        if self.__size == 1:
            self.__typedobj = Appcall__.typedobj(self.__type + ";")
        else:
            self.__typedobj = Appcall__.typedobj("%s x[%d];" % (self.__type, self.__size))
        # Now store the object in a string buffer
        ok, buf = self.__typedobj.store(L)
        if ok:
            return Appcall__.byref(buf)
        else:
            return None

    def try_to_convert_to_list(self, obj):
        """Is this object a list? We check for the existance of attribute zero and attribute self.size-1"""
        if not (hasattr(obj, "0") and hasattr(obj, str(self.__size-1))):
            return obj
        # at this point, we are sure we have an "idc list"
        # let us convert to a Python list
        return [getattr(obj, str(x)) for x in range(0, self.__size)]

    def unpack(self, buf, as_list=True):
        """Unpacks an array back into a list or an object"""
        # take the value from the special ref object
        if isinstance(buf, ida_idaapi.PyIdc_cvt_refclass__):
            buf = buf.value

        # we can only unpack from strings
        if type(buf) != bytes:
            raise ValueError("Cannot unpack this type!")
        # now unpack
        ok, obj = self.__typedobj.retrieve(buf)
        if not ok:
            raise ValueError("Failed while unpacking!")
        if not as_list:
            return obj
        return self.try_to_convert_to_list(obj)


# -----------------------------------------------------------------------
# Wrapper class for the appcall()
class Appcall_callable__(object):
    """
    Helper class to issue appcalls using a natural syntax:
      appcall.FunctionNameInTheDatabase(arguments, ....)
    or
      appcall["Function@8"](arguments, ...)
    or
      f8 = appcall["Function@8"]
      f8(arg1, arg2, ...)
    or
      o = appcall.obj()
      i = byref(5)
      appcall.funcname(arg1, i, "hello", o)
    """
    def __init__(self, ea, tinfo_or_typestr = None, fields = None):
        """Initializes an appcall with a given function ea"""
        self.__ea      = ea
        self.__tif     = None
        self.__type    = None
        self.__fields  = None
        self.__options = None # Appcall options
        self.__timeout = None # Appcall timeout

        if tinfo_or_typestr:
          if isinstance(tinfo_or_typestr, ida_idaapi.string_types):
            # a type string? assume (typestr, fields), try to deserialize
            tif = ida_typeinf.tinfo_t()
            if not tif.deserialize(None, tinfo_or_typestr, fields):
              raise ValueError("Could not deserialize type string")
          else:
            if not isinstance(tinfo_or_typestr, ida_typeinf.tinfo_t):
              raise ValueError("Invalid argument 'tinfo_or_typestr'")
            tif = tinfo_or_typestr
          self.__tif = tif
          (self.__type, self.__fields, _) = tif.serialize()

    def __get_timeout(self):
        return self.__timeout

    def __set_timeout(self, v):
        self.__timeout = v

    timeout = property(__get_timeout, __set_timeout)
    """An Appcall instance can change its timeout value with this attribute"""

    def __get_options(self):
        return self.__options if self.__options != None else Appcall__.get_appcall_options()

    def __set_options(self, v):
        if self.timeout:
            # If timeout value is set, then put the timeout flag and encode the timeout value
            v |= Appcall__.APPCALL_TIMEOUT | (self.timeout << 16)
        else:
            # Timeout is not set, then clear the timeout flag
            v &= ~Appcall__.APPCALL_TIMEOUT

        self.__options = v

    options = property(__get_options, __set_options)
    """Sets the Appcall options locally to this Appcall instance"""

    def __call__(self, *args):
        """Make object callable. We redirect execution to idaapi.appcall()"""
        if self.ea is None:
            raise ValueError("Object not callable!")

        # convert arguments to a list
        arg_list = list(args)

        # Save appcall options and set new global options
        old_opt = Appcall__.get_appcall_options()
        Appcall__.set_appcall_options(self.options)

        # Do the Appcall (use the wrapped version)
        try:
            return _ida_idd.appcall(
                self.ea,
                _ida_dbg.get_current_thread(),
                self.type,
                self.fields,
                arg_list)
        finally:
            # Restore appcall options
            Appcall__.set_appcall_options(old_opt)

    def __get_ea(self):
        return self.__ea

    def __set_ea(self, val):
        self.__ea = val

    ea = property(__get_ea, __set_ea)
    """Returns or sets the EA associated with this object"""

    def __get_tif(self):
        return self.__tif

    tif = property(__get_tif)
    """Returns the tinfo_t object"""

    def __get_size(self):
        if self.__type == None:
            return -1
        r = _ida_typeinf.calc_type_size(None, self.__type)
        if not r:
            return -1
        return r

    size = property(__get_size)
    """Returns the size of the type"""

    def __get_type(self):
        return self.__type

    type = property(__get_type)
    """Returns the typestring"""

    def __get_fields(self):
        return self.__fields

    fields = property(__get_fields)
    """Returns the field names"""


    def retrieve(self, src=None, flags=0):
        """
        Unpacks a typed object from the database if an ea is given or from a string if a string was passed
        :param src: the address of the object or a string
        :returns: Returns a tuple of boolean and object or error number (Bool, Error | Object).
        """

        # Nothing passed? Take the address and unpack from the database
        if src is None:
            src = self.ea

        if type(src) == bytes:
            return _ida_typeinf.unpack_object_from_bv(None, self.type, self.fields, src, flags)
        else:
            return _ida_typeinf.unpack_object_from_idb(None, self.type, self.fields, src, flags)

    def store(self, obj, dest_ea=None, base_ea=0, flags=0):
        """
        Packs an object into a given ea if provided or into a string if no address was passed.
        :param obj: The object to pack
        :param dest_ea: If packing to idb this will be the store location
        :param base_ea: If packing to a buffer, this will be the base that will be used to relocate the pointers

        :returns: Tuple(Boolean, packed_string or error code) if packing to a string
        :returns: a return code is returned (0 indicating success) if packing to the database
        """

        # no ea passed? thus pack to a string
        if dest_ea is None:
            return _ida_typeinf.pack_object_to_bv(obj,
                                             None,
                                             self.type,
                                             self.fields,
                                             base_ea,
                                             flags)
        else:
            return _ida_typeinf.pack_object_to_idb(obj,
                                              None,
                                              self.type,
                                              self.fields,
                                              dest_ea,
                                              flags)

# -----------------------------------------------------------------------
class Appcall_consts__(object):
    """
    Helper class used by Appcall.Consts attribute
    It is used to retrieve constants via attribute access
    """
    def __init__(self, default=None):
        self.__default = default

    def __getattr__(self, attr):
        v = Appcall__.valueof(attr, self.__default)
        if v is None:
            raise AttributeError("No constant with name " + attr)
        return v

# -----------------------------------------------------------------------
class Appcall__(object):
    APPCALL_MANUAL = 0x1
    """
    Only set up the appcall, do not run it.
    you should call CleanupAppcall() when finished
    """

    APPCALL_DEBEV  = 0x2
    """
    Return debug event information
    If this bit is set, exceptions during appcall
    will generate idc exceptions with full
    information about the exception
    """

    APPCALL_TIMEOUT = 0x4
    """
    Appcall with timeout
    The timeout value in milliseconds is specified
    in the high 2 bytes of the 'options' argument:
    If timed out, errbuf will contain "timeout".
    """

    __name__ = "Appcall__"

    def __init__(self):
        self.__consts = Appcall_consts__()

    def __get_consts(self):
        return self.__consts

    Consts = property(__get_consts)
    """Use Appcall.Consts.CONST_NAME to access constants"""

    @staticmethod
    def __name_or_ea(name_or_ea):
        """
        Function that accepts a name or an ea and checks if the address is enabled.
        If a name is passed then idaapi.get_name_ea() is applied to retrieve the name
        :returns: Returns the resolved EA, or raises an exception if the address is not enabled
        """

        # a string? try to resolve it
        if type(name_or_ea) in ida_idaapi.string_types:
            ea = _ida_name.get_name_ea(_ida_idaapi.BADADDR, name_or_ea)
        else:
            ea = name_or_ea
        # could not resolve name or invalid address?
        if ea == _ida_idaapi.BADADDR or not _ida_bytes.is_mapped(ea):
            raise AttributeError("Undefined function " + name_or_ea)
        return ea

    @staticmethod
    def __typedecl_or_tinfo(typedecl_or_tinfo, flags = None):
        """
        Function that accepts a tinfo_t object or type declaration as a string
        If a type declaration is passed then ida_typeinf.parse_decl() is applied to prepare tinfo_t object
        :returns: Returns the tinfo_t object, or raises an exception if the declaration cannot be parsed
        """

        # a string? try to parse it
        if isinstance(typedecl_or_tinfo, ida_idaapi.string_types):
          if flags is None:
              flags = ida_typeinf.PT_SIL|ida_typeinf.PT_NDC|ida_typeinf.PT_TYP
          tif = ida_typeinf.tinfo_t()
          if ida_typeinf.parse_decl(tif, None, typedecl_or_tinfo, flags) == None:
            raise ValueError("Could not parse type: " + typedecl_or_tinfo)
        else:
            if not isinstance(typedecl_or_tinfo, ida_typeinf.tinfo_t):
              raise ValueError("Invalid argument 'typedecl_or_tinfo'")
            tif = typedecl_or_tinfo
        return tif

    @staticmethod
    def proto(name_or_ea, proto_or_tinfo, flags = None):
        """
        Allows you to instantiate an appcall (callable object) with the desired prototype
        :param name_or_ea: The name of the function (will be resolved with LocByName())
        :param proto_or_tinfo: function prototype as a string or type of the function as tinfo_t object
        :returns: a callbable Appcall instance with the given prototypes and flags, or
                  an exception if the prototype could not be parsed or the address is not resolvable.
        """

        # resolve and raise exception on error
        ea = Appcall__.__name_or_ea(name_or_ea)

        # parse the type if it is given as (prototype, flags)
        tif = Appcall__.__typedecl_or_tinfo(proto_or_tinfo, flags)

        # Return the callable method with type info
        return Appcall_callable__(ea, tif)

    def __getattr__(self, name_or_ea):
        """Allows you to call functions as if they were member functions (by returning a callable object)"""
        # resolve and raise exception on error
        ea = self.__name_or_ea(name_or_ea)
        if ea == _ida_idaapi.BADADDR:
            raise AttributeError("Undefined function " + name)
        # Return the callable method
        return Appcall_callable__(ea)

    def __getitem__(self, idx):
        """
        Use self[func_name] syntax if the function name contains invalid characters for an attribute name
        See __getattr___
        """
        return self.__getattr__(idx)

    @staticmethod
    def valueof(name, default=0):
        """
        If the name could not be resolved then the default value will be returned

        :returns: the numeric value of a given name string.
        """
        t, v = _ida_name.get_name_value(_ida_idaapi.BADADDR, name)
        if t == 0: # NT_NONE
          v = default
        return v

    @staticmethod
    def int64(v):
        """Whenever a 64bit number is needed use this method to construct an object"""
        return ida_idaapi.PyIdc_cvt_int64__(v)

    @staticmethod
    def byref(val):
        """
        Method to create references to immutable objects
        Currently we support references to int/strings
        Objects need not be passed by reference (this will be done automatically)
        """
        return ida_idaapi.PyIdc_cvt_refclass__(val)

    @staticmethod
    def buffer(str = None, size = 0, fill="\x00"):
        """
        Creates a string buffer. The returned value (r) will be a byref object.
        Use r.value to get the contents and r.size to get the buffer's size
        """
        if str is None:
            str = ""
        left = size - len(str)
        if left > 0:
            str = str + (fill * left)
        r = Appcall__.byref(str)
        r.size = size
        return r

    @staticmethod
    def obj(**kwds):
        """Returns an empty object or objects with attributes as passed via its keywords arguments"""
        return ida_idaapi.object_t(**kwds)

    @staticmethod
    def cstr(val):
        return ida_idaapi.as_cstr(val)

    @staticmethod
    def UTF16(s):
        return ida_idaapi.as_UTF16(s)
    unicode = UTF16

    @staticmethod
    def array(type_name):
        """Defines an array type. Later you need to pack() / unpack()"""
        return Appcall_array__(type_name)

    @staticmethod
    def typedobj(typedecl_or_tinfo, ea=None):
        """
        Returns an appcall object for a type (can be given as tinfo_t object or
        as a string declaration)
        One can then use retrieve() member method
        :param ea: Optional parameter that later can be used to retrieve the type
        :returns: Appcall object or raises ValueError exception
        """
        # parse the type if it is given as string
        tif = Appcall__.__typedecl_or_tinfo(typedecl_or_tinfo)
        # Return the callable method with type info
        return Appcall_callable__(ea, tif)

    @staticmethod
    def set_appcall_options(opt):
        """Method to change the Appcall options globally (not per Appcall)"""
        old_opt = Appcall__.get_appcall_options()
        _ida_ida.inf_set_appcall_options(opt)
        return old_opt

    @staticmethod
    def get_appcall_options():
        """Return the global Appcall options"""
        return _ida_ida.inf_get_appcall_options()

    @staticmethod
    def cleanup_appcall(tid = 0):
        """Equivalent to IDC's CleanupAppcall()"""
        return _ida_idd.cleanup_appcall(tid)

Appcall = Appcall__()
#</pycode(py_idd)>
