summaryrefslogtreecommitdiff
path: root/rust/src/interaction.rs
diff options
context:
space:
mode:
authorrbran <git@rubens.io>2025-04-23 19:24:45 +0000
committerMason Reed <35282038+emesare@users.noreply.github.com>2025-05-12 17:45:24 -0400
commitc2d90f2e5a010546d0e1f1deeb1a6d8531ea4d4f (patch)
tree20a6c6268943154fdda6a4d6884f2f29d9d4de50 /rust/src/interaction.rs
parent4a4a8488269922269b20a12a7b71272b925b9f18 (diff)
[Rust] Implement custom interactive handler
Diffstat (limited to 'rust/src/interaction.rs')
-rw-r--r--rust/src/interaction.rs433
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<(), ()>>)>(