diff --git a/binaryninjaapi.h b/binaryninjaapi.h index c36e21a2a8..9aba7d346b 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 + \ingroup interaction + + \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(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 @@ -16799,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); }; /*! @@ -16858,6 +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(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 c4ac091171..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,6 +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, 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); @@ -7200,6 +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(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 adc661c805..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,6 +197,12 @@ bool InteractionHandler::GetDirectoryNameInput(string& result, const string& pro return GetTextLineInput(result, prompt, "Select Directory"); } +bool InteractionHandler::GetCheckboxInput(int64_t& 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 +324,12 @@ static bool GetDirectoryNameInputCallback(void* ctxt, char** result, const char* return true; } +static bool GetCheckboxInputCallback(void* ctxt, int64_t* 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) { @@ -353,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; @@ -369,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; @@ -399,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; @@ -460,6 +486,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 +616,11 @@ bool BinaryNinja::GetDirectoryNameInput(string& result, const string& prompt, co return true; } +bool BinaryNinja::GetCheckboxInput(int64_t& result, const std::string& prompt, const std::string& title) +{ + return BNGetCheckboxInput(&result, prompt.c_str(), title.c_str()); +} + bool BinaryNinja::GetFormInput(vector& fields, const string& title) { @@ -635,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; @@ -678,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 c1c5509505..12d4f734a0 100644 --- a/python/interaction.py +++ b/python/interaction.py @@ -485,6 +485,43 @@ 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 + """ + def __init__(self, prompt, default=None): + self._prompt = prompt + self._result = None + + def _fill_core_struct(self, value): + value.type = FormInputFieldType.CheckboxFormField + value.prompt = self._prompt + + def _fill_core_result(self, value): + value.intResult = self._result + + def _get_result(self, value): + self._result = value.intResult + + @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 + class InteractionHandler: _interaction_handler = None @@ -505,6 +542,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) @@ -650,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 = [] @@ -714,6 +762,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 + ) + ) else: field_objs.append(LabelField(fields[i].prompt)) if not self.get_form_input(field_objs, title): @@ -795,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 @@ -1361,6 +1418,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 + :param prompt: String to prompt with + :param title: Title of the window when executed in the UI + :rtype: int 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): """ @@ -1382,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 f854f612f5..02cbfe6a11 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::), @@ -245,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 {