"""
summary: dynamically provide a custom call type

description:
  This plugin can greatly improve decompilation of indirect calls:

      call    [eax+4]

  For them, the decompiler has to guess the prototype of the called function.
  This has to be done at a very early phase of decompilation because
  the function prototype influences the data flow analysis. On the other
  hand, we do not have global data flow analysis results yet because
  we haven't analyzed all calls in the function. It is a chicked-and-egg
  problem.

  The decompiler uses various techniques to guess the called function
  prototype. While it works very well, it may fail in some cases.

  To fix, the user can specify the call prototype manually, using
  "Edit, Operand types, Set operand type" at the call instruction.

  This plugin illustrates another approach to the problem:
  if you happen to be able to calculate the call prototypes dynamically,
  this is how to inform the decompiler about them.

level: intermediate
"""

import ida_idaapi
import ida_nalt
import ida_kernwin
import ida_typeinf
import ida_hexrays

testing = False # only for testing purposes

class callinfo_provider_t(ida_hexrays.Hexrays_Hooks):

    # this callback will be called for all call instructions
    # our plugin may provide the function prototype or even a complete new callinfo
    # object. The callinfo object may be useful if the prototype is not enough
    # to express all details of the call.
    def build_callinfo(self, blk, type):
        # it is a good idea to skip direct calls.
        # note that some indirect calls may be resolved and become direct calls,
        # and will be filtered out here:
        ida_kernwin.msg("%x: got called for: %s\n" % (blk.tail.ea, blk.tail.dstr()))
        tail = blk.tail
        if tail.opcode == ida_hexrays.m_call:
            return

        # also, if the type was specified by the user, do not interfere
        call_ea = tail.ea
        tif = ida_typeinf.tinfo_t()
        if ida_nalt.get_op_tinfo(tif, call_ea, 0):
            return

        global testing
        if not testing:
            # ok, the decompiler really has to guess the type.
            # just for the sake of an example, return a predefined prototype.
            # in real life you will provide the prototype you discovered yourself,
            # using your magic of yours :)
            my_proto = "int f();"
            ida_kernwin.msg("%x: providing prototype %s\n" % (call_ea, my_proto))
            ida_typeinf.parse_decl(type, None, my_proto, 0)
        else:
            # as an alternative to filling 'type', you can
            # choose to return a mcallinfo_t instance.
            mi = ida_hexrays.mcallinfo_t()
            mi.cc = ida_typeinf.CM_CC_STDCALL | ida_typeinf.CM_N32_F48 # let's use stdcall to differentiate from the tinfo_t-filling version
            mi.return_type = ida_typeinf.tinfo_t(ida_typeinf.BT_INT)
            mi.return_argloc.set_reg1(0) # eax
            return mi

# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "Hex-Rays custom prototype provider (IDAPython)"
    wanted_hotkey = ""
    comment = "Sample plugin21 for Hex-Rays decompiler"
    help = ""
    def init(self):
        if ida_hexrays.init_hexrays_plugin():
            self.hooks = callinfo_provider_t()
            self.hooks.hook()
            ida_kernwin.warning(
                "Installed callinfo provider sample (vds21.py)\n" +\
                "Please note that it is just an example\n" +\
                "and will spoil your decompilations!")
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
        self.hooks.unhook()
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return my_plugin_t()

