use std::ffi::{c_char, c_void, CStr}; use std::ptr; use binaryninjacore_sys::*; use crate::binary_view::BinaryView; use crate::flowgraph::FlowGraph; use crate::interaction::form::{Form, FormInputField}; use crate::interaction::report::{Report, ReportCollection}; use crate::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon}; use crate::string::{raw_to_string, BnString}; pub fn register_interaction_handler(custom: R) { let leak_custom = Box::leak(Box::new(custom)); let mut callbacks = BNInteractionHandlerCallbacks { context: leak_custom as *mut R as *mut c_void, showPlainTextReport: Some(cb_show_plain_text_report::), showMarkdownReport: Some(cb_show_markdown_report::), showHTMLReport: Some(cb_show_html_report::), showGraphReport: Some(cb_show_graph_report::), showReportCollection: Some(cb_show_report_collection::), getTextLineInput: Some(cb_get_text_line_input::), getIntegerInput: Some(cb_get_integer_input::), getAddressInput: Some(cb_get_address_input::), getChoiceInput: Some(cb_get_choice_input::), getLargeChoiceInput: Some(cb_get_large_choice_input::), getOpenFileNameInput: Some(cb_get_open_file_name_input::), getSaveFileNameInput: Some(cb_get_save_file_name_input::), getDirectoryNameInput: Some(cb_get_directory_name_input::), getCheckboxInput: Some(cb_get_checkbox_input::), getFormInput: Some(cb_get_form_input::), showMessageBox: Some(cb_show_message_box::), openUrl: Some(cb_open_url::), runProgressDialog: Some(cb_run_progress_dialog::), }; unsafe { BNRegisterInteractionHandler(&mut callbacks) } } pub trait InteractionHandler: Sync + Send + 'static { fn show_message_box( &mut self, title: &str, text: &str, buttons: MessageBoxButtonSet, icon: MessageBoxIcon, ) -> MessageBoxButtonResult; fn open_url(&mut self, url: &str) -> bool; fn run_progress_dialog( &mut self, title: &str, can_cancel: bool, task: &InteractionHandlerTask, ) -> bool; fn show_plain_text_report(&mut self, view: Option<&BinaryView>, title: &str, contents: &str); fn show_graph_report(&mut self, view: Option<&BinaryView>, title: &str, graph: &FlowGraph); fn show_markdown_report( &mut self, view: Option<&BinaryView>, title: &str, _contents: &str, plain_text: &str, ) { self.show_plain_text_report(view, title, plain_text); } fn show_html_report( &mut self, view: Option<&BinaryView>, title: &str, _contents: &str, plain_text: &str, ) { self.show_plain_text_report(view, title, plain_text); } fn show_report_collection(&mut self, _title: &str, reports: &ReportCollection) { for report in reports { match &report { Report::PlainText(rpt) => self.show_plain_text_report( report.view().as_deref(), &report.title(), &rpt.contents(), ), Report::Markdown(rm) => self.show_markdown_report( report.view().as_deref(), &report.title(), &rm.contents(), &rm.plaintext(), ), Report::Html(rh) => self.show_html_report( report.view().as_deref(), &report.title(), &rh.contents(), &rh.plaintext(), ), Report::FlowGraph(rfg) => self.show_graph_report( report.view().as_deref(), &report.title(), &rfg.flow_graph(), ), } } } fn get_form_input(&mut self, form: &mut Form) -> bool; fn get_text_line_input(&mut self, prompt: &str, title: &str) -> Option { let mut form = Form::new(title.to_owned()); form.add_field(FormInputField::TextLine { prompt: prompt.to_string(), default: None, value: None, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_string()) } fn get_integer_input(&mut self, prompt: &str, title: &str) -> Option { let mut form = Form::new(title.to_owned()); form.add_field(FormInputField::Integer { prompt: prompt.to_string(), value: 0, default: None, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_int()) } fn get_address_input( &mut self, prompt: &str, title: &str, view: Option<&BinaryView>, current_addr: u64, ) -> Option { let mut form = Form::new(title.to_owned()); form.add_field(FormInputField::Address { prompt: prompt.to_string(), view: view.map(|v| v.to_owned()), current_address: current_addr, value: 0, default: None, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_address()) } fn get_choice_input( &mut self, prompt: &str, title: &str, choices: Vec, ) -> Option { let mut form = Form::new(title.to_owned()); form.add_field(FormInputField::Choice { prompt: prompt.to_string(), choices, default: None, value: 0, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_index()) } fn get_large_choice_input( &mut self, prompt: &str, title: &str, choices: Vec, ) -> Option { self.get_choice_input(prompt, title, choices) } fn get_open_file_name_input( &mut self, prompt: &str, extension: Option, ) -> Option { let mut form = Form::new(prompt.to_owned()); form.add_field(FormInputField::OpenFileName { prompt: prompt.to_string(), default: None, value: None, extension, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_string()) } fn get_save_file_name_input( &mut self, prompt: &str, extension: Option, default: Option, ) -> Option { let mut form = Form::new(prompt.to_owned()); form.add_field(FormInputField::SaveFileName { prompt: prompt.to_string(), extension, default, value: None, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_string()) } fn get_directory_name_input( &mut self, prompt: &str, default: Option, ) -> Option { let mut form = Form::new(prompt.to_owned()); form.add_field(FormInputField::DirectoryName { prompt: prompt.to_string(), default, value: None, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_string()) } fn get_checkbox_input( &mut self, prompt: &str, title: &str, default: Option, ) -> Option { let mut form = Form::new(title.to_owned()); form.add_field(FormInputField::Checkbox { prompt: prompt.to_string(), default: default.map(|b| b == 1), value: default.map(|b| b == 1)?, }); if !self.get_form_input(&mut form) { return None; } form.get_field_with_name(prompt) .and_then(|f| f.try_value_int()) .map(|b| if b != 0 { 1 } else { 0 }) } } pub struct InteractionHandlerTask { ctxt: *mut c_void, task: Option< unsafe extern "C" fn( *mut c_void, progress: Option bool>, *mut c_void, ), >, } impl InteractionHandlerTask { pub fn task bool>(&mut self, progress: &mut P) { let Some(task) = self.task else { // Assuming a nullptr task mean nothing need to be done return; }; let progress_ctxt = progress as *mut P as *mut c_void; ffi_wrap!("custom_interaction_run_progress_dialog", unsafe { task( self.ctxt, Some(cb_custom_interaction_handler_task::

), progress_ctxt, ) }) } } unsafe extern "C" fn cb_custom_interaction_handler_task bool>( ctxt: *mut c_void, cur: usize, max: usize, ) -> bool { let ctxt = ctxt as *mut P; (*ctxt)(cur, max) } unsafe extern "C" fn cb_show_plain_text_report( ctxt: *mut c_void, view: *mut BNBinaryView, title: *const c_char, contents: *const c_char, ) { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); let contents = raw_to_string(contents).unwrap(); let view = match !view.is_null() { true => Some(BinaryView::from_raw(view)), false => None, }; (*ctxt).show_plain_text_report(view.as_ref(), &title, &contents) } unsafe extern "C" fn cb_show_markdown_report( ctxt: *mut c_void, view: *mut BNBinaryView, title: *const c_char, contents: *const c_char, plaintext: *const c_char, ) { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); let contents = raw_to_string(contents).unwrap(); let plaintext = raw_to_string(plaintext).unwrap(); let view = match !view.is_null() { true => Some(BinaryView::from_raw(view)), false => None, }; (*ctxt).show_markdown_report(view.as_ref(), &title, &contents, &plaintext) } unsafe extern "C" fn cb_show_html_report( ctxt: *mut c_void, view: *mut BNBinaryView, title: *const c_char, contents: *const c_char, plaintext: *const c_char, ) { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); let contents = raw_to_string(contents).unwrap(); let plaintext = raw_to_string(plaintext).unwrap(); let view = match !view.is_null() { true => Some(BinaryView::from_raw(view)), false => None, }; (*ctxt).show_html_report(view.as_ref(), &title, &contents, &plaintext) } unsafe extern "C" fn cb_show_graph_report( ctxt: *mut c_void, view: *mut BNBinaryView, title: *const c_char, graph: *mut BNFlowGraph, ) { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); let view = match !view.is_null() { true => Some(BinaryView::from_raw(view)), false => None, }; (*ctxt).show_graph_report(view.as_ref(), &title, &FlowGraph::from_raw(graph)) } unsafe extern "C" fn cb_show_report_collection( ctxt: *mut c_void, title: *const c_char, report: *mut BNReportCollection, ) { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); (*ctxt).show_report_collection( &title, &ReportCollection::from_raw(ptr::NonNull::new(report).unwrap()), ) } unsafe extern "C" fn cb_get_text_line_input( ctxt: *mut c_void, result_ffi: *mut *mut c_char, prompt: *const c_char, title: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let title = raw_to_string(title).unwrap(); let result = (*ctxt).get_text_line_input(&prompt, &title); if let Some(result) = result { unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) }; true } else { unsafe { *result_ffi = ptr::null_mut() }; false } } unsafe extern "C" fn cb_get_integer_input( ctxt: *mut c_void, result_ffi: *mut i64, prompt: *const c_char, title: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let title = raw_to_string(title).unwrap(); let result = (*ctxt).get_integer_input(&prompt, &title); if let Some(result) = result { unsafe { *result_ffi = result }; true } else { unsafe { *result_ffi = 0 }; false } } unsafe extern "C" fn cb_get_address_input( ctxt: *mut c_void, result_ffi: *mut u64, prompt: *const c_char, title: *const c_char, view: *mut BNBinaryView, current_addr: u64, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let title = raw_to_string(title).unwrap(); let view = (!view.is_null()).then(|| BinaryView::from_raw(view)); let result = (*ctxt).get_address_input(&prompt, &title, view.as_ref(), current_addr); if let Some(result) = result { unsafe { *result_ffi = result }; true } else { unsafe { *result_ffi = 0 }; false } } unsafe extern "C" fn cb_get_choice_input( ctxt: *mut c_void, result_ffi: *mut usize, prompt: *const c_char, title: *const c_char, choices: *mut *const c_char, count: usize, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let title = raw_to_string(title).unwrap(); let choices = unsafe { core::slice::from_raw_parts(choices, count) }; // SAFETY: BnString and *const c_char are transparent let choices = unsafe { core::mem::transmute::<&[*const c_char], &[BnString]>(choices) }; let choices: Vec = choices .iter() .map(|x| x.to_string_lossy().to_string()) .collect(); let result = (*ctxt).get_choice_input(&prompt, &title, choices); if let Some(result) = result { unsafe { *result_ffi = result }; true } else { unsafe { *result_ffi = 0 }; false } } unsafe extern "C" fn cb_get_large_choice_input( ctxt: *mut c_void, result_ffi: *mut usize, prompt: *const c_char, title: *const c_char, choices: *mut *const c_char, count: usize, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let title = raw_to_string(title).unwrap(); let choices = unsafe { core::slice::from_raw_parts(choices, count) }; // SAFETY: BnString and *const c_char are transparent let choices = unsafe { core::mem::transmute::<&[*const c_char], &[BnString]>(choices) }; let choices: Vec = choices .iter() .map(|x| x.to_string_lossy().to_string()) .collect(); let result = (*ctxt).get_large_choice_input(&prompt, &title, choices); if let Some(result) = result { unsafe { *result_ffi = result }; true } else { unsafe { *result_ffi = 0 }; false } } unsafe extern "C" fn cb_get_open_file_name_input( ctxt: *mut c_void, result_ffi: *mut *mut c_char, prompt: *const c_char, ext: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let ext = (!ext.is_null()).then(|| unsafe { CStr::from_ptr(ext) }); let result = (*ctxt).get_open_file_name_input(&prompt, ext.map(|x| x.to_string_lossy().to_string())); if let Some(result) = result { unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) }; true } else { unsafe { *result_ffi = ptr::null_mut() }; false } } unsafe extern "C" fn cb_get_save_file_name_input( ctxt: *mut c_void, result_ffi: *mut *mut c_char, prompt: *const c_char, ext: *const c_char, default_name: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let ext = raw_to_string(ext); let default_name = raw_to_string(default_name); let result = (*ctxt).get_save_file_name_input(&prompt, ext, default_name); if let Some(result) = result { unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) }; true } else { unsafe { *result_ffi = ptr::null_mut() }; false } } unsafe extern "C" fn cb_get_directory_name_input( ctxt: *mut c_void, result_ffi: *mut *mut c_char, prompt: *const c_char, default_name: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let default_name = raw_to_string(default_name); let result = (*ctxt).get_directory_name_input(&prompt, default_name); if let Some(result) = result { unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) }; true } else { unsafe { *result_ffi = ptr::null_mut() }; false } } unsafe extern "C" fn cb_get_checkbox_input( ctxt: *mut c_void, result_ffi: *mut i64, prompt: *const c_char, title: *const c_char, default_choice: *const i64, ) -> bool { let ctxt = ctxt as *mut R; let prompt = raw_to_string(prompt).unwrap(); let title = raw_to_string(title).unwrap(); let default = (!default_choice.is_null()).then(|| *default_choice); let result = (*ctxt).get_checkbox_input(&prompt, &title, default); if let Some(result) = result { unsafe { *result_ffi = result }; true } else { unsafe { *result_ffi = 0 }; false } } unsafe extern "C" fn cb_get_form_input( ctxt: *mut c_void, fields: *mut BNFormInputField, count: usize, title: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let raw_fields = unsafe { core::slice::from_raw_parts_mut(fields, count) }; let fields: Vec<_> = raw_fields .iter_mut() .map(|x| FormInputField::from_raw(x)) .collect(); let title = raw_to_string(title).unwrap(); let mut form = Form::new_with_fields(title, fields); let results = (*ctxt).get_form_input(&mut form); // Update the fields with the new values. Freeing the old ones. raw_fields .iter_mut() .enumerate() .for_each(|(idx, raw_field)| { FormInputField::free_raw(*raw_field); *raw_field = form.fields[idx].into_raw(); }); results } unsafe extern "C" fn cb_show_message_box( ctxt: *mut c_void, title: *const c_char, text: *const c_char, buttons: BNMessageBoxButtonSet, icon: BNMessageBoxIcon, ) -> BNMessageBoxButtonResult { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); let text = raw_to_string(text).unwrap(); (*ctxt).show_message_box(&title, &text, buttons, icon) } unsafe extern "C" fn cb_open_url( ctxt: *mut c_void, url: *const c_char, ) -> bool { let ctxt = ctxt as *mut R; let url = raw_to_string(url).unwrap(); (*ctxt).open_url(&url) } unsafe extern "C" fn cb_run_progress_dialog( ctxt: *mut c_void, title: *const c_char, can_cancel: bool, task: Option< unsafe extern "C" fn( *mut c_void, Option bool>, *mut c_void, ), >, task_ctxt: *mut c_void, ) -> bool { let ctxt = ctxt as *mut R; let title = raw_to_string(title).unwrap(); let task = InteractionHandlerTask { ctxt: task_ctxt, task, }; (*ctxt).run_progress_dialog(&title, can_cancel, &task) }