From c779861eced1f7cfd9cdff75101f55183dc3c5c4 Mon Sep 17 00:00:00 2001 From: Alexander Khosrowshahi Date: Mon, 9 Jun 2025 12:27:47 -0400 Subject: [PATCH 1/2] Add Checkbox input support to python api --- binaryninjaapi.h | 11 ++++++ binaryninjacore.h | 2 + interaction.cpp | 22 +++++++++++ python/interaction.py | 68 +++++++++++++++++++++++++++++++++ rust/src/interaction/handler.rs | 1 + 5 files changed, 104 insertions(+) diff --git a/binaryninjaapi.h b/binaryninjaapi.h index c36e21a2a8..f9b557b2c6 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -2010,6 +2010,16 @@ namespace BinaryNinja { */ bool GetDirectoryNameInput(std::string& result, const std::string& prompt, const std::string& defaultName = ""); + /*! Prompts the user for a checkbox input, and returns True if the checkbox is checked, otherwise returns False. + \ingroup interaction + + \param[out] result Reference to the bool the result will be copied to + \param[in] prompt Prompt for the dialog + \param[in] title Title for the input popup when used in UI + \return Whether a checkbox input was successfully received + */ + bool GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title); + /*! Prompts the user for a set of inputs specified in `fields` with given title. The fields parameter is a list containing FieldInputFields @@ -16858,6 +16868,7 @@ namespace BinaryNinja { const std::string& defaultName = ""); virtual bool GetDirectoryNameInput( std::string& result, const std::string& prompt, const std::string& defaultName = ""); + virtual bool GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title); virtual bool GetFormInput(std::vector& fields, const std::string& title) = 0; virtual BNMessageBoxButtonResult ShowMessageBox(const std::string& title, const std::string& text, diff --git a/binaryninjacore.h b/binaryninjacore.h index c4ac091171..2b65501da6 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -3021,6 +3021,7 @@ extern "C" bool (*getSaveFileNameInput)( void* ctxt, char** result, const char* prompt, const char* ext, const char* defaultName); bool (*getDirectoryNameInput)(void* ctxt, char** result, const char* prompt, const char* defaultName); + bool (*getCheckboxInput)(void* ctxt, bool* result, const char* prompt, const char* title); bool (*getFormInput)(void* ctxt, BNFormInputField* fields, size_t count, const char* title); BNMessageBoxButtonResult (*showMessageBox)( void* ctxt, const char* title, const char* text, BNMessageBoxButtonSet buttons, BNMessageBoxIcon icon); @@ -7200,6 +7201,7 @@ extern "C" BINARYNINJACOREAPI bool BNGetSaveFileNameInput( char** result, const char* prompt, const char* ext, const char* defaultName); BINARYNINJACOREAPI bool BNGetDirectoryNameInput(char** result, const char* prompt, const char* defaultName); + BINARYNINJACOREAPI bool BNGetCheckboxInput(bool* result, const char* prompt, const char* title); BINARYNINJACOREAPI bool BNGetFormInput(BNFormInputField* fields, size_t count, const char* title); BINARYNINJACOREAPI void BNFreeFormInputResults(BNFormInputField* fields, size_t count); BINARYNINJACOREAPI BNMessageBoxButtonResult BNShowMessageBox( diff --git a/interaction.cpp b/interaction.cpp index adc661c805..2ce4f88a5c 100644 --- a/interaction.cpp +++ b/interaction.cpp @@ -188,6 +188,12 @@ bool InteractionHandler::GetDirectoryNameInput(string& result, const string& pro return GetTextLineInput(result, prompt, "Select Directory"); } +bool InteractionHandler::GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title) +{ + return GetCheckboxInput(result, prompt, "Select an option"); +} + + static void ShowPlainTextReportCallback(void* ctxt, BNBinaryView* view, const char* title, const char* contents) { @@ -309,6 +315,12 @@ static bool GetDirectoryNameInputCallback(void* ctxt, char** result, const char* return true; } +static bool GetCheckboxInputCallback(void* ctxt, bool* result, const char* prompt, const char* title) +{ + InteractionHandler* handler = (InteractionHandler*)ctxt; + return handler->GetCheckboxInput(*result, prompt, title); +} + static bool GetFormInputCallback(void* ctxt, BNFormInputField* fieldBuf, size_t count, const char* title) { @@ -460,6 +472,7 @@ void BinaryNinja::RegisterInteractionHandler(InteractionHandler* handler) cb.getOpenFileNameInput = GetOpenFileNameInputCallback; cb.getSaveFileNameInput = GetSaveFileNameInputCallback; cb.getDirectoryNameInput = GetDirectoryNameInputCallback; + cb.getCheckboxInput = GetCheckboxInputCallback; cb.getFormInput = GetFormInputCallback; cb.showMessageBox = ShowMessageBoxCallback; cb.openUrl = OpenUrlCallback; @@ -589,6 +602,15 @@ bool BinaryNinja::GetDirectoryNameInput(string& result, const string& prompt, co return true; } +bool BinaryNinja::GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title) +{ + bool* value = nullptr; + if (!BNGetCheckboxInput(value, prompt.c_str(), title.c_str())) + return false; + result = value; + return true; +} + bool BinaryNinja::GetFormInput(vector& fields, const string& title) { diff --git a/python/interaction.py b/python/interaction.py index c1c5509505..5348065054 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -485,6 +485,54 @@ def result(self): def result(self, value): self._result = value +class CheckBoxField: + """ + ``CheckBoxField`` prompts the user to choose a yes/no option in a checkbox. + Result is stored in self.result as a boolean value. + + :param str prompt: Prompt to be presented to the user + :param Optional[bool]: Optional boolean value for the default setting of the checkbox. \ + by default set to 'false' + """ + def __init__(self, prompt, default=None): + self._prompt = prompt + self._default = default + self._result = None + + def _fill_core_struct(self, value): + value.type = FormInputFieldType.CheckBoxFormField + value.prompt = self._prompt + value.hasDefault = self._default is not None + if self._default is not None: + value.boolDefault = self._default + + def _fill_core_result(self, value): + self._boolResult = value.result + + @property + def prompt(self): + return self._prompt + + @prompt.setter + def prompt(self, value): + self._prompt = value + + @property + def result(self): + return self._result + + @result.setter + def result(self, value): + self._result = value + + @property + def default(self): + return self._default + + @default.setter + def default(self, value): + self._default = value + class InteractionHandler: _interaction_handler = None @@ -505,6 +553,7 @@ def __init__(self): self._cb.getOpenFileNameInput = self._cb.getOpenFileNameInput.__class__(self._get_open_filename_input) self._cb.getSaveFileNameInput = self._cb.getSaveFileNameInput.__class__(self._get_save_filename_input) self._cb.getDirectoryNameInput = self._cb.getDirectoryNameInput.__class__(self._get_directory_name_input) + self._cb.getCheckboxInput = self._cb.getCheckboxInput.__class__(self._get_checkbox_input) self._cb.getFormInput = self._cb.getFormInput.__class__(self._get_form_input) self._cb.showMessageBox = self._cb.showMessageBox.__class__(self._show_message_box) self._cb.openUrl = self._cb.openUrl.__class__(self._open_url) @@ -714,6 +763,12 @@ def _get_form_input(self, ctxt, fields, count, title): default=fields[i].stringDefault if fields[i].hasDefault else None ) ) + elif fields[i].type == FormInputFieldType.CheckBoxFormField: + field_objs.append( + CheckBoxField( + fields[i].prompt, default=fields[i].boolDefault if fields[i].hasDefault else None + ) + ) else: field_objs.append(LabelField(fields[i].prompt)) if not self.get_form_input(field_objs, title): @@ -1361,6 +1416,19 @@ def get_directory_name_input(prompt: str, default_name: str = ""): core.free_string(value) return result.decode("utf-8") +def get_checkbox_input(prompt: str, title: str): + """ + ``get_checkbox_input`` prompts the user for a checkbox input, and returns True if the checkbox is checked, otherwise returns False. + :param prompt: String to prompt with + :param title: Title of the window when executed in the UI + :rtype: bool indicating the state of the checkbox + """ + value = ctypes.c_bool() + if not core.BNGetCheckboxInput(value, prompt, title): + return None + result = value.value + assert result is not None + return result def get_form_input(fields, title): """ diff --git a/rust/src/interaction/handler.rs b/rust/src/interaction/handler.rs index f854f612f5..ac1d26a8bd 100644 --- a/rust/src/interaction/handler.rs +++ b/rust/src/interaction/handler.rs @@ -27,6 +27,7 @@ pub fn register_interaction_handler(custom: R) { getOpenFileNameInput: Some(cb_get_open_file_name_input::), getSaveFileNameInput: Some(cb_get_save_file_name_input::), getDirectoryNameInput: Some(cb_get_directory_name_input::), + getCheckboxInput: None, getFormInput: Some(cb_get_form_input::), showMessageBox: Some(cb_show_message_box::), openUrl: Some(cb_open_url::), From 0132a58a24a757a5eff1c8bb1cb8129f1e8b70d6 Mon Sep 17 00:00:00 2001 From: Alexander Khosrowshahi Date: Mon, 9 Jun 2025 17:26:37 -0400 Subject: [PATCH 2/2] Add checkbox support in form dialog --- binaryninjaapi.h | 9 +++--- binaryninjacore.h | 7 +++-- interaction.cpp | 28 +++++++++++++------ python/interaction.py | 49 +++++++++++++++++---------------- rust/src/interaction/form.rs | 13 +++++++++ rust/src/interaction/handler.rs | 17 ++++++++++++ 6 files changed, 85 insertions(+), 38 deletions(-) diff --git a/binaryninjaapi.h b/binaryninjaapi.h index f9b557b2c6..9aba7d346b 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -2010,15 +2010,15 @@ namespace BinaryNinja { */ bool GetDirectoryNameInput(std::string& result, const std::string& prompt, const std::string& defaultName = ""); - /*! Prompts the user for a checkbox input, and returns True if the checkbox is checked, otherwise returns False. + /*! Prompts the user for a checkbox input \ingroup interaction - \param[out] result Reference to the bool the result will be copied to + \param[out] result Reference to the integer the result will be copied to \param[in] prompt Prompt for the dialog \param[in] title Title for the input popup when used in UI \return Whether a checkbox input was successfully received */ - bool GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title); + bool GetCheckboxInput(int64_t& result, const std::string& prompt, const std::string& title); /*! Prompts the user for a set of inputs specified in `fields` with given title. The fields parameter is a list containing FieldInputFields @@ -16809,6 +16809,7 @@ namespace BinaryNinja { static FormInputField SaveFileName( const std::string& prompt, const std::string& ext, const std::string& defaultName = ""); static FormInputField DirectoryName(const std::string& prompt, const std::string& defaultName = ""); + static FormInputField Checkbox(const std::string& prompt); }; /*! @@ -16868,7 +16869,7 @@ namespace BinaryNinja { const std::string& defaultName = ""); virtual bool GetDirectoryNameInput( std::string& result, const std::string& prompt, const std::string& defaultName = ""); - virtual bool GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title); + virtual bool GetCheckboxInput(int64_t& result, const std::string& prompt, const std::string& title); virtual bool GetFormInput(std::vector& fields, const std::string& title) = 0; virtual BNMessageBoxButtonResult ShowMessageBox(const std::string& title, const std::string& text, diff --git a/binaryninjacore.h b/binaryninjacore.h index 2b65501da6..d3955fe0ad 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -2975,7 +2975,8 @@ extern "C" ChoiceFormField, OpenFileNameFormField, SaveFileNameFormField, - DirectoryNameFormField + DirectoryNameFormField, + CheckboxFormField } BNFormInputFieldType; typedef struct BNFormInputField @@ -3021,7 +3022,7 @@ extern "C" bool (*getSaveFileNameInput)( void* ctxt, char** result, const char* prompt, const char* ext, const char* defaultName); bool (*getDirectoryNameInput)(void* ctxt, char** result, const char* prompt, const char* defaultName); - bool (*getCheckboxInput)(void* ctxt, bool* result, const char* prompt, const char* title); + bool (*getCheckboxInput)(void* ctxt, int64_t* result, const char* prompt, const char* title); bool (*getFormInput)(void* ctxt, BNFormInputField* fields, size_t count, const char* title); BNMessageBoxButtonResult (*showMessageBox)( void* ctxt, const char* title, const char* text, BNMessageBoxButtonSet buttons, BNMessageBoxIcon icon); @@ -7201,7 +7202,7 @@ extern "C" BINARYNINJACOREAPI bool BNGetSaveFileNameInput( char** result, const char* prompt, const char* ext, const char* defaultName); BINARYNINJACOREAPI bool BNGetDirectoryNameInput(char** result, const char* prompt, const char* defaultName); - BINARYNINJACOREAPI bool BNGetCheckboxInput(bool* result, const char* prompt, const char* title); + BINARYNINJACOREAPI bool BNGetCheckboxInput(int64_t* result, const char* prompt, const char* title); BINARYNINJACOREAPI bool BNGetFormInput(BNFormInputField* fields, size_t count, const char* title); BINARYNINJACOREAPI void BNFreeFormInputResults(BNFormInputField* fields, size_t count); BINARYNINJACOREAPI BNMessageBoxButtonResult BNShowMessageBox( diff --git a/interaction.cpp b/interaction.cpp index 2ce4f88a5c..ee054dfe33 100644 --- a/interaction.cpp +++ b/interaction.cpp @@ -111,6 +111,15 @@ FormInputField FormInputField::DirectoryName(const string& prompt, const string& return result; } +FormInputField FormInputField::Checkbox(const string& prompt) +{ + FormInputField result; + result.type = CheckboxFormField; + result.prompt = prompt; + result.hasDefault = false; + return result; +} + void InteractionHandler::ShowMarkdownReport( Ref view, const string& title, const string& contents, const string& plainText) @@ -188,7 +197,7 @@ bool InteractionHandler::GetDirectoryNameInput(string& result, const string& pro return GetTextLineInput(result, prompt, "Select Directory"); } -bool InteractionHandler::GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title) +bool InteractionHandler::GetCheckboxInput(int64_t& result, const std::string& prompt, const std::string& title) { return GetCheckboxInput(result, prompt, "Select an option"); } @@ -315,7 +324,7 @@ static bool GetDirectoryNameInputCallback(void* ctxt, char** result, const char* return true; } -static bool GetCheckboxInputCallback(void* ctxt, bool* result, const char* prompt, const char* title) +static bool GetCheckboxInputCallback(void* ctxt, int64_t* result, const char* prompt, const char* title) { InteractionHandler* handler = (InteractionHandler*)ctxt; return handler->GetCheckboxInput(*result, prompt, title); @@ -365,6 +374,9 @@ static bool GetFormInputCallback(void* ctxt, BNFormInputField* fieldBuf, size_t case DirectoryNameFormField: fields.push_back(FormInputField::DirectoryName(fieldBuf[i].prompt, fieldBuf[i].defaultName)); break; + case CheckboxFormField: + fields.push_back(FormInputField::Checkbox(fieldBuf[i].prompt)); + break; default: fields.push_back(FormInputField::Label(fieldBuf[i].prompt)); break; @@ -381,6 +393,7 @@ static bool GetFormInputCallback(void* ctxt, BNFormInputField* fieldBuf, size_t case DirectoryNameFormField: fields.back().stringDefault = fieldBuf[i].stringDefault; break; + case CheckboxFormField: case IntegerFormField: fields.back().intDefault = fieldBuf[i].intDefault; break; @@ -411,6 +424,7 @@ static bool GetFormInputCallback(void* ctxt, BNFormInputField* fieldBuf, size_t case DirectoryNameFormField: fieldBuf[i].stringResult = BNAllocString(fields[i].stringResult.c_str()); break; + case CheckboxFormField: case IntegerFormField: fieldBuf[i].intResult = fields[i].intResult; break; @@ -602,13 +616,9 @@ bool BinaryNinja::GetDirectoryNameInput(string& result, const string& prompt, co return true; } -bool BinaryNinja::GetCheckboxInput(bool& result, const std::string& prompt, const std::string& title) +bool BinaryNinja::GetCheckboxInput(int64_t& result, const std::string& prompt, const std::string& title) { - bool* value = nullptr; - if (!BNGetCheckboxInput(value, prompt.c_str(), title.c_str())) - return false; - result = value; - return true; + return BNGetCheckboxInput(&result, prompt.c_str(), title.c_str()); } @@ -657,6 +667,7 @@ bool BinaryNinja::GetFormInput(vector& fields, const string& tit case DirectoryNameFormField: fieldBuf[i].stringDefault = fields[i].stringDefault.c_str(); break; + case CheckboxFormField: case IntegerFormField: fieldBuf[i].intDefault = fields[i].intDefault; break; @@ -700,6 +711,7 @@ bool BinaryNinja::GetFormInput(vector& fields, const string& tit case DirectoryNameFormField: fields[i].stringResult = fieldBuf[i].stringResult; break; + case CheckboxFormField: case IntegerFormField: fields[i].intResult = fieldBuf[i].intResult; break; diff --git a/python/interaction.py b/python/interaction.py index 5348065054..12d4f734a0 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -485,29 +485,26 @@ def result(self): def result(self, value): self._result = value -class CheckBoxField: +class CheckboxField: """ - ``CheckBoxField`` prompts the user to choose a yes/no option in a checkbox. + ``CheckboxField`` prompts the user to choose a yes/no option in a checkbox. Result is stored in self.result as a boolean value. :param str prompt: Prompt to be presented to the user - :param Optional[bool]: Optional boolean value for the default setting of the checkbox. \ - by default set to 'false' """ def __init__(self, prompt, default=None): self._prompt = prompt - self._default = default self._result = None def _fill_core_struct(self, value): - value.type = FormInputFieldType.CheckBoxFormField + value.type = FormInputFieldType.CheckboxFormField value.prompt = self._prompt - value.hasDefault = self._default is not None - if self._default is not None: - value.boolDefault = self._default def _fill_core_result(self, value): - self._boolResult = value.result + value.intResult = self._result + + def _get_result(self, value): + self._result = value.intResult @property def prompt(self): @@ -525,14 +522,6 @@ def result(self): def result(self, value): self._result = value - @property - def default(self): - return self._default - - @default.setter - def default(self, value): - self._default = value - class InteractionHandler: _interaction_handler = None @@ -699,6 +688,16 @@ def _get_directory_name_input(self, ctxt, result, prompt, default_name): except: log_error(traceback.format_exc()) + def _get_checkbox_input(self, ctxt, result, prompt): + try: + value = self.get_checkbox_input(prompt) + if value is None: + return False + result[0] = value + return True + except: + log_error(traceback.format_exc()) + def _get_form_input(self, ctxt, fields, count, title): try: field_objs = [] @@ -763,10 +762,10 @@ def _get_form_input(self, ctxt, fields, count, title): default=fields[i].stringDefault if fields[i].hasDefault else None ) ) - elif fields[i].type == FormInputFieldType.CheckBoxFormField: + elif fields[i].type == FormInputFieldType.CheckboxFormField: field_objs.append( - CheckBoxField( - fields[i].prompt, default=fields[i].boolDefault if fields[i].hasDefault else None + CheckboxField( + fields[i].prompt ) ) else: @@ -850,6 +849,9 @@ def get_save_filename_input(self, prompt, ext, default_name): def get_directory_name_input(self, prompt, default_name): return get_text_line_input(prompt, "Select Directory") + def get_checkbox_input(self, prompt): + return get_checkbox_input(prompt, "Choose Option(s)") + def get_form_input(self, fields, title): return False @@ -1418,10 +1420,10 @@ def get_directory_name_input(prompt: str, default_name: str = ""): def get_checkbox_input(prompt: str, title: str): """ - ``get_checkbox_input`` prompts the user for a checkbox input, and returns True if the checkbox is checked, otherwise returns False. + ``get_checkbox_input`` prompts the user for a checkbox input :param prompt: String to prompt with :param title: Title of the window when executed in the UI - :rtype: bool indicating the state of the checkbox + :rtype: int indicating the state of the checkbox """ value = ctypes.c_bool() if not core.BNGetCheckboxInput(value, prompt, title): @@ -1450,6 +1452,7 @@ def get_form_input(fields, title): OpenFileNameField Prompt for file to open SaveFileNameField Prompt for file to save to DirectoryNameField Prompt for directory name + CheckboxFormField Prompt for a checkbox ===================== =================================================== This API is flexible and works both in the UI via a pop-up dialog and on the command-line. diff --git a/rust/src/interaction/form.rs b/rust/src/interaction/form.rs index 06799681d1..95f91129e3 100644 --- a/rust/src/interaction/form.rs +++ b/rust/src/interaction/form.rs @@ -112,6 +112,11 @@ pub enum FormInputField { default: Option, value: Option, }, + Checkbox { + prompt: String, + default: Option, + value: bool, + } } impl FormInputField { @@ -186,6 +191,11 @@ impl FormInputField { default: string_default, value: string_result, }, + BNFormInputFieldType::CheckboxFormField => Self::Checkbox { + prompt, + default: value.hasDefault.then_some(value.intResult != 0), + value: value.intResult != 0, + }, } } @@ -258,6 +268,7 @@ impl FormInputField { FormInputField::OpenFileName { .. } => BNFormInputFieldType::OpenFileNameFormField, FormInputField::SaveFileName { .. } => BNFormInputFieldType::SaveFileNameFormField, FormInputField::DirectoryName { .. } => BNFormInputFieldType::DirectoryNameFormField, + FormInputField::Checkbox { .. } => BNFormInputFieldType::CheckboxFormField, } } @@ -274,6 +285,7 @@ impl FormInputField { FormInputField::OpenFileName { prompt, .. } => Some(prompt.clone()), FormInputField::SaveFileName { prompt, .. } => Some(prompt.clone()), FormInputField::DirectoryName { prompt, .. } => Some(prompt.clone()), + FormInputField::Checkbox { prompt, .. } => Some(prompt.clone()), } } @@ -325,6 +337,7 @@ impl FormInputField { FormInputField::OpenFileName { default, .. } => Some(default.is_some()), FormInputField::SaveFileName { default, .. } => Some(default.is_some()), FormInputField::DirectoryName { default, .. } => Some(default.is_some()), + FormInputField::Checkbox { default, .. } => Some(default.is_some()), } } diff --git a/rust/src/interaction/handler.rs b/rust/src/interaction/handler.rs index ac1d26a8bd..02cbfe6a11 100644 --- a/rust/src/interaction/handler.rs +++ b/rust/src/interaction/handler.rs @@ -246,6 +246,23 @@ pub trait InteractionHandler: Sync + Send + 'static { form.get_field_with_name(prompt) .and_then(|f| f.try_value_string()) } + + fn get_choicebox_input( + &mut self, + prompt: &str, + title: &str, + ) -> Option { + let mut form = Form::new(title.to_owned()); + form.add_field(FormInputField::Checkbox { + prompt: prompt.to_string(), + value: false, + default: None, + }); + if !self.get_form_input(&mut form) { + return None; + } + form.get_field_with_name(prompt).and_then(|f| f.try_value_int()) + } } pub struct InteractionHandlerTask {