# -------------------------------------------------------------------------- # import sys import ida_idaapi, _ida_idaapi import ida_pro #ICON WARNING|QUESTION|INFO|NONE #AUTOHIDE NONE|DATABASE|REGISTRY|SESSION #HIDECANCEL #BUTTON YES|NO|CANCEL "Value|NONE" #STARTITEM {id:ItemName} #HELP / ENDHELP try: import types import ctypes # On Windows, we use stdcall # Callback for buttons # typedef int (idaapi *buttoncb_t)(int button_code, form_actions_t &fa); _BUTTONCB_T = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p) # Callback for form change # typedef int (idaapi *formchgcb_t)(int field_id, form_actions_t &fa); _FORMCHGCB_T = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p) except: try: _BUTTONCB_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p) _FORMCHGCB_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_void_p) except: _BUTTONCB_T = _FORMCHGCB_T = None # ----------------------------------------------------------------------- # textctrl_info_t clinked object class textctrl_info_t(ida_idaapi.py_clinked_object_t): """Class representing textctrl_info_t""" # Some constants TXTF_AUTOINDENT = 0x0001 """Auto-indent on new line""" TXTF_ACCEPTTABS = 0x0002 """Tab key inserts 'tabsize' spaces""" TXTF_READONLY = 0x0004 """Text cannot be edited (but can be selected and copied)""" TXTF_SELECTED = 0x0008 """Shows the field with its text selected""" TXTF_MODIFIED = 0x0010 """Gets/sets the modified status""" TXTF_FIXEDFONT = 0x0020 """The control uses IDA's fixed font""" def __init__(self, text="", flags=0, tabsize=0): ida_idaapi.py_clinked_object_t.__init__(self) if text: self.text = text if flags: self.flags = flags if tabsize: self.tabsize = tabsize def _create_clink(self): return _ida_kernwin.textctrl_info_t_create() def _del_clink(self, lnk): return _ida_kernwin.textctrl_info_t_destroy(lnk) def _get_clink_ptr(self): return _ida_kernwin.textctrl_info_t_get_clink_ptr(self) def assign(self, other): """Copies the contents of 'other' to 'self'""" return _ida_kernwin.textctrl_info_t_assign(self, other) def __set_text(self, s): """Sets the text value""" return _ida_kernwin.textctrl_info_t_set_text(self, s) def __get_text(self): """Sets the text value""" return _ida_kernwin.textctrl_info_t_get_text(self) def __set_flags__(self, flags): """Sets the flags value""" return _ida_kernwin.textctrl_info_t_set_flags(self, flags) def __get_flags__(self): """Returns the flags value""" return _ida_kernwin.textctrl_info_t_get_flags(self) def __set_tabsize__(self, tabsize): """Sets the tabsize value""" return _ida_kernwin.textctrl_info_t_set_tabsize(self, tabsize) def __get_tabsize__(self): """Returns the tabsize value""" return _ida_kernwin.textctrl_info_t_get_tabsize(self) value = property(__get_text, __set_text) """Alias for the text property""" text = property(__get_text, __set_text) """Text value""" flags = property(__get_flags__, __set_flags__) """Flags value""" tabsize = property(__get_tabsize__, __set_tabsize__) # ----------------------------------------------------------------------- class Form(object): FT_ASCII = 'A' """Ascii string - char *""" FT_SEG = 'S' """Segment - sel_t *""" FT_HEX = 'N' """Hex number - uval_t *""" FT_SHEX = 'n' """Signed hex number - sval_t *""" FT_COLOR = 'K' """Color button - bgcolor_t *""" FT_ADDR = '$' """Address - ea_t *""" FT_UINT64 = 'L' """default base uint64 - uint64""" FT_INT64 = 'l' """default base int64 - int64""" FT_RAWHEX = 'M' """Hex number, no 0x prefix - uval_t *""" FT_FILE = 'f' """File browse - char * at least QMAXPATH""" FT_DEC = 'D' """Decimal number - sval_t *""" FT_OCT = 'O' """Octal number, C notation - sval_t *""" FT_BIN = 'Y' """Binary number, 0b prefix - sval_t *""" FT_CHAR = 'H' """Char value -- sval_t *""" FT_IDENT = 'I' """Identifier - char * at least MAXNAMELEN""" FT_BUTTON = 'B' """Button - def handler(code)""" FT_DIR = 'F' """Path to directory - char * at least QMAXPATH""" FT_TYPE = 'T' """Type declaration - char * at least MAXSTR""" _FT_USHORT = '_US' """Unsigned short""" FT_FORMCHG = '%/' """Form change callback - formchgcb_t""" FT_ECHOOSER = 'E' """Embedded chooser - idaapi.Choose""" FT_MULTI_LINE_TEXT = 't' """Multi text control - textctrl_info_t""" FT_DROPDOWN_LIST = 'b' """Dropdown list control - Form.DropdownControl""" FT_HTML_LABEL = 'h' """HTML label to display (only for GUI version, and for dynamic labels; no input)""" FT_CHKGRP = 'C' FT_CHKGRP2= 'c' FT_RADGRP = 'R' FT_RADGRP2= 'r' @staticmethod def create_string_buffer(value, size=None): if value is None: assert(size is not None) return ctypes.create_string_buffer(size) else: if sys.version_info.major >= 3: return ctypes.create_string_buffer(value.encode("UTF-8"), size) else: return ctypes.create_string_buffer(value, size) @staticmethod def fieldtype_to_ctype(tp, i64 = False): """ Factory method returning a ctype class corresponding to the field type string """ if tp in (Form.FT_SEG, Form.FT_HEX, Form.FT_RAWHEX, Form.FT_ADDR): return ctypes.c_uint64 if i64 else ctypes.c_ulong elif tp in (Form.FT_SHEX, Form.FT_DEC, Form.FT_OCT, Form.FT_BIN, Form.FT_CHAR): return ctypes.c_int64 if i64 else ctypes.c_long elif tp == Form.FT_UINT64: return ctypes.c_uint64 elif tp == Form.FT_INT64: return ctypes.c_int64 elif tp == Form.FT_COLOR: return ctypes.c_ulong elif tp == Form._FT_USHORT: return ctypes.c_ushort elif tp in (Form.FT_FORMCHG, Form.FT_ECHOOSER): return ctypes.c_void_p else: return None # # Generic argument helper classes # class NumericArgument(object): """ Argument representing various integer arguments (ushort, uint32, uint64, etc...) @param tp: One of Form.FT_XXX """ DefI64 = False def __init__(self, tp, value, i64=None): cls = Form.fieldtype_to_ctype(tp, i64 if i64 is not None else self.DefI64) if cls is None: raise TypeError("Invalid numeric field type: %s" % tp) # Get a pointer type to the ctype type self.arg = ctypes.pointer(cls(value)) def __set_value(self, v): self.arg.contents.value = v value = property(lambda self: self.arg.contents.value, __set_value) class StringArgument(object): """ Argument representing a character buffer """ def __init__(self, size=None, value=None): if size is None: raise SyntaxError("The string size must be passed") if isinstance(size, str): value, size = size, None self.size = size self.arg = Form.create_string_buffer(value, size) def __get_value(self): return self.arg.value.decode("UTF-8") def __set_value(self, v): self.arg.value = v.encode("UTF-8") value = property(__get_value, __set_value) # # Base control class # class Control(object): def __init__(self): self.id = 0 """Automatically assigned control ID""" self.input_field_index = None """If this control is an input field, once Compile() returns this will hold its index. This is used only to compute the possible STARTITEM index""" self.arg = None """Control argument value. This could be one element or a list/tuple (for multiple args per control)""" self.form = None """Reference to the parent form. It is filled by Form.Add()""" self.form_hasattr = False def get_tag(self): """ Control tag character. One of Form.FT_XXXX. The form class will expand the {} notation and replace them with the tags """ pass def get_arg(self): """ Control returns the parameter to be pushed on the stack (Of ask_form()) """ return self.arg def free(self): """ Free the control """ # Release the parent form reference self.form = None def is_input_field(self): """ Return True if this field acts as an input """ return False # # Label controls # class LabelControl(Control): """ Base class for static label control """ def __init__(self, tp): Form.Control.__init__(self) self.tp = tp def get_tag(self): return '%%%d%s' % (self.id, self.tp) class StringLabel(LabelControl): """ String label control """ def __init__(self, value, tp=None, size=ida_pro.MAXSTR): """ Type field can be one of: A - ascii string T - type declaration I - ident F - folder f - file X - command """ if tp is None: tp = Form.FT_ASCII Form.LabelControl.__init__(self, tp) self.size = size self.arg = Form.create_string_buffer(value, size) class NumericLabel(LabelControl, NumericArgument): """ Numeric label control """ def __init__(self, value, tp=None): if tp is None: tp = Form.FT_HEX Form.LabelControl.__init__(self, tp) Form.NumericArgument.__init__(self, tp, value) # # Group controls # class GroupItemControl(Control): """ Base class for group control items """ def __init__(self, tag, parent): Form.Control.__init__(self) self.tag = tag self.parent = parent # Item position (filled when form is compiled) self.pos = 0 def assign_pos(self): self.pos = self.parent.next_child_pos() def get_tag(self): return "%s%d" % (self.tag, self.id) def is_input_field(self): return True class ChkGroupItemControl(GroupItemControl): """ Checkbox group item control """ def __init__(self, tag, parent): Form.GroupItemControl.__init__(self, tag, parent) def __get_value(self): return (self.parent.value & (1 << self.pos)) != 0 def __set_value(self, v): pv = self.parent.value if v: pv = pv | (1 << self.pos) else: pv = pv & ~(1 << self.pos) self.parent.value = pv checked = property(__get_value, __set_value) """Get/Sets checkbox item check status""" class RadGroupItemControl(GroupItemControl): """ Radiobox group item control """ def __init__(self, tag, parent): Form.GroupItemControl.__init__(self, tag, parent) def __get_value(self): return self.parent.value == self.pos def __set_value(self, v): self.parent.value = self.pos selected = property(__get_value, __set_value) """Get/Sets radiobox item selection status""" class GroupControl(Control, NumericArgument): """ Base class for group controls """ def __init__(self, children_names, tag, value=0): Form.Control.__init__(self) self.children_names = children_names self.tag = tag self._reset() Form.NumericArgument.__init__(self, Form._FT_USHORT, value) def _reset(self): self.childpos = 0 def next_child_pos(self): v = self.childpos self.childpos += 1 return v def get_tag(self): return "%d" % self.id class ChkGroupControl(GroupControl): """ Checkbox group control class. It holds a set of checkbox controls """ ItemClass = None """ Group control item factory class instance We need this because later we won't be treating ChkGroupControl or RadGroupControl individually, instead we will be working with GroupControl in general. """ def __init__(self, children_names, value=0, secondary=False): # Assign group item factory class if Form.ChkGroupControl.ItemClass is None: Form.ChkGroupControl.ItemClass = Form.ChkGroupItemControl Form.GroupControl.__init__( self, children_names, Form.FT_CHKGRP2 if secondary else Form.FT_CHKGRP, value) class RadGroupControl(GroupControl): """ Radiobox group control class. It holds a set of radiobox controls """ ItemClass = None def __init__(self, children_names, value=0, secondary=False): """ Creates a radiogroup control. @param children_names: A tuple containing group item names @param value: Initial selected radio item @param secondory: Allows rendering one the same line as the previous group control. Use this if you have another group control on the same line. """ # Assign group item factory class if Form.RadGroupControl.ItemClass is None: Form.RadGroupControl.ItemClass = Form.RadGroupItemControl Form.GroupControl.__init__( self, children_names, Form.FT_RADGRP2 if secondary else Form.FT_RADGRP, value) # # Input controls # class InputControl(Control): """ Generic form input control. It could be numeric control, string control, directory/file browsing, etc... """ def __init__(self, tp, width, swidth, hlp=None, is_relative_offset=False): """ @param width: The maximum possible number of characters that can be entered into the input field @param swidth: The width of visible part of the input field """ Form.Control.__init__(self) self.tp = tp self.width = width self.swidth = swidth self.hlp = hlp self.is_relative_offset = is_relative_offset def get_tag(self): return "%s%d:%s%s:%s:%s" % ( self.tp, self.id, "+" if self.is_relative_offset else "", self.width, self.swidth, ":" if self.hlp is None else self.hlp) def is_input_field(self): return True class NumericInput(InputControl, NumericArgument): """ A composite class serving as a base numeric input control class """ def __init__(self, tp=None, value=0, width=50, swidth=10, hlp=None, is_relative_offset=False, i64=None): if tp is None: tp = Form.FT_HEX Form.InputControl.__init__(self, tp, width, swidth, hlp, is_relative_offset) Form.NumericArgument.__init__(self, self.tp, value, i64=i64) class ColorInput(NumericInput): """ Color button input control """ def __init__(self, value = 0): """ @param value: Initial color value in RGB """ Form.NumericInput.__init__(self, tp=Form.FT_COLOR, value=value, i64=False) class StringInput(InputControl, StringArgument): """ Base string input control class. This class also constructs a StringArgument """ def __init__(self, tp=None, width=ida_pro.MAXSTR, swidth=40, hlp=None, value=None, size=None): """ @param width: String size. But in some cases it has special meaning. For example in FileInput control. If you want to define the string buffer size then pass the 'size' argument @param swidth: Control width @param value: Initial value @param size: String size """ if tp is None: tp = Form.FT_ASCII if not size: size = width Form.InputControl.__init__(self, tp, width, swidth, hlp) Form.StringArgument.__init__(self, size=size, value=value) class FileInput(StringInput): """ File Open/Save input control """ def __init__(self, width=512, swidth=80, save=False, open=False, hlp=None, value=None): if save == open: raise ValueError("Invalid mode. Choose either open or save") if width < 512: raise ValueError("Invalid width. Must be greater than 512.") # The width field is overloaded in this control and is used # to denote the type of the FileInput dialog (save or load) # On the other hand it is passed as is to the StringArgument part Form.StringInput.__init__( self, tp=Form.FT_FILE, width="1" if save else "0", swidth=swidth, hlp=hlp, size=width, value=value) class DirInput(StringInput): """ Directory browsing control """ def __init__(self, width=512, swidth=80, hlp=None, value=None): if width < 512: raise ValueError("Invalid width. Must be greater than 512.") Form.StringInput.__init__( self, tp=Form.FT_DIR, width=width, swidth=swidth, hlp=hlp, size=width, value=value) class ButtonInput(InputControl): """ Button control. A handler along with a 'code' (numeric value) can be associated with the button. This way one handler can handle many buttons based on the button code (or in other terms id or tag) """ def __init__(self, handler, code="", swidth="", hlp=None): """ @param handler: Button handler. A callback taking one argument which is the code. @param code: A code associated with the button and that is later passed to the handler. """ Form.InputControl.__init__( self, Form.FT_BUTTON, code, swidth, hlp) self.handler = handler self.arg = _BUTTONCB_T(self.helper_cb) def helper_cb(self, button_code, p_fa): # Remember the pointer to the forms_action in the parent form self.form.p_fa = p_fa # Call user's handler r = self.handler(button_code) return 0 if r is None else r def is_input_field(self): return False class FormChangeCb(Control): """ Form change handler. This can be thought of like a dialog procedure. Everytime a form action occurs, this handler will be called along with the control id. The programmer can then call various form actions accordingly: - EnableField - ShowField - MoveField - GetFieldValue - etc... Special control IDs: -1 (The form is initialized) and -2 (Ok has been clicked) """ def __init__(self, handler): """ Constructs the handler. @param handler: The handler (preferrably a member function of a class derived from the Form class). """ Form.Control.__init__(self) # Save the handler self.handler = handler # Create a callback stub # We use this mechanism to create an intermediate step # where we can create an 'fa' adapter for use by Python self.arg = _FORMCHGCB_T(self.helper_cb) def helper_cb(self, fid, p_fa): # Remember the pointer to the forms_action in the parent form self.form.p_fa = p_fa # Call user's handler r = self.handler(fid) return 0 if r is None else r def get_tag(self): return Form.FT_FORMCHG def free(self): Form.Control.free(self) # Remove reference to the handler # (Normally the handler is a member function in the parent form) self.handler = None class EmbeddedChooserControl(InputControl): """ Embedded chooser control. This control links to a Chooser2 control created with the 'embedded=True' """ def __init__(self, chooser=None, swidth=40, hlp=None): """ Embedded chooser control @param chooser: A chooser2 instance (must be constructed with 'embedded=True') """ # !! Make sure a chooser instance is passed !! if chooser is None or not isinstance(chooser, Choose): raise ValueError("Invalid chooser passed.") # Create an embedded chooser structure from the Choose instance, # and retrieve the pointer to the chooser_base_t. emb = chooser.Embedded(create_chobj=True) # if chooser.Embedded() != 0: if emb is None: raise ValueError("Failed to create embedded chooser instance.") # Construct input control Form.InputControl.__init__(self, Form.FT_ECHOOSER, "", swidth) self.selobj = ida_pro.sizevec_t() # Get a pointer to the selection vector if sys.version_info.major >= 3: sel = self.selobj.this.__int__() else: sel = self.selobj.this.__long__() # Get a pointer to a c_void_p constructed from an address p_embedded = ctypes.pointer(ctypes.c_void_p.from_address(emb)) p_sel = ctypes.pointer(ctypes.c_void_p.from_address(sel)) # - Create the embedded chooser info on control creation # - Do not free the embeded chooser because after we get the args # via Compile() the user can still call Execute() which relies # on the already computed args self.arg = (p_embedded, p_sel) # Save chooser instance self.chooser = chooser # Add a bogus 'size' attribute self.size = 0 value = property(lambda self: self.chooser) """Returns the embedded chooser instance""" def __get_selection__(self): if len(self.selobj): out = [] for item in self.selobj: out.append(int(item)) return out selection = property(__get_selection__) """Returns the selection""" def free(self): """ Frees the embedded chooser data """ self.chooser.Close() self.chooser = None Form.Control.free(self) class DropdownListControl(InputControl, ida_pro._qstrvec_t): """ Dropdown control This control allows manipulating a dropdown control """ def __init__(self, items=[], readonly=True, selval=0, width=50, swidth=50, hlp = None): """ @param items: A string list of items used to prepopulate the control @param readonly: Specifies whether the dropdown list is editable or not @param selval: The preselected item index (when readonly) or text value (when editable) @param width: the control width (n/a if the dropdown list is readonly) @param swidth: string width """ # Ignore the width if readonly was set if readonly: width = 0 # Init the input control base class Form.InputControl.__init__( self, Form.FT_DROPDOWN_LIST, width, swidth, hlp) # Init the associated qstrvec ida_pro._qstrvec_t.__init__(self, items) # Remember if readonly or not self.readonly = readonly if readonly: # Create a C integer and remember it self.__selval = ctypes.c_int(selval) val_addr = ctypes.addressof(self.__selval) else: # Create an strvec with one qstring self.__selval = ida_pro._qstrvec_t([selval]) # Get address of the first element val_addr = self.__selval.addressof(0) # Two arguments: # - argument #1: a pointer to the qstrvec containing the items # - argument #2: an integer to hold the selection # or # #2: a qstring to hold the dropdown text control value self.arg = ( ctypes.pointer(ctypes.c_void_p.from_address(self.clink_ptr)), ctypes.pointer(ctypes.c_void_p.from_address(val_addr)) ) def __set_selval(self, val): if self.readonly: self.__selval.value = val else: self.__selval[0] = val def __get_selval(self): # Return the selection index # or the entered text value return self.__selval.value if self.readonly else self.__selval[0] value = property(__get_selval, __set_selval) selval = property(__get_selval, __set_selval) """ Read/write the selection value. The value is used as an item index in readonly mode or text value in editable mode This value can be used only after the form has been closed. """ def free(self): self._free() def set_items(self, items): """Sets the dropdown list items""" self.from_list(items) class MultiLineTextControl(InputControl, textctrl_info_t): """ Multi line text control. This class inherits from textctrl_info_t. Thus the attributes are also inherited This control allows manipulating a multilinetext control """ def __init__(self, text="", flags=0, tabsize=0, width=50, swidth=50, hlp = None): """ @param text: Initial text value @param flags: One of textctrl_info_t.TXTF_.... values @param tabsize: Tab size @param width: Display width @param swidth: String width """ # Init the input control base class Form.InputControl.__init__(self, Form.FT_MULTI_LINE_TEXT, width, swidth, hlp) # Init the associated textctrl_info base class textctrl_info_t.__init__(self, text=text, flags=flags, tabsize=tabsize) # Get the argument as a pointer from the embedded ti self.arg = ctypes.pointer(ctypes.c_void_p.from_address(self.clink_ptr)) def free(self): self._free() # # Form class # def __init__(self, form, controls): """ Contruct a Form class. This class wraps around ask_form() or open_form() and provides an easier / alternative syntax for describing forms. The form control names are wrapped inside the opening and closing curly braces and the control themselves are defined and instantiated via various form controls (subclasses of Form). @param form: The form string @param controls: A dictionary containing the control name as a _key_ and control object as _value_ """ self._reset() self.form = form """Form string""" self.controls = controls """Dictionary of controls""" self.__args = None self.title = None """The Form title. It will be filled when the form is compiled""" self.modal = True """By default, forms are modal""" self.openform_flags = 0 """ If non-modal, these flags will be passed to open_form. This is an OR'ed combination of the PluginForm.FORM_* values. """ def Free(self): """ Frees all resources associated with a compiled form. Make sure you call this function when you finish using the form. """ # Free all the controls for name, ctrl in self.__controls.items(): if ctrl.parent_hasattr: delattr(self, name) ctrl.parent_hasattr = False ctrl.free() # Reset the controls # (Note that we are not removing the form control attributes, no need) self._reset() # Unregister, so we don't try and free it again at closing-time. _ida_kernwin.py_unregister_compiled_form(self) def _reset(self): """ Resets the Form class state variables """ self.__controls = {} self.__ctrl_id = 1 def __getitem__(self, name): """Returns a control object by name""" return self.__controls[name] def Add(self, name, ctrl, mkattr = True): """ Low level function. Prefer AddControls() to this function. This function adds one control to the form. @param name: Control name @param ctrl: Control object @param mkattr: Create control name / control object as a form attribute """ # Assign a unique ID ctrl.id = self.__ctrl_id self.__ctrl_id += 1 # Create attribute with control name if mkattr: setattr(self, name, ctrl) ctrl.parent_hasattr = True # Remember the control self.__controls[name] = ctrl # Link the form to the control via its form attribute ctrl.form = self # Is it a group? Add each child if isinstance(ctrl, Form.GroupControl): self._AddGroup(ctrl, mkattr) def FindControlById(self, id): """ Finds a control instance given its id """ for ctrl in self.__controls.values(): if ctrl.id == id: return ctrl return None @staticmethod def _ParseFormTitle(form): """ Parses the form's title from the form text """ help_state = 0 for i, line in enumerate(form.split("\n")): if line.startswith("STARTITEM ") or line.startswith("BUTTON "): continue # Skip "HELP" and remember state elif help_state == 0 and line == "HELP": help_state = 1 # Mark inside HELP continue elif help_state == 1 and line == "ENDHELP": help_state = 2 # Mark end of HELP continue return line.strip() return None def _AddGroup(self, Group, mkattr=True): """ Internal function. This function expands the group item names and creates individual group item controls @param Group: The group class (checkbox or radio group class) """ # Create group item controls for each child for child_name in sorted(Group.children_names): self.Add( child_name, # Use the class factory Group.ItemClass(Group.tag, Group), mkattr) def AddControls(self, controls, mkattr=True): """ Adds controls from a dictionary. The dictionary key is the control name and the value is a Form.Control object @param controls: The control dictionary """ for name in sorted(controls.keys()): # Add the control self.Add(name, controls[name], mkattr) def CompileEx(self, form): """ Low level function. Compiles (parses the form syntax and adds the control) the form string and returns the argument list to be passed the argument list to ask_form(). The form controls are wrapped inside curly braces: {ControlName}. A special operator can be used to return the index of a given control by its name: {id:ControlName}. This is useful when you use the STARTITEM form keyword to set the initially focused control. (note that, technically, the index is not the same as the ID; that's because STARTITEM uses raw, 0-based indexes rather than control IDs to determine the focused widget.) @param form: Compiles the form and returns the arguments needed to be passed to ask_form() """ # First argument is the form string args = [None] # Second argument, if form is not modal, is the set of flags if not self.modal: args.append(self.openform_flags | 0x80) # Add FORM_QWIDGET ctrlcnt = 1 # Reset all group control internal flags for ctrl in self.__controls.values(): if isinstance(ctrl, Form.GroupControl): ctrl._reset() def next_control(form, p, first_pass): i1 = form.find("{", p) if i1 < 0: return form, None, None, None if form[i1 - 1] == '\\' and i1 > 0: if first_pass: return next_control(form, i1 + 1, first_pass) else: # Remove escape sequence and restart search form = form[:i1 - 1] + form[i1:] return next_control(form, i1, first_pass) i2 = form.find("}", i1) if i2 < 0: raise SyntaxError("No matching closing brace '}'") ctrlname = form[i1 + 1:i2] if not ctrlname: raise ValueError("Control %d has an invalid name!" % ctrlcnt) return form, i1, i2, ctrlname control_count = 0 last_input_field_index = 0 # First pass: assign input_field_index values to controls p = 0 while True: form, i1, i2, ctrlname = next_control(form, p, first_pass=True) if ctrlname is None: break p = i2 if ctrlname.startswith("id:"): continue ctrl = self.__controls.get(ctrlname, None) if ctrl is None: raise ValueError("No matching control '%s'" % ctrlname) if isinstance(ctrl, Form.FormChangeCb) and control_count > 0: raise SyntaxError("Control '%s' should be the first control in the form" % ctrlname) # If this control is an input, assign its index if ctrl.is_input_field(): ctrl.input_field_index = last_input_field_index last_input_field_index += 1 control_count += 1 p = 0 while True: form, i1, i2, ctrlname = next_control(form, p, first_pass=False) if ctrlname is None: break # Is it the IDOF operator? if ctrlname.startswith("id:"): idfunc = True # Take actual ctrlname ctrlname = ctrlname[3:] else: idfunc = False # Find the control ctrl = self.__controls.get(ctrlname, None) if ctrl is None: raise ValueError("No matching control '%s'" % ctrlname) # Replace control name by tag if idfunc: tag = str(ctrl.input_field_index if ctrl.input_field_index is not None else ctrl.id) else: tag = ctrl.get_tag() taglen = len(tag) form = form[:i1] + tag + form[i2+1:] # Set new position p = i1 + taglen # Was it an IDOF() ? No need to push parameters # Just ID substitution is fine if idfunc: continue # For GroupItem controls, there are no individual arguments # The argument is assigned for the group itself if isinstance(ctrl, Form.GroupItemControl): # GroupItem controls will have their position dynamically set ctrl.assign_pos() else: # Push argument(s) # (Some controls need more than one argument) arg = ctrl.get_arg() if isinstance(arg, (list, tuple)): # Push all args args.extend(arg) else: # Push one arg args.append(arg) ctrlcnt += 1 # If no FormChangeCb instance was passed, and thus there's no '%/' # in the resulting form string, let's provide a minimal one, so that # we will retrieve 'p_fa', and thus actions that rely on it will work. if form.find(Form.FT_FORMCHG) < 0: form = form + Form.FT_FORMCHG fccb = Form.FormChangeCb(lambda *args: 1) self.Add("___dummyfchgcb", fccb) # Regardless of the actual position of '%/' in the form # string, a formchange callback _must_ be right after # the form string. if self.modal: inspos = 1 else: inspos = 2 args.insert(inspos, fccb.get_arg()) # Patch in the final form string if sys.version_info.major >= 3: args[0] = form.encode("UTF-8") else: args[0] = form self.title = self._ParseFormTitle(form) return args def Compile(self): """ Compiles a form and returns the form object (self) and the argument list. The form object will contain object names corresponding to the form elements @return: It will raise an exception on failure. Otherwise the return value is ignored """ # Reset controls self._reset() # Insert controls self.AddControls(self.controls) # Compile form and get args self.__args = self.CompileEx(self.form) # Register this form, to make sure it will be freed at closing-time. _ida_kernwin.py_register_compiled_form(self) return (self, self.__args) def Compiled(self): """ Checks if the form has already been compiled @return: Boolean """ return self.__args is not None def _ChkCompiled(self): if not self.Compiled(): raise SyntaxError("Form is not compiled") def Execute(self): """ Displays a modal dialog containing the compiled form. @return: 1 - ok ; 0 - cancel """ self._ChkCompiled() if not self.modal: raise SyntaxError("Form is not modal. Open() should be instead") return ask_form(*self.__args) def Open(self): """ Opens a widget containing the compiled form. """ self._ChkCompiled() if self.modal: raise SyntaxError("Form is modal. Execute() should be instead") open_form(*self.__args) def EnableField(self, ctrl, enable): """ Enable or disable an input field @return: False - no such control """ return _ida_kernwin.formchgcbfa_enable_field(self.p_fa, ctrl.id, enable) def ShowField(self, ctrl, show): """ Show or hide an input field @return: False - no such control """ return _ida_kernwin.formchgcbfa_show_field(self.p_fa, ctrl.id, show) def MoveField(self, ctrl, x, y, w, h): """ Move/resize an input field @return: False - no such fiel """ return _ida_kernwin.formchgcbfa_move_field(self.p_fa, ctrl.id, x, y, w, h) def GetFocusedField(self): """ Get currently focused input field. @return: None if no field is selected otherwise the control ID """ id = _ida_kernwin.formchgcbfa_get_focused_field(self.p_fa) return self.FindControlById(id) def SetFocusedField(self, ctrl): """ Set currently focused input field @return: False - no such control """ return _ida_kernwin.formchgcbfa_set_focused_field(self.p_fa, ctrl.id) def RefreshField(self, ctrl): """ Refresh a field @return: False - no such control """ return _ida_kernwin.formchgcbfa_refresh_field(self.p_fa, ctrl.id) def Close(self, close_normally): """ Close the form @param close_normally: 1: form is closed normally as if the user pressed Enter 0: form is closed abnormally as if the user pressed Esc @return: None """ return _ida_kernwin.formchgcbfa_close(self.p_fa, close_normally) def GetControlValue(self, ctrl): """ Returns the control's value depending on its type @param ctrl: Form control instance @return: - color button, radio controls: integer - file/dir input, string input and string label: string - embedded chooser control (0-based indices of selected items): integer list - for multilinetext control: textctrl_info_t - dropdown list controls: string (when editable) or index (when readonly) - None: on failure """ tid, size = self.ControlToFieldTypeIdAndSize(ctrl) r = _ida_kernwin.formchgcbfa_get_field_value( self.p_fa, ctrl.id, tid, size) # Multilinetext? Unpack the tuple into a new textctrl_info_t instance if r is not None and tid == 7: return textctrl_info_t(text=r[0], flags=r[1], tabsize=r[2]) else: return r def SetControlValue(self, ctrl, value): """ Set the control's value depending on its type @param ctrl: Form control instance @param value: - embedded chooser: a 0-base indices list to select embedded chooser items - multilinetext: a textctrl_info_t - dropdown list: an integer designating the selection index if readonly a string designating the edit control value if not readonly @return: Boolean true on success """ tid, _ = self.ControlToFieldTypeIdAndSize(ctrl) return _ida_kernwin.formchgcbfa_set_field_value( self.p_fa, ctrl.id, tid, value) @staticmethod def ControlToFieldTypeIdAndSize(ctrl): """ Converts a control object to a tuple containing the field id and the associated buffer size """ # Input control depend on the associated buffer size (supplied by the user) # Make sure you check instances types taking into account inheritance if isinstance(ctrl, Form.DropdownListControl): return (8, 1 if ctrl.readonly else 0) elif isinstance(ctrl, Form.MultiLineTextControl): return (7, 0) elif isinstance(ctrl, Form.EmbeddedChooserControl): return (5, 0) # Group items or controls elif isinstance(ctrl, (Form.GroupItemControl, Form.GroupControl)): return (2, 0) elif isinstance(ctrl, Form.StringLabel): return (3, min(ida_pro.MAXSTR, ctrl.size)) elif isinstance(ctrl, Form.ColorInput): return (4, 0) elif isinstance(ctrl, Form.NumericInput): # Pass the numeric control type return (6, ord(ctrl.tp[0])) elif isinstance(ctrl, Form.InputControl): return (1, ctrl.size) else: raise NotImplementedError("Not yet implemented") # -------------------------------------------------------------------------- # Instantiate ask_form/open_form function pointers try: import ctypes # Setup the numeric argument size Form.NumericArgument.DefI64 = _ida_idaapi.BADADDR == 0xFFFFFFFFFFFFFFFF # int ask_form(const char *form, ...) __ask_form_callable = ctypes.CFUNCTYPE(ctypes.c_int)(_ida_kernwin.py_get_ask_form()) # specify types of the fixed arguments explicitly so that varargs are passed correctly on arm macOS # https://bugs.python.org/issue42880 __ask_form_callable.argtypes = [ ctypes.c_char_p ] # TWidget *open_form(const char *form, uint32 flags, ...) __open_form_callable = ctypes.CFUNCTYPE(ctypes.c_void_p )(_ida_kernwin.py_get_open_form()) __open_form_callable.argtypes = [ ctypes.c_char_p, ctypes.c_uint32 ] except: def __ask_form_callable(*args): warning("ask_form() needs ctypes library in order to work") return 0 def __open_form_callable(*args): warning("open_form() needs ctypes library in order to work") def __call_form_callable(call, *args): assert(len(args)) with disabled_script_timeout_t(): if sys.version_info.major >= 3 and isinstance(args[0], str): largs = list(args) largs[0] = largs[0].encode("UTF-8") args = tuple(largs) r = call(*args) return r def ask_form(*args): return __call_form_callable(__ask_form_callable, *args) def open_form(*args): if len(args) == 1: args = (args[0], 0) # add default flags return __call_form_callable(__open_form_callable, *args) #