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()
+
#
%}