diff options
| author | rbran <git@rubens.io> | 2025-04-23 19:24:45 +0000 |
|---|---|---|
| committer | Mason Reed <35282038+emesare@users.noreply.github.com> | 2025-05-12 17:45:24 -0400 |
| commit | c2d90f2e5a010546d0e1f1deeb1a6d8531ea4d4f (patch) | |
| tree | 20a6c6268943154fdda6a4d6884f2f29d9d4de50 /rust/src/interaction.rs | |
| parent | 4a4a8488269922269b20a12a7b71272b925b9f18 (diff) | |
[Rust] Implement custom interactive handler
Diffstat (limited to 'rust/src/interaction.rs')
| -rw-r--r-- | rust/src/interaction.rs | 433 |
1 files changed, 49 insertions, 384 deletions
diff --git a/rust/src/interaction.rs b/rust/src/interaction.rs index fad207d1..1a3ef2aa 100644 --- a/rust/src/interaction.rs +++ b/rust/src/interaction.rs @@ -14,15 +14,17 @@ //! Interfaces for asking the user for information: forms, opening files, etc. -use binaryninjacore_sys::*; - -use std::ffi::{c_char, c_void, CStr}; +use std::ffi::{c_char, c_void}; use std::path::PathBuf; -use crate::binary_view::BinaryView; -use crate::rc::Ref; +use binaryninjacore_sys::*; + use crate::string::{BnString, IntoCStr}; +pub mod form; +pub mod handler; +pub mod report; + pub type MessageBoxButtonSet = BNMessageBoxButtonSet; pub type MessageBoxIcon = BNMessageBoxIcon; pub type MessageBoxButtonResult = BNMessageBoxButtonResult; @@ -76,6 +78,48 @@ pub fn get_address_input(prompt: &str, title: &str) -> Option<u64> { Some(value) } +pub fn get_choice_input(prompt: &str, title: &str, choices: &[&str]) -> Option<usize> { + let prompt = prompt.to_cstr(); + let title = title.to_cstr(); + let mut choices_inner: Vec<BnString> = choices.iter().copied().map(BnString::new).collect(); + // SAFETY BnString and *const c_char are transparent + let choices: &mut [*const c_char] = unsafe { + core::mem::transmute::<&mut [BnString], &mut [*const c_char]>(&mut choices_inner[..]) + }; + let mut result = 0; + let succ = unsafe { + BNGetChoiceInput( + &mut result, + prompt.as_ptr(), + title.as_ptr(), + choices.as_mut_ptr(), + choices.len(), + ) + }; + succ.then_some(result) +} + +pub fn get_large_choice_input(prompt: &str, title: &str, choices: &[&str]) -> Option<usize> { + let prompt = prompt.to_cstr(); + let title = title.to_cstr(); + let mut choices_inner: Vec<BnString> = choices.iter().copied().map(BnString::new).collect(); + // SAFETY BnString and *const c_char are transparent + let choices: &mut [*const c_char] = unsafe { + core::mem::transmute::<&mut [BnString], &mut [*const c_char]>(&mut choices_inner[..]) + }; + let mut result = 0; + let succ = unsafe { + BNGetLargeChoiceInput( + &mut result, + prompt.as_ptr(), + title.as_ptr(), + choices.as_mut_ptr(), + choices.len(), + ) + }; + succ.then_some(result) +} + pub fn get_open_filename_input(prompt: &str, extension: &str) -> Option<PathBuf> { let mut value: *mut c_char = std::ptr::null_mut(); @@ -142,385 +186,6 @@ pub fn show_message_box( unsafe { BNShowMessageBox(title.as_ptr(), text.as_ptr(), buttons, icon) } } -pub enum FormResponses { - None, - String(String), - Integer(i64), - Address(u64), - Index(usize), -} - -enum FormData { - Label { - _text: BnString, - }, - Text { - _prompt: BnString, - _default: Option<BnString>, - }, - Choice { - _prompt: BnString, - _choices: Vec<BnString>, - _raw: Vec<*const c_char>, - }, - File { - _prompt: BnString, - _ext: BnString, - _default: Option<BnString>, - }, - FileSave { - _prompt: BnString, - _ext: BnString, - _default_name: BnString, - _default: Option<BnString>, - }, -} - -pub struct FormInputBuilder { - fields: Vec<BNFormInputField>, - data: Vec<FormData>, -} - -impl FormInputBuilder { - pub fn new() -> Self { - Self { - fields: vec![], - data: vec![], - } - } - - /// Form Field: Text output - pub fn label_field(mut self, text: &str) -> Self { - let text = BnString::new(text); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::LabelFormField; - result.hasDefault = false; - result.prompt = text.as_ptr(); - self.fields.push(result); - - self.data.push(FormData::Label { _text: text }); - self - } - - /// Form Field: Vertical spacing - pub fn separator_field(mut self) -> Self { - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::SeparatorFormField; - result.hasDefault = false; - self.fields.push(result); - self - } - - /// Form Field: Prompt for a string value - pub fn text_field(mut self, prompt: &str, default: Option<&str>) -> Self { - let prompt = BnString::new(prompt); - let default = default.map(BnString::new); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::TextLineFormField; - result.prompt = prompt.as_ptr(); - result.hasDefault = default.is_some(); - if let Some(ref default) = default { - result.stringDefault = default.as_ptr(); - } - self.fields.push(result); - - self.data.push(FormData::Text { - _prompt: prompt, - _default: default, - }); - self - } - - /// Form Field: Prompt for multi-line string value - pub fn multiline_field(mut self, prompt: &str, default: Option<&str>) -> Self { - let prompt = BnString::new(prompt); - let default = default.map(BnString::new); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::MultilineTextFormField; - result.prompt = prompt.as_ptr(); - result.hasDefault = default.is_some(); - if let Some(ref default) = default { - result.stringDefault = default.as_ptr(); - } - self.fields.push(result); - - self.data.push(FormData::Text { - _prompt: prompt, - _default: default, - }); - self - } - - /// Form Field: Prompt for an integer - pub fn integer_field(mut self, prompt: &str, default: Option<i64>) -> Self { - let prompt = BnString::new(prompt); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::IntegerFormField; - result.prompt = prompt.as_ptr(); - result.hasDefault = default.is_some(); - if let Some(default) = default { - result.intDefault = default; - } - self.fields.push(result); - - self.data.push(FormData::Label { _text: prompt }); - self - } - - /// Form Field: Prompt for an address - pub fn address_field( - mut self, - prompt: &str, - view: Option<Ref<BinaryView>>, - current_address: Option<u64>, - default: Option<u64>, - ) -> Self { - let prompt = BnString::new(prompt); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::AddressFormField; - result.prompt = prompt.as_ptr(); - if let Some(view) = view { - // the view is being moved into result, there is no need to clone - // and drop is intentionally being avoided with `Ref::into_raw` - result.view = unsafe { Ref::into_raw(view) }.handle; - } - result.currentAddress = current_address.unwrap_or(0); - result.hasDefault = default.is_some(); - if let Some(default) = default { - result.addressDefault = default; - } - self.fields.push(result); - - self.data.push(FormData::Label { _text: prompt }); - self - } - - /// Form Field: Prompt for a choice from provided options - pub fn choice_field(mut self, prompt: &str, choices: &[&str], default: Option<usize>) -> Self { - let prompt = BnString::new(prompt); - let choices: Vec<BnString> = choices.iter().map(|&s| BnString::new(s)).collect(); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::ChoiceFormField; - result.prompt = prompt.as_ptr(); - let mut raw_choices: Vec<*const c_char> = choices.iter().map(|c| c.as_ptr()).collect(); - result.choices = raw_choices.as_mut_ptr(); - result.count = choices.len(); - result.hasDefault = default.is_some(); - if let Some(default) = default { - result.indexDefault = default; - } - self.fields.push(result); - - self.data.push(FormData::Choice { - _prompt: prompt, - _choices: choices, - _raw: raw_choices, - }); - self - } - - /// Form Field: Prompt for file to open - pub fn open_file_field( - mut self, - prompt: &str, - ext: Option<&str>, - default: Option<&str>, - ) -> Self { - let prompt = BnString::new(prompt); - let ext = if let Some(ext) = ext { - BnString::new(ext) - } else { - BnString::new("") - }; - let default = default.map(BnString::new); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::OpenFileNameFormField; - result.prompt = prompt.as_ptr(); - result.ext = ext.as_ptr(); - result.hasDefault = default.is_some(); - if let Some(ref default) = default { - result.stringDefault = default.as_ptr(); - } - self.fields.push(result); - - self.data.push(FormData::File { - _prompt: prompt, - _ext: ext, - _default: default, - }); - self - } - - /// Form Field: Prompt for file to save to - pub fn save_file_field( - mut self, - prompt: &str, - ext: Option<&str>, - default_name: Option<&str>, - default: Option<&str>, - ) -> Self { - let prompt = BnString::new(prompt); - let ext = if let Some(ext) = ext { - BnString::new(ext) - } else { - BnString::new("") - }; - let default_name = if let Some(default_name) = default_name { - BnString::new(default_name) - } else { - BnString::new("") - }; - let default = default.map(BnString::new); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::SaveFileNameFormField; - result.prompt = prompt.as_ptr(); - result.ext = ext.as_ptr(); - result.defaultName = default_name.as_ptr(); - result.hasDefault = default.is_some(); - if let Some(ref default) = default { - result.stringDefault = default.as_ptr(); - } - self.fields.push(result); - - self.data.push(FormData::FileSave { - _prompt: prompt, - _ext: ext, - _default_name: default_name, - _default: default, - }); - self - } - - /// Form Field: Prompt for directory name - pub fn directory_name_field( - mut self, - prompt: &str, - default_name: Option<&str>, - default: Option<&str>, - ) -> Self { - let prompt = BnString::new(prompt); - let default_name = if let Some(default_name) = default_name { - BnString::new(default_name) - } else { - BnString::new("") - }; - let default = default.map(BnString::new); - - let mut result = unsafe { std::mem::zeroed::<BNFormInputField>() }; - result.type_ = BNFormInputFieldType::DirectoryNameFormField; - result.prompt = prompt.as_ptr(); - result.defaultName = default_name.as_ptr(); - result.hasDefault = default.is_some(); - if let Some(ref default) = default { - result.stringDefault = default.as_ptr(); - } - self.fields.push(result); - - self.data.push(FormData::File { - _prompt: prompt, - _ext: default_name, - _default: default, - }); - self - } - - /// Prompts the user for a set of inputs specified in `fields` with given title. - /// The fields parameter is a list which can contain the following types: - /// - /// This API is flexible and works both in the UI via a pop-up dialog and on the command-line. - /// - /// ```no_run - /// # use binaryninja::interaction::FormInputBuilder; - /// # use binaryninja::interaction::FormResponses; - /// let responses = FormInputBuilder::new() - /// .text_field("First Name", None) - /// .text_field("Last Name", None) - /// .choice_field( - /// "Favorite Food", - /// &vec![ - /// "Pizza", - /// "Also Pizza", - /// "Also Pizza", - /// "Yummy Pizza", - /// "Wrong Answer", - /// ], - /// Some(0), - /// ) - /// .get_form_input("Form Title"); - /// - /// let food = match responses[2] { - /// FormResponses::Index(0) => "Pizza", - /// FormResponses::Index(1) => "Also Pizza", - /// FormResponses::Index(2) => "Also Pizza", - /// FormResponses::Index(3) => "Wrong Answer", - /// _ => panic!("This person doesn't like pizza?!?"), - /// }; - /// - /// let FormResponses::String(last_name) = &responses[0] else { - /// unreachable!() - /// }; - /// let FormResponses::String(first_name) = &responses[1] else { - /// unreachable!() - /// }; - /// - /// println!("{} {} likes {}", &first_name, &last_name, food); - /// ``` - pub fn get_form_input(&mut self, title: &str) -> Vec<FormResponses> { - let title = title.to_cstr(); - if unsafe { BNGetFormInput(self.fields.as_mut_ptr(), self.fields.len(), title.as_ptr()) } { - let result = self - .fields - .iter() - .map(|form_field| match form_field.type_ { - BNFormInputFieldType::LabelFormField - | BNFormInputFieldType::SeparatorFormField => FormResponses::None, - - BNFormInputFieldType::TextLineFormField - | BNFormInputFieldType::MultilineTextFormField - | BNFormInputFieldType::OpenFileNameFormField - | BNFormInputFieldType::SaveFileNameFormField - | BNFormInputFieldType::DirectoryNameFormField => { - FormResponses::String(unsafe { - CStr::from_ptr(form_field.stringResult) - .to_str() - .unwrap() - .to_owned() - }) - } - - BNFormInputFieldType::IntegerFormField => { - FormResponses::Integer(form_field.intResult) - } - BNFormInputFieldType::AddressFormField => { - FormResponses::Address(form_field.addressResult) - } - BNFormInputFieldType::ChoiceFormField => { - FormResponses::Index(form_field.indexResult) - } - }) - .collect(); - unsafe { BNFreeFormInputResults(self.fields.as_mut_ptr(), self.fields.len()) }; - result - } else { - vec![] - } - } -} - -impl Default for FormInputBuilder { - fn default() -> Self { - Self::new() - } -} - struct TaskContext<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(F); pub fn run_progress_dialog<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>( |
