diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 0000000..f9d134a --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,13 @@ +The IDAPython Team: + +* Gergely Erdelyi - http://d-dome.net/idapython/ + + Original IDAPython author. + +* Hex-Rays - http://www.hex-rays.com/ + + Hex-Rays joined the project in September 2009 and started contributing. + +* Ero Carrera - http://dkbza.org/ + + Project contributor \ No newline at end of file diff --git a/build.py b/build.py index b79bad7..852a0ea 100644 --- a/build.py +++ b/build.py @@ -36,7 +36,7 @@ else: # IDAPython version VERSION_MAJOR = 1 VERSION_MINOR = 4 -VERSION_PATCH = 0 +VERSION_PATCH = 1 # Determine Python version PYTHON_MAJOR_VERSION = int(platform.python_version()[0]) @@ -66,12 +66,15 @@ BINDIST_MANIFEST = [ "README.txt", "COPYING.txt", "CHANGES.txt", + "AUTHORS.txt", "STATUS.txt", "docs/notes.txt", "examples/chooser.py", "examples/colours.py", "examples/debughook.py", + "examples/ex_cli.py", "examples/ex1.idc", + "examples/ex_custdata.py", "examples/ex1_idaapi.py", "examples/ex1_idautils.py", "examples/hotkey.py", diff --git a/examples/ex_cli.py b/examples/ex_cli.py new file mode 100644 index 0000000..3b31424 --- /dev/null +++ b/examples/ex_cli.py @@ -0,0 +1,100 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to implement a CLI +# (c) Hex-Rays +# +from idaapi import NW_OPENIDB, NW_CLOSEIDB, NW_TERMIDA, NW_REMOVE, COLSTR, cli_t + +# +class mycli_t(cli_t): + flags = 0 + sname = "pycli" + lname = "Python CLI" + hint = "pycli hint" + + def OnExecuteLine(self, line): + """ + The user pressed Enter. The CLI is free to execute the line immediately or ask for more lines. + + This callback is mandatory. + + @param line: typed line(s) + @return Boolean: True-executed line, False-ask for more lines + """ + print "OnExecute:", line + return True + + def OnKeydown(self, line, x, sellen, vkey, shift): + """ + A keyboard key has been pressed + This is a generic callback and the CLI is free to do whatever it wants. + + This callback is optional. + + @param line: current input line + @param x: current x coordinate of the cursor + @param sellen: current selection length (usually 0) + @param vkey: virtual key code. if the key has been handled, it should be returned as zero + @param shift: shift state + + @return: + None - Nothing was changed + tuple(line, x, sellen, vkey): if either of the input line or the x coordinate or the selection length has been modified. + It is possible to return a tuple with None elements to preserve old values. Example: tuple(new_line, None, None, None) or tuple(new_line) + """ + print "Onkeydown: line=%s x=%d sellen=%d vkey=%d shift=%d" % (line, x, sellen, vkey, shift) + return None + + def OnCompleteLine(self, prefix, n, line, prefix_start): + """ + The user pressed Tab. Find a completion number N for prefix PREFIX + + This callback is optional. + + @param prefix: Line prefix at x (string) + @param n: completion number (int) + @param line: the current line (string) + @param prefix_start: the index where PREFIX starts in LINE (int) + + @return: None if no completion could be generated otherwise a String with the completion suggestion + """ + print "OnCompleteLine: prefix=%s n=%d line=%s prefix_start=%d" % (prefix, n, line, prefix_start) + return None +# + + +# ----------------------------------------------------------------------- +def nw_handler(code, old=0): + if code == NW_OPENIDB: + print "nw_handler(): installing CLI" + mycli.register() + elif code == NW_CLOSEIDB: + print "nw_handler(): removing CLI" + mycli.unregister() + elif code == NW_TERMIDA: + print "nw_handler(): uninstalled nw handler" + idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB | NW_REMOVE, nw_handler) + +# ----------------------------------------------------------------------- + +# Already installed? +try: + mycli + # remove previous CLI + mycli.unregister() + del mycli + # remove previous handler + nw_handler(NW_TERMIDA) +except: + pass +finally: + mycli = mycli_t() + +# register CLI +if mycli.register(): + print "CLI installed" + # install new handler + idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB, nw_handler) +else: + del mycli + print "Failed to install CLI" + diff --git a/examples/ex_custdata.py b/examples/ex_custdata.py new file mode 100644 index 0000000..c5e8c70 --- /dev/null +++ b/examples/ex_custdata.py @@ -0,0 +1,216 @@ +# ----------------------------------------------------------------------- +# This is an example illustrating how to use custom data types in Python +# (c) Hex-Rays +# +from idaapi import data_type_t, data_format_t, NW_OPENIDB, NW_CLOSEIDB, NW_TERMIDA, NW_REMOVE, COLSTR +import struct +import ctypes +import platform + +# + +# ----------------------------------------------------------------------- +class pascal_data_type(data_type_t): + def __init__(self): + data_type_t.__init__(self, name="py_pascal_string", + value_size = 2, menu_name = "Pascal string", + asm_keyword = "pstr") + + def calc_item_size(self, ea, maxsize): + # Custom data types may be used in structure definitions. If this case + # ea is a member id. Check for this situation and return 1 + if _idaapi.is_member_id(ea): + return 1 + + # get the length byte + n = _idaapi.get_byte(ea) + + # string too big? + if n > maxsize: + return 0 + # ok, accept the string + return n + 1 + +class pascal_data_format(data_format_t): + FORMAT_NAME = "py_pascal_string_pstr" + def __init__(self): + data_format_t.__init__(self, name=pascal_data_format.FORMAT_NAME) + + def printf(self, value, current_ea, operand_num, dtid): + # Take the length byte + n = ord(value[0]) + o = ['"'] + for ch in value[1:]: + b = ord(ch) + if b < 0x20 or b > 128: + o.append(r'\x%02x' % ord(ch)) + else: + o.append(ch) + o.append('"') + return "".join(o) + +# ----------------------------------------------------------------------- +class simplevm_data_type(data_type_t): + ASM_KEYWORD = "svm_emit" + def __init__(self): + data_type_t.__init__(self, + name="py_simple_vm", + value_size = 1, + menu_name = "SimpleVM", + asm_keyword = simplevm_data_type.ASM_KEYWORD) + + def calc_item_size(self, ea, maxsize): + if _idaapi.is_member_id(ea): + return 1 + # get the opcode and see if it has an imm + n = 5 if (_idaapi.get_byte(ea) & 3) == 0 else 1 + # string too big? + if n > maxsize: + return 0 + # ok, accept + return n + +class simplevm_data_format(data_format_t): + def __init__(self): + data_format_t.__init__(self, + name="py_simple_vm_format", + menu_name = "SimpleVM") + + # Some tables for the disassembler + INST = {1: 'add', 2: 'mul', 3: 'sub', 4: 'xor', 5: 'mov'} + REGS = {1: 'r1', 2: 'r2', 3: 'r3'} + def disasm(self, inst): + """A simple local disassembler. In reality one can use a full-blown disassembler to render the text""" + opbyte = ord(inst[0]) + op = opbyte >> 4 + if not (1<=op<=5): + return None + r1 = (opbyte & 0xf) >> 2 + r2 = opbyte & 3 + sz = 0 + if r2 == 0: + if len(inst) != 5: + return None + imm = struct.unpack_from('L', inst, 1)[0] + sz = 5 + else: + imm = None + sz = 1 + text = "%s %s, %s" % ( + COLSTR(simplevm_data_format.INST[op], idaapi.SCOLOR_INSN), + COLSTR(simplevm_data_format.REGS[r1], idaapi.SCOLOR_REG), + COLSTR("0x%08X" % imm, idaapi.SCOLOR_NUMBER) if imm is not None else COLSTR(simplevm_data_format.REGS[r2], idaapi.SCOLOR_REG)) + return (sz, text) + + def printf(self, value, current_ea, operand_num, dtid): + r = self.disasm(value) + if not r: + return None + if dtid == 0: + return "%s(%s)" % (simplevm_data_type.ASM_KEYWORD, r[1]) + return r[1] + +# ----------------------------------------------------------------------- +# This format will display DWORD values as MAKE_DWORD(0xHI, 0xLO) +class makedword_data_format(data_format_t): + def __init__(self): + data_format_t.__init__(self, + name="py_makedword", + value_size = 4, + menu_name = "Make DWORD") + + def printf(self, value, current_ea, operand_num, dtid): + if len(value) != 4: return None + w1 = struct.unpack_from("H", value, 0)[0] + w2 = struct.unpack_from("H", value, 2)[0] + return "MAKE_DWORD(0x%04X, 0x%04X)" % (w2, w1) + +# ----------------------------------------------------------------------- +# This format will try to load a resource string given a number +# So instead of displaying: +# push 66h +# call message_box_from_rsrc_string +# It can be rendered as; +# push RSRC("The message") +# call message_box_from_rsrc_string +# +# The get_rsrc_string() is not optimal since it loads/unloads the +# DLL each time for a new string. It can be improved in many ways. +class rsrc_string_format(data_format_t): + def __init__(self): + data_format_t.__init__(self, + name="py_w32rsrcstring", + value_size = 1, + menu_name = "Resource string") + self.cache_node = idaapi.netnode("$ py_w32rsrcstring", 0, 1) + + def get_rsrc_string(self, fn, id): + """ + Simple method that loads the input file as a DLL with LOAD_LIBRARY_AS_DATAFILE flag. + It then tries to LoadString() + """ + k32 = ctypes.windll.kernel32 + u32 = ctypes.windll.user32 + + hinst = k32.LoadLibraryExA(fn, 0, 0x2) + if hinst == 0: + return "" + buf = ctypes.create_string_buffer(1024) + r = u32.LoadStringA(hinst, id, buf, 1024-1) + k32.FreeLibrary(hinst) + return buf.value if r else "" + + def printf(self, value, current_ea, operand_num, dtid): + # Is it already cached? + val = self.cache_node.supval(current_ea) + + # Not cached? + if val == None: + # Retrieve it + num = idaapi.struct_unpack(value) + val = self.get_rsrc_string(idaapi.get_input_file_path(), num) + # Cache it + self.cache_node.supset(current_ea, val) + + # Failed to retrieve? + if val == "" or val == "\x00": + return None + # Return the format + return "RSRC_STR(\"%s\")" % COLSTR(val, idaapi.SCOLOR_IMPNAME) + +# ----------------------------------------------------------------------- +# Table of formats and types to be registered/unregistered +# If a tuple has one element then it is the format to be registered with dtid=0 +# If the tuple has more than one element, the tuple[0] is the data type and tuple[1:] are the data formats +new_formats = [ + (pascal_data_type(), pascal_data_format()), + (simplevm_data_type(), simplevm_data_format()), + (makedword_data_format(),), + (simplevm_data_format(),) +] + +if platform.system() == 'Windows': + new_formats.append((rsrc_string_format(),)) + +# + +# ----------------------------------------------------------------------- +def nw_handler(code, old=0): + # delete notifications + if code == NW_OPENIDB: + idaapi.register_data_types_and_formats(new_formats) + elif code == NW_CLOSEIDB: + idaapi.unregister_data_types_and_formats(new_formats) + elif code == NW_TERMIDA: + idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB | NW_REMOVE, nw_handler) + +# ----------------------------------------------------------------------- +# Check if already installed +if idaapi.find_custom_data_type(pascal_data_format.FORMAT_NAME) == -1: + if not idaapi.register_data_types_and_formats(new_formats): + print "Failed to register types!" + else: + idaapi.notify_when(NW_TERMIDA | NW_OPENIDB | NW_CLOSEIDB, nw_handler) + print "Formats installed!" +else: + print "Formats already installed!" diff --git a/python/init.py b/python/init.py index 1b7ad65..6402ac3 100644 --- a/python/init.py +++ b/python/init.py @@ -39,16 +39,27 @@ class IDAPythonStdOut: def isatty(self): return False +# ----------------------------------------------------------------------- +def runscript(script): + """ + Executes a script. + This function is present for backward compatiblity. Please use idaapi.IDAPython_ExecScript() instead + + @param script: script path + + @return: Error string or None on success + """ + + import idaapi + return idaapi.IDAPython_ExecScript(script, globals()) + # ----------------------------------------------------------------------- def print_banner(): banner = [ - "Python interpreter version %d.%d.%d %s (serial %d)" % sys.version_info, - "Copyright (c) 1990-2010 Python Software Foundation - http://www.python.org/", - "", - "IDAPython" + (" 64-bit" if __EA64__ else "") + " version %d.%d.%d %s (serial %d)" % IDAPYTHON_VERSION, - "Copyright (c) 2004-2010 Gergely Erdelyi - http://code.google.com/p/idapython/" + "Python %d.%d.%d %s (serial %d) (c) 1990-2010 Python Software Foundation" % sys.version_info, + "IDAPython" + (" 64-bit" if __EA64__ else "") + " v%d.%d.%d %s (serial %d) (c) The IDAPython Team " % IDAPYTHON_VERSION ] - sepline = '-' * max([len(s) for s in banner]) + sepline = '-' * (max([len(s) for s in banner])+1) print sepline print "\n".join(banner) diff --git a/swig/graph.i b/swig/graph.i index 97dcda8..a349702 100644 --- a/swig/graph.i +++ b/swig/graph.i @@ -726,7 +726,7 @@ void pyg_select_node(PyObject *self, int nid) } // %} -#endif +#endif // __NT__ #ifdef __NT__ %inline %{ @@ -739,9 +739,8 @@ void pyg_select_node(PyObject *self, int nid); bool pyg_show(PyObject *self); // %} -#endif +#endif // __NT__ -#ifdef __NT__ %pythoncode %{ # class GraphViewer: @@ -907,4 +906,4 @@ class GraphViewer: # # %} -#endif + diff --git a/swig/idaapi.i b/swig/idaapi.i index 13b335b..7a5f675 100644 --- a/swig/idaapi.i +++ b/swig/idaapi.i @@ -1919,6 +1919,7 @@ def IDAPython_ExecScript(script, g): execfile(script, g) except Exception, e: PY_COMPILE_ERR = str(e) + "\n" + traceback.format_exc() + print PY_COMPILE_ERR finally: # Restore the globals to the state before the script was run g['__file__'] = old__file__ @@ -2132,7 +2133,5 @@ static bool notify_when(int when, PyObject *py_callable) %include "typeinf.i" %include "ua.i" %include "xref.i" -#ifdef __NT__ - %include "graph.i" -#endif +%include "graph.i" %include "fpro.i" diff --git a/swig/idp.i b/swig/idp.i index 424299c..599cd9a 100644 --- a/swig/idp.i +++ b/swig/idp.i @@ -143,21 +143,6 @@ static PyObject *AssembleLine(ea_t ea, ea_t cs, ea_t ip, bool use32, const char Py_RETURN_NONE; } -//------------------------------------------------------------------------- -/* -# -def ph_get_tbyte_size(): - """ - Returns the 'ph.tbyte_size' field as defined in he processor module - """ - pass -# -*/ -static size_t ph_get_tbyte_size() -{ - return ph.tbyte_size; -} - //------------------------------------------------------------------------- /* # @@ -173,6 +158,216 @@ static size_t ph_get_id() return ph.id; } +//------------------------------------------------------------------------- +/* +# +def ph_get_version(): + """ + Returns the 'ph.version' + """ + pass +# +*/ +static size_t ph_get_version() +{ + return ph.version; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_flag(): + """ + Returns the 'ph.flag' + """ + pass +# +*/ +static size_t ph_get_flag() +{ + return ph.flag; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_cnbits(): + """ + Returns the 'ph.cnbits' + """ + pass +# +*/ +static size_t ph_get_cnbits() +{ + return ph.cnbits; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_dnbits(): + """ + Returns the 'ph.dnbits' + """ + pass +# +*/ +static size_t ph_get_dnbits() +{ + return ph.dnbits; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_regFirstSreg(): + """ + Returns the 'ph.regFirstSreg' + """ + pass +# +*/ +static size_t ph_get_regFirstSreg() +{ + return ph.regFirstSreg; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_regLastSreg(): + """ + Returns the 'ph.regLastSreg' + """ + pass +# +*/ +static size_t ph_get_regLastSreg() +{ + return ph.regLastSreg; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_segreg_size(): + """ + Returns the 'ph.segreg_size' + """ + pass +# +*/ +static size_t ph_get_segreg_size() +{ + return ph.segreg_size; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_regCodeSreg(): + """ + Returns the 'ph.regCodeSreg' + """ + pass +# +*/ +static size_t ph_get_regCodeSreg() +{ + return ph.regCodeSreg; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_regDataSreg(): + """ + Returns the 'ph.regDataSreg' + """ + pass +# +*/ +static size_t ph_get_regDataSreg() +{ + return ph.regDataSreg; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_high_fixup_bits(): + """ + Returns the 'ph.high_fixup_bits' + """ + pass +# +*/ +static size_t ph_get_high_fixup_bits() +{ + return ph.high_fixup_bits; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_icode_return(): + """ + Returns the 'ph.icode_return' + """ + pass +# +*/ +static size_t ph_get_icode_return() +{ + return ph.icode_return; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_instruc_start(): + """ + Returns the 'ph.instruc_start' + """ + pass +# +*/ +static size_t ph_get_instruc_start() +{ + return ph.instruc_start; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_instruc_end(): + """ + Returns the 'ph.instruc_end' + """ + pass +# +*/ +static size_t ph_get_instruc_end() +{ + return ph.instruc_end; +} + +//------------------------------------------------------------------------- +/* +# +def ph_get_tbyte_size(): + """ + Returns the 'ph.tbyte_size' field as defined in he processor module + """ + pass +# +*/ +static size_t ph_get_tbyte_size() +{ + return ph.tbyte_size; +} + //------------------------------------------------------------------------- /* # diff --git a/swig/kernwin.i b/swig/kernwin.i index 07da901..0659747 100644 --- a/swig/kernwin.i +++ b/swig/kernwin.i @@ -22,8 +22,12 @@ %ignore skipSpaces; %ignore stristr; -// Ignore the cli_t class +// CLI %ignore cli_t; +%ignore install_command_interpreter; +%rename (install_command_interpreter) py_install_command_interpreter; +%ignore remove_command_interpreter; +%rename (remove_command_interpreter) py_remove_command_interpreter; %include "typemaps.i" @@ -755,6 +759,262 @@ PyObject *choose2_find(const char *title) #ifdef __NT__ %{ +// +//-------------------------------------------------------------------------- +#define MAX_PY_CLI 12 + +// Callbacks table +// This structure was devised because the cli callbacks have no user-data parameter +struct py_cli_cbs_t +{ + bool (idaapi *execute_line)(const char *line); + bool (idaapi *complete_line)( + qstring *completion, + const char *prefix, + int n, + const char *line, + int x); + bool (idaapi *keydown)( + qstring *line, + int *p_x, + int *p_sellen, + uint16 *vk_key, + int shift); +}; + +// CLI Python wrapper class +class py_cli_t +{ +private: + //-------------------------------------------------------------------------- + cli_t cli; + PyObject *self; + qstring cli_sname, cli_lname, cli_hint; + + //-------------------------------------------------------------------------- + static py_cli_t *py_clis[MAX_PY_CLI]; + static const py_cli_cbs_t py_cli_cbs[MAX_PY_CLI]; + //-------------------------------------------------------------------------- +#define IMPL_PY_CLI_CB(CBN) \ + static bool idaapi s_keydown##CBN(qstring *line, int *p_x, int *p_sellen, uint16 *vk_key, int shift) \ + { \ + return py_clis[CBN]->on_keydown(line, p_x, p_sellen, vk_key, shift); \ + } \ + static bool idaapi s_execute_line##CBN(const char *line) \ + { \ + return py_clis[CBN]->on_execute_line(line); \ + } \ + static bool idaapi s_complete_line##CBN(qstring *completion, const char *prefix, int n, const char *line, int x) \ + { \ + return py_clis[CBN]->on_complete_line(completion, prefix, n, line, x); \ + } + + IMPL_PY_CLI_CB(0); IMPL_PY_CLI_CB(1); IMPL_PY_CLI_CB(2); IMPL_PY_CLI_CB(3); + IMPL_PY_CLI_CB(4); IMPL_PY_CLI_CB(5); IMPL_PY_CLI_CB(6); IMPL_PY_CLI_CB(7); + IMPL_PY_CLI_CB(8); IMPL_PY_CLI_CB(9); IMPL_PY_CLI_CB(10); IMPL_PY_CLI_CB(11); +#undef IMPL_PY_CLI_CB + + //-------------------------------------------------------------------------- + // callback: the user pressed Enter + // CLI is free to execute the line immediately or ask for more lines + // Returns: true-executed line, false-ask for more lines + bool on_execute_line(const char *line) + { + PyObject *result = PyObject_CallMethod(self, (char *)S_ON_EXECUTE_LINE, "s", line); + bool ok = result != NULL && PyObject_IsTrue(result); + PyW_ShowErr(S_ON_EXECUTE_LINE); + Py_XDECREF(result); + return ok; + } + + //-------------------------------------------------------------------------- + // callback: a keyboard key has been pressed + // This is a generic callback and the CLI is free to do whatever + // it wants. + // line - current input line (in/out argument) + // p_x - pointer to current x coordinate of the cursor (in/out) + // p_sellen - pointer to current selection length (usually 0) + // p_vk_key - pointer to virtual key code (in/out) + // if the key has been handled, it should be reset to 0 by CLI + // shift - shift state + // Returns: true-modified input line or x coordinate or selection length + // This callback is optional + bool on_keydown( + qstring *line, + int *p_x, + int *p_sellen, + uint16 *vk_key, + int shift) + { + PyObject *result = PyObject_CallMethod( + self, + (char *)S_ON_KEYDOWN, + "siiHi", + line->c_str(), + *p_x, + *p_sellen, + *vk_key, + shift); + + bool ok = result != NULL && PyTuple_Check(result); + + PyW_ShowErr(S_ON_KEYDOWN); + + if ( ok ) + { + Py_ssize_t sz = PyTuple_Size(result); + PyObject *item; + + if ( sz > 0 && (item = PyTuple_GetItem(result, 0)) != NULL && PyString_Check(item) ) + *line = PyString_AsString(item); + + if ( sz > 1 && (item = PyTuple_GetItem(result, 1)) != NULL && PyInt_Check(item) ) + *p_x = PyInt_AsLong(item); + + if ( sz > 2 && (item = PyTuple_GetItem(result, 2)) != NULL && PyInt_Check(item) ) + *p_sellen = PyInt_AsLong(item); + + if ( sz > 3 && (item = PyTuple_GetItem(result, 3)) != NULL && PyInt_Check(item) ) + *vk_key = PyInt_AsLong(item) & 0xffff; + } + + Py_XDECREF(result); + return ok; + } + + // callback: the user pressed Tab + // Find a completion number N for prefix PREFIX + // LINE is given as context information. X is the index where PREFIX starts in LINE + // New prefix should be stored in PREFIX. + // Returns: true if generated a new completion + // This callback is optional + bool on_complete_line( + qstring *completion, + const char *prefix, + int n, + const char *line, + int x) + { + PyObject *result = PyObject_CallMethod(self, (char *)S_ON_COMPLETE_LINE, "sisi", prefix, n, line, x); + bool ok = result != NULL && PyString_Check(result); + PyW_ShowErr(S_ON_COMPLETE_LINE); + if ( ok ) + *completion = PyString_AsString(result); + + Py_XDECREF(result); + return ok; + } + + // Private ctor (use bind()) + py_cli_t() + { + } + +public: + //--------------------------------------------------------------------------- + static int bind(PyObject *py_obj) + { + int cli_idx; + // Find an empty slot + for ( cli_idx = 0; cli_idx < MAX_PY_CLI; ++cli_idx ) + { + if ( py_clis[cli_idx] == NULL ) + break; + } + py_cli_t *py_cli = NULL; + do + { + // No free slots? + if ( cli_idx >= MAX_PY_CLI ) + break; + + // Create a new instance + py_cli = new py_cli_t(); + PyObject *attr; + + // Start populating the 'cli' member + py_cli->cli.size = sizeof(cli_t); + + // Store 'flags' + if ( (attr = PyW_TryGetAttrString(py_obj, S_FLAGS)) == NULL ) + { + py_cli->cli.flags = 0; + } + else + { + py_cli->cli.flags = PyLong_AsLong(attr); + Py_DECREF(attr); + } + + // Store 'sname' + if ( !PyW_GetStringAttr(py_obj, "sname", &py_cli->cli_sname) ) + break; + py_cli->cli.sname = py_cli->cli_sname.c_str(); + + // Store 'lname' + if ( !PyW_GetStringAttr(py_obj, "lname", &py_cli->cli_lname) ) + break; + py_cli->cli.lname = py_cli->cli_lname.c_str(); + + // Store 'hint' + if ( !PyW_GetStringAttr(py_obj, "hint", &py_cli->cli_hint) ) + break; + py_cli->cli.hint = py_cli->cli_hint.c_str(); + + // Store callbacks + if ( !PyObject_HasAttrString(py_obj, S_ON_EXECUTE_LINE) ) + break; + py_cli->cli.execute_line = py_cli_cbs[cli_idx].execute_line; + + py_cli->cli.complete_line = PyObject_HasAttrString(py_obj, S_ON_COMPLETE_LINE) ? py_cli_cbs[cli_idx].complete_line : NULL; + py_cli->cli.keydown = PyObject_HasAttrString(py_obj, S_ON_KEYDOWN) ? py_cli_cbs[cli_idx].keydown : NULL; + + // install CLI + install_command_interpreter(&py_cli->cli); + + // Take reference to this object + py_cli->self = py_obj; + Py_INCREF(py_obj); + + // Save the instance + py_clis[cli_idx] = py_cli; + + return cli_idx; + } while (false); + + delete py_cli; + return -1; + } + + //--------------------------------------------------------------------------- + static void unbind(int cli_idx) + { + // Out of bounds or not set? + if ( cli_idx < 0 || cli_idx >= MAX_PY_CLI || py_clis[cli_idx] == NULL ) + return; + + py_cli_t *py_cli = py_clis[cli_idx]; + remove_command_interpreter(&py_cli->cli); + + Py_DECREF(py_cli->self); + delete py_cli; + + py_clis[cli_idx] = NULL; + + return; + } +}; +py_cli_t *py_cli_t::py_clis[MAX_PY_CLI] = {NULL}; +#define DECL_PY_CLI_CB(CBN) { s_execute_line##CBN, s_complete_line##CBN, s_keydown##CBN } +const py_cli_cbs_t py_cli_t::py_cli_cbs[MAX_PY_CLI] = +{ + DECL_PY_CLI_CB(0), DECL_PY_CLI_CB(1), DECL_PY_CLI_CB(2), DECL_PY_CLI_CB(3), + DECL_PY_CLI_CB(4), DECL_PY_CLI_CB(5), DECL_PY_CLI_CB(6), DECL_PY_CLI_CB(7), + DECL_PY_CLI_CB(8), DECL_PY_CLI_CB(9), DECL_PY_CLI_CB(10), DECL_PY_CLI_CB(11) +}; +#undef DECL_PY_CLI_CB +// + // //--------------------------------------------------------------------------- // Base class for all custviewer place_t providers @@ -1619,6 +1879,18 @@ public: #ifdef __NT__ %inline %{ +// +static int py_install_command_interpreter(PyObject *py_obj) +{ + return py_cli_t::bind(py_obj); +} + +static void py_remove_command_interpreter(int cli_idx) +{ + py_cli_t::unbind(cli_idx); +} +// + // // // Pywraps Simple Custom Viewer functions @@ -2348,8 +2620,112 @@ class Choose2(object): # %} -#ifdef __NT__ %pythoncode %{ +# +class cli_t(pyidc_opaque_object_t): + """ + cli_t wrapper class. + + This class allows you to implement your own command line interface handlers. + """ + + def __init__(self): + self.__cli_idx = -1 + self.__clink__ = None + + + def register(self, flags = 0, sname = None, lname = None, hint = None): + """ + Registers the CLI. + + @param flags: Feature bits. No bits are defined yet, must be 0 + @param sname: Short name (displayed on the button) + @param lname: Long name (displayed in the menu) + @param hint: Hint for the input line + + @return Boolean: True-Success, False-Failed + """ + + # Already registered? + if self.__cli_idx >= 0: + return True + + if sname is not None: self.sname = sname + if lname is not None: self.lname = lname + if hint is not None: self.hint = hint + + # Register + self.__cli_idx = _idaapi.install_command_interpreter(self) + return False if self.__cli_idx < 0 else True + + + def unregister(self): + """ + Unregisters the CLI (if it was registered) + """ + if self.__cli_idx < 0: + return False + + _idaapi.remove_command_interpreter(self.__cli_idx) + self.__cli_idx = -1 + return True + + + def __del__(self): + self.unregister() + + # + # Implement these methods in the subclass: + # +# +# def OnExecuteLine(self, line): +# """ +# The user pressed Enter. The CLI is free to execute the line immediately or ask for more lines. +# +# This callback is mandatory. +# +# @param line: typed line(s) +# @return Boolean: True-executed line, False-ask for more lines +# """ +# return True +# +# def OnKeydown(self, line, x, sellen, vkey, shift): +# """ +# A keyboard key has been pressed +# This is a generic callback and the CLI is free to do whatever it wants. +# +# This callback is optional. +# +# @param line: current input line +# @param x: current x coordinate of the cursor +# @param sellen: current selection length (usually 0) +# @param vkey: virtual key code. if the key has been handled, it should be returned as zero +# @param shift: shift state +# +# @return: +# None - Nothing was changed +# tuple(line, x, sellen, vkey): if either of the input line or the x coordinate or the selection length has been modified. +# It is possible to return a tuple with None elements to preserve old values. Example: tuple(new_line, None, None, None) or tuple(new_line) +# """ +# return None +# +# def OnCompleteLine(self, prefix, n, line, prefix_start): +# """ +# The user pressed Tab. Find a completion number N for prefix PREFIX +# +# This callback is optional. +# +# @param prefix: Line prefix at x (string) +# @param n: completion number (int) +# @param line: the current line (string) +# @param prefix_start: the index where PREFIX starts in LINE (int) +# +# @return: None if no completion could be generated otherwise a String with the completion suggestion +# """ +# return None +# + +# # class simplecustviewer_t(object): """The base class for implementing simple custom viewers""" @@ -2586,4 +2962,3 @@ class simplecustviewer_t(object): # # %} -#endif diff --git a/swig/ua.i b/swig/ua.i index 601f7a0..b27b7ca 100644 --- a/swig/ua.i +++ b/swig/ua.i @@ -1347,5 +1347,28 @@ class processor_t(pyidc_opaque_object_t): """This function returns cmd.auxpref value""" return self.cmd.auxpref + +# ---------------------------------------------------------------------- +class __ph(object): + id = property(lambda self: ph_get_id()) + cnbits = property(lambda self: ph_get_cnbits()) + dnbits = property(lambda self: ph_get_dnbits()) + flag = property(lambda self: ph_get_flag()) + high_fixup_bits = property(lambda self: ph_get_high_fixup_bits()) + icode_return = property(lambda self: ph_get_icode_return()) + instruc = property(lambda self: ph_get_instruc()) + instruc_end = property(lambda self: ph_get_instruc_end()) + instruc_start = property(lambda self: ph_get_instruc_start()) + regCodeSreg = property(lambda self: ph_get_regCodeSreg()) + regDataSreg = property(lambda self: ph_get_regDataSreg()) + regFirstSreg = property(lambda self: ph_get_regFirstSreg()) + regLastSreg = property(lambda self: ph_get_regLastSreg()) + regnames = property(lambda self: ph_get_regnames()) + segreg_size = property(lambda self: ph_get_segreg_size()) + tbyte_size = property(lambda self: ph_get_tbyte_size()) + version = property(lambda self: ph_get_version()) + +ph = __ph() + # %}