"""
summary: being notified, and logging some IDP events

description:
  hooks to be notified about certain IDP events, and
  dump their information to the "Output" window
  See enum event_t in idp.hpp for additional.
"""
import inspect
import ida_idp
import ida_ua

class idp_logger_hooks_t(ida_idp.IDP_Hooks):
    def __init__(self):
        ida_idp.IDP_Hooks.__init__(self)
        self.inhibit_log = 0

    def _format_value(self, v):
        return str(v)

    def _log(self, msg=None):
        if self.inhibit_log <= 0:
            if msg:
                print(f'>>> idp_logger_hooks_f: {msg}')
            else:
                stack = inspect.stack()
                frame, _, _, _, _, _ = stack[1]
                args, _, _, values = inspect.getargvalues(frame)
                method_name = inspect.getframeinfo(frame)[2]
                argstrs = []
                for arg in args[1:]:
                    argstrs.append("%s=%s" % (arg, self._format_value(values[arg])))
                print(f'>>> idp_logger_hooks_t.{method_name}: {", ".join(args)}')
        return 0

    def ev_init(self, idp_modname):
        """
        The IDP module is just loaded.
        idp_modname - processor module name
        Returns: <0 on failure
        """
        return self._log(f'idp_logger_hooks_t.ev_init(self, {idp_modname})')

    def ev_term(self):
        """
        The IDP module is being unloaded
        """
        return self._log()

    def ev_newprc(self, nproc, keep_cfg):
        """
        Before changing proccesor type
        nproc - processor number in the array of processor names
        Returns: >=0-ok,<0-prohibit
        """
        return self._log()

    def ev_newasm(self, asmnum):
        """
        Before setting a new assembler.
        """
        return self._log()

    def ev_newfile(self, fname):
        """
        A new file has been loaded.
        """
        return self._log()

    def ev_oldfile(self, filename):
        """
        An old file has been loaded.
        """
        return self._log()

    def ev_newbinary(self, filename, fileoff, basepara, binoff, nbytes):
        """
        Before loading a binary file
        """
        return self._log()

    def ev_endbinary(self, ok):
        """
        After loading a binary file
        Returns: >=0-ok
        """
        return self._log()

    def ev_set_idp_options(self, keyword, value_type, value, idb_loaded):
        """
        Set IDP-specific option
        Returns: 1-ok, 0-not implemented, -1-error
        """
        #msg = f'KEY: {keyword}, TYPE: {value_type}, value: {value}, loaded: {idb_loaded}'
        return self._log()

    def ev_set_proc_options(self, options, confidence):
        """
        Called if the user specified an option string in the command line:
        Returns: <0-bad option string
        """
        return self._log()

    def ev_ana_insn(self, insn):
        """
        Decodes an instruction into insn
        Returns: insn.size (=the size of the decoded instruction) or zero
        """
        self._log()
        return insn.size

    def ev_emu_insn(self, insn):
        """
        Emulate instruction, create cross-references, plan to analyze
        subsequent instructions, modify flags etc. Upon entrance to this function
        all information about the instruction is in 'insn' structure.
        Returns: -1, the kernel will delete the instruction.
        """
        return self._log()

    def ev_out_header(self, ctx):
        """
        Function to produce start of disassembled text
        """
        self._log()

    def ev_out_footer(self, ctx):
        """
        Function to produce end of disassembled text
        """
        self._log()

    def ev_out_segstart(self, ctx, segment):
        """
        Function to produce start of segment
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_out_insn(self, ctx):
        """
        Generate text representation of an instruction in 'ctx.insn' structure.
        This function shouldn't change the database, flags or anything else.
        All these actions should be performed only by u_emu() function.
        Returns: nothing
        """
        self._log()

    def ev_out_segstart(self, ctx, segment):
        """
        Function to produce start of segment
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_out_segend(self, ctx, segment):
        """
        Function to produce end of segment
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_out_assumes(self, ctx):
        """
        Function to produce assume directives
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_out_mnem(self, ctx):
        """
        Generate text representation of an instruction in 'ctx.insn'
        Returns: 1-if appended the mnemonics, 0-not implemented.
        """
        return self._log()

    def ev_out_operand(self, ctx, op):
        """
        Generate text representation of an instructon operand.
        This function shouldn't change the database, flags or anything else.
        All these actions should be performed only by u_emu() function.
        Returns: 1-ok, -1-operand is hidden
        """
        return self._log()

    def ev_out_data(self, ctx, analyze_only):
        """
        Generate text represenation of data items
        This function MAY change the database and create cross-references, etc.
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_out_label(self, ctx, label):
        """
        The kernel is going to generate an instruction label line
        or a function header.
        Returns: <0-the kernel should not generate the label, 0-not implemented/continue
        """
        return self._log()

    def ev_out_special_item(self, ctx, segtype):
        """
        Generate text representation of an item in a special segment
        i.e. absolute symbols, externs, communal definitions etc.
        Returns: 1-ok, 0-not implemented, -1-overflow
        """
        return self._log()

    def ev_gen_stkvar_def(self, ctx, mptr, v):
        """
        Generate stack variable definition line.
        Default line is varname = type ptr value,
        where 'type' is one of byte,word,dword,qword,tbyte.
        Returns: 1-ok, 0-not implemented.
        """
        return self._log()

    def ev_gen_regvar_def(self, ctx, v):
        """
        Generate register variable definition line.
        Returns: >0-ok, 0-not implemented.
        """
        return self._log()

    def ev_gen_src_file_lnnum(self, ctx, filename, lnnum):
        """
        Callback: generate analog of#line "file.c" 123
        directive.
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_creating_segm(self, s):
        """
        A new segment is about to be created.
        Returns 1-ok, <0-should not be created
        """
        return self._log()

    def ev_moving_segm(self, segment, to, flags):
        """
        May the kernel move the segment?
        returns: 0-yes, <0-the kernel should stop
        """
        return self._log()

    def ev_coagulate(self, start_ea):
        """
        Try to define some unexplored bytes
        This notification will be called if the
        kernel tried all possibilities and could
        not find anything more useful than to
        convert to array of bytes.
        The module can help the kernel and convert
        the bytes into something more useful.
        Returns: number of converted bytes
        """
        return self._log()

    def ev_undefine(self, ea):
        """
        An item in the database (insn or data) is being deleted.
        Returns: >=0-ok, <0-the kernel should stop
        """
        return self._log()

    def ev_treat_hindering_item(self, hindering_item_ea, new_item_flags, new_item_ea, new_item_length):
        """
        An item hinders creation of another item
        Returns: 0-no reaction, <0-the kernel may delete the hindering item
        """
        return self._log()

    def ev_rename(self, ea, new_name):
        """
        The kernel is going to rename a byte
        Returns: <0-then the kernel should not rename it
        """
        return self._log()

    def ev_is_far_jump(self, icode):
        """
        Is indirect far jump or call instruction?
        meaningful only if the processor has 'near' and 'far' reference types.
        Returns: 0-not implemented, 1-yes, -1-no
        """
        return self._log()

    def ev_is_sane_insn(self, insn, no_crefs):
        """
        Is the instruction sane for the current file type?
        Returns: >=0-ok, <0-no
        """
        return self._log()

    def ev_is_cond_insn(self, insn):
        """
        Is conditional instruction?
        Returns: 1-yes, -1-no, 0-not implemented.
        """
        return self._log()

    def ev_is_call_insn(self, insn):
        """
        Is the instruction a "call"?
        Returns: 0-unknown, <0-no, 1-yes
        """
        return self._log()

    def ev_is_ret_insn(self, insn, flags):
        """
        Is the instruction a "return"?
        Returns: 0-unknown, <0-no, 1-yes
        """
        return self._log()

    def ev_may_be_func(self, insn, state):
        """
        Can a function start here?
        Returns: probability 0..100
        """
        return self._log()

    def ev_is_basic_block_end(self, insn, call_insn_stops_block):
        """
        Is the current instruction end of a basic block?
        Returns: 0-unknown, -1-no, 1-yes
        """
        return self._log()

    def ev_is_indirect_jump(self, insn):
        """
        Callback: determine if instruction is an indrect jump
        If CF_JUMP bit cannot describe all jump types
        jumps, please define this callback.
        Returns: 0-use CF_JUMP, 1-no, 2-yes
        """
        return self._log()

    def ev_is_switch(self, swi, insn):
        """
        Find 'switch' idiom or override processor module's
        decision.
        Returns: 1-switch found, -1-no switch found, 0-not implmented
        """
        return self._log()

    def ev_calc_switch_cases(self, casevec, targets, insn_ea, si):
        """
        Calculate case values and targets for a custom jump table.
        Returns: 1-ok, <=0-failed
        """
        return self._log()

    def ev_create_switch_xrefs(self, jumpea, swi):
        """
        Create xrefs for a custom jump table
        Must return 1
        """
        self._log()
        return 1

    def ev_is_align_insn(self, ea):
        """
        Is the instruction created only for alignment purposes?
        Returns: number of bytes in the instruction
        """
        return self._log()

    def ev_is_alloca_probe(self, ea):
        """
        Does the function at 'ea' behave as __alloca_probe?
        Returns: 1-yes, 0-no
        """
        return self._log()

    def ev_delay_slot_insn(self, ea, bexec, fexec):
        """
        Get delay slot instruction.
        Returns 1-yes, <=O-ordianry insn.
        """
        return self._log()

    def ev_is_sp_based(self, mode, insn, op):
        """
        Check whether the operand is relative to stack pointer or frame pointer.
        This function is used to determine how to output a stack variable
        This function may be absent. If it is absent, then all operands
        are sp based by default.
        Define this function only if some stack references use frame pointer
        instead of stack pointer.
        Returns: 0-not implemented, 1-ok
        """
        return self._log()

    def ev_can_have_type(self, op):
        """
        Can the operand have a type as offset, segment, decimal, etc?
        (for example, a register AX can't have a type, meaning that the user can't
        change its representation. see bytes.hpp for information about types and flags)
        Returns: 0-unknown, <0-no, 1-yes
        """
        return self._log()

    def ev_cmp_operands(self, op1, op2):
        """
        Compare instruction operands.
        Returns 1-equal, -1-not equal, 0-not implemented
        """
        return self._log()

    def ev_adjust_refinfo(self, ri, ea, n, fd):
        """
        Called from apply_fixup before converting operand to reference.
        Can be used for changing the reference info.
        Returns: <0-do not create an offset, 0-not implemented
        """
        return self._log()

    def ev_get_operand_string(self, buf, insn, opnum):
        """
        Request text string for operand.
        Returns: 0-no string (or empty), >0-original string length (without final 0)
        """
        return self._log()

    def ev_get_reg_name(self, buf, reg, width, reghi):
        """
        Generate text representation of a register.
        Most processor modules do not need to implement this callback.
        Returns: -1-error, strlen(buf)-success
        """
        return self._log()

    def ev_str2reg(self, regname):
        """
        Convert a register name to a register number
        Returns: 0-not implemented, register number + 1
        """
        return self._log()

    def ev_get_autocmt(self, insn):
        """
        Get instruction comment. 'insn' describes the instruction in question
        Returns: 1-new comment has been generated, 0-not been handled.
        """
        return self._log()

    def ev_get_bg_color(self, color, ea):
        """
        Get item background color.
        Plugins can hook this callback to color disassembly lines dynamically.
        Returns: 0-not implemented, 1-color set
        """
        return self._log()

    def ev_is_jump_func(self, pfn, jump_target, func_pointer):
        """
        Is the function a trivial "jump" function?
        Returns: <0-no, 0-don't know, 1-yes
        """
        return self._log()

    def ev_func_bounds(self, possible_return_code, pfn, max_func_end_ea):
        """
        Find_func_bounds() finished its work.
        The module may fine tune the function bounds.
        Returns: None
        """
        return self._log()

    def ev_verify_sp(self, pfn):
        """
        All function instructions have been analyzed.
        Now the processor module can analyze the stack pointer
        for the whole function
        Returns: 0-ok, <0-bad stack pointer
        """
        return self._log()

    def ev_verify_noreturn(self, pfn):
        """
        The kernel wants to set 'noreturn' flags for a function
        Returns: 0-ok, <0-do not set 'noreturn' flag
        """
        return self._log()

    def ev_create_func_frame(self, pfn):
        """
        Create a function frame for a newly created function.
        Set up frame size, its attributes etc.
        Returns: 1-ok, 0-not implemented
        """
        return self._log()

    def ev_get_frame_retsize(self, frsize, pfn):
        """
        Get size of function return address in bytes
        If this function is absent, the kernel will assume
        8 bytes for 64-bit function,4 bytes for 32-bit
        function, c2 bytes otherwise.
        Returns 1-ok, 0-not implemented
        """
        return self._log()

    def ev_get_stkvar_scale_factor(self):
        """
        Should stack variable references be multiplied by
        a coefficient before being used in the stack frame?
        Currently used by TMS320C55 because the references into
        the stack should be multiplied by 2.
        Returns: 0-not implemented, >0-scaling factor
        """
        return self._log()

    def ev_demangle_name(self, out, res , demreq):
        """
        Demangle a C++ (or another language) name into a user-readable string.
        This event is called by ::demangle_name().
        Returns: 0-not implemented, 1-success
        """
        return self._log()

    def ev_add_cref(self, frm, to, type):
        """
        A code reference is being created.
        Returns: 0-not implemented, <0-cancel cref creation
        """
        return self._log()

    def ev_add_dref(self, frm, to, type):
        """
        A data reference is being created.
        Returns: 0-not implemented, <0-cancel dref creation
        """
        return self._log()

    def ev_del_cref(self, frm, to, expand):
        """
        A code reference is being deleted.
        Returns: 0-not implemented, <0-cancel cref deletion
        """
        return self._log()

    def ev_del_dref(self, frm, to):
        """
        A data reference is being deleted.
        Retuns: 0-not implemented, <0-cancel dref deletion
        """
        return self._log()

    def ev_coagulate_dref(self, from_ea, to_ea, may_define, code_ea):
        """
        Data reference is being analyzed.
        plugin may correct code_ea (e.g. for thumb mode refs, we clear the last bit)
        Returns: >0-new code_ea, -1-cancel dref analysis
        """
        return self._log()

    def ev_may_show_sreg(self, current_ea):
        """
        The kernel wants to display the segment registers
        in the messages window.
        Returns: 0-not implemented, <0-kernel should not show.
        """
        return self._log()

    def ev_loader_elf_machine(self, li, machine_type, p_procname, p_pd, loader, reader):
        """
        ELF loader machine type checkpoint.
        A plugin check of the 'machine_type'. If it is the desired one,
        the the plugin fills 'p_procname' with the processor name.
        Returns: e_machine value
        """
        pass

    def ev_auto_queue_empty(self, atype):
        """
        One analysis queue is empty.
        Returns: an int (?)
        """
        return self._log()

    def ev_validate_flirt_func(self, start_ea, func_name):
        """
        FLIRT has recognized a library function.
        This callback can be used by a plugin or proc module
        to intercept it and validate such a function.
        Returns: -1-do not create, 0-validated
        """
        return self._log()

    def ev_adjust_libfunc_ea(self, sig, libfun, ea):
        """
        Called when a signature module has been matched against
        bytes in the database. This is used to compute the
        offset at which a particular module's libfunc should
        be applied.
        Returns: 1-ea has been modified, <=0 not modified
        """
        return self._log()

    def ev_assemble(self, ea, cs, ip, use32, line):
        """
        Assemble an instruction
        (make sure that PR_ASSEMBLE flag is set in the processor flags)
        (display a warning if an error occurs)
        Returns opcode string-ok, None-failed
        """
        return self._log()

    def ev_extract_address(self, out_ea, screen_ea, string, position):
        """
        Extract address from a string.
        Returns: 1-ok, 0-use standard algorithm, -1-error
        """
        return self._log()

    def ev_realcvt(self, m,e, swt):
        """
        Floating point -> IEEE conversion
        Returns: 1-ok, 0-not implemented, REAL_ERROR_-error
        """
        return self._log()

    def ev_gen_asm_or_lst(self, starting, fp, is_asm, flags, outline):
        """
        Callback generating asm orlst file.
        The kernel calls this callback twice, at the beginning
        and at the end of listing generation. The processor
        module can intercept this event and adjust its output.
        Returns: None
        """
        return self._log()

    def ev_gen_map_file(self, nlines, fp):
        """
        Generate a map file. If not implemented
        the kernel itself will create the map file.
        Returns: 1-ok, 0-not implemented, -1-write error
        """
        return self._log()

    def ev_create_flat_group(self, image_base, bitness, dataseg_sel):
        """
        Create special segment representing the flat group.
        Returns: return value ignored.
        """
        return self._log()

    def ev_analyze_prolog(self, ea):
        """
        Analyzes function prolog, epilog, and updates
        purge, and function attributes
        Returns: 1-ok, 0-not implemented.
        """
        return self._log()


# Remove an existing hook on second run
try:
    idp_hook_stat = "un"
    print("IDP hook: checking for hook...")
    idphook
    print("IDP hook: unhooking....")
    idp_hook_stat2 = ""
    idphook.unhook()
    del idphook
except:
    print("IDP hook: not installed, installing now....")
    idp_hook_stat = ""
    idp_hook_stat2 = "un"
    idphook = idp_logger_hooks_t()
    idphook.hook()

print(f'IDP hook {idp_hook_stat}installed. Run the script again to {idp_hook_stat2}install')
