binaryninja/interaction/
handler.rs

1use std::ffi::{c_char, c_void, CStr};
2use std::ptr;
3
4use binaryninjacore_sys::*;
5
6use crate::binary_view::BinaryView;
7use crate::flowgraph::FlowGraph;
8use crate::interaction::form::{Form, FormInputField};
9use crate::interaction::report::{Report, ReportCollection};
10use crate::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon};
11use crate::string::{raw_to_string, BnString};
12
13pub fn register_interaction_handler<R: InteractionHandler>(custom: R) {
14    let leak_custom = Box::leak(Box::new(custom));
15    let mut callbacks = BNInteractionHandlerCallbacks {
16        context: leak_custom as *mut R as *mut c_void,
17        showPlainTextReport: Some(cb_show_plain_text_report::<R>),
18        showMarkdownReport: Some(cb_show_markdown_report::<R>),
19        showHTMLReport: Some(cb_show_html_report::<R>),
20        showGraphReport: Some(cb_show_graph_report::<R>),
21        showReportCollection: Some(cb_show_report_collection::<R>),
22        getTextLineInput: Some(cb_get_text_line_input::<R>),
23        getIntegerInput: Some(cb_get_integer_input::<R>),
24        getAddressInput: Some(cb_get_address_input::<R>),
25        getChoiceInput: Some(cb_get_choice_input::<R>),
26        getLargeChoiceInput: Some(cb_get_large_choice_input::<R>),
27        getOpenFileNameInput: Some(cb_get_open_file_name_input::<R>),
28        getSaveFileNameInput: Some(cb_get_save_file_name_input::<R>),
29        getDirectoryNameInput: Some(cb_get_directory_name_input::<R>),
30        getCheckboxInput: Some(cb_get_checkbox_input::<R>),
31        getFormInput: Some(cb_get_form_input::<R>),
32        showMessageBox: Some(cb_show_message_box::<R>),
33        openUrl: Some(cb_open_url::<R>),
34        runProgressDialog: Some(cb_run_progress_dialog::<R>),
35    };
36    unsafe { BNRegisterInteractionHandler(&mut callbacks) }
37}
38
39pub trait InteractionHandler: Sync + Send + 'static {
40    fn show_message_box(
41        &mut self,
42        title: &str,
43        text: &str,
44        buttons: MessageBoxButtonSet,
45        icon: MessageBoxIcon,
46    ) -> MessageBoxButtonResult;
47
48    fn open_url(&mut self, url: &str) -> bool;
49
50    fn run_progress_dialog(
51        &mut self,
52        title: &str,
53        can_cancel: bool,
54        task: &InteractionHandlerTask,
55    ) -> bool;
56
57    fn show_plain_text_report(&mut self, view: Option<&BinaryView>, title: &str, contents: &str);
58
59    fn show_graph_report(&mut self, view: Option<&BinaryView>, title: &str, graph: &FlowGraph);
60
61    fn show_markdown_report(
62        &mut self,
63        view: Option<&BinaryView>,
64        title: &str,
65        _contents: &str,
66        plain_text: &str,
67    ) {
68        self.show_plain_text_report(view, title, plain_text);
69    }
70
71    fn show_html_report(
72        &mut self,
73        view: Option<&BinaryView>,
74        title: &str,
75        _contents: &str,
76        plain_text: &str,
77    ) {
78        self.show_plain_text_report(view, title, plain_text);
79    }
80
81    fn show_report_collection(&mut self, _title: &str, reports: &ReportCollection) {
82        for report in reports {
83            match &report {
84                Report::PlainText(rpt) => self.show_plain_text_report(
85                    report.view().as_deref(),
86                    &report.title(),
87                    &rpt.contents(),
88                ),
89                Report::Markdown(rm) => self.show_markdown_report(
90                    report.view().as_deref(),
91                    &report.title(),
92                    &rm.contents(),
93                    &rm.plaintext(),
94                ),
95                Report::Html(rh) => self.show_html_report(
96                    report.view().as_deref(),
97                    &report.title(),
98                    &rh.contents(),
99                    &rh.plaintext(),
100                ),
101                Report::FlowGraph(rfg) => self.show_graph_report(
102                    report.view().as_deref(),
103                    &report.title(),
104                    &rfg.flow_graph(),
105                ),
106            }
107        }
108    }
109
110    fn get_form_input(&mut self, form: &mut Form) -> bool;
111
112    fn get_text_line_input(&mut self, prompt: &str, title: &str) -> Option<String> {
113        let mut form = Form::new(title.to_owned());
114        form.add_field(FormInputField::TextLine {
115            prompt: prompt.to_string(),
116            default: None,
117            value: None,
118        });
119        if !self.get_form_input(&mut form) {
120            return None;
121        }
122        form.get_field_with_name(prompt)
123            .and_then(|f| f.try_value_string())
124    }
125
126    fn get_integer_input(&mut self, prompt: &str, title: &str) -> Option<i64> {
127        let mut form = Form::new(title.to_owned());
128        form.add_field(FormInputField::Integer {
129            prompt: prompt.to_string(),
130            value: 0,
131            default: None,
132        });
133        if !self.get_form_input(&mut form) {
134            return None;
135        }
136        form.get_field_with_name(prompt)
137            .and_then(|f| f.try_value_int())
138    }
139
140    fn get_address_input(
141        &mut self,
142        prompt: &str,
143        title: &str,
144        view: Option<&BinaryView>,
145        current_addr: u64,
146    ) -> Option<u64> {
147        let mut form = Form::new(title.to_owned());
148        form.add_field(FormInputField::Address {
149            prompt: prompt.to_string(),
150            view: view.map(|v| v.to_owned()),
151            current_address: current_addr,
152            value: 0,
153            default: None,
154        });
155        if !self.get_form_input(&mut form) {
156            return None;
157        }
158        form.get_field_with_name(prompt)
159            .and_then(|f| f.try_value_address())
160    }
161
162    fn get_choice_input(
163        &mut self,
164        prompt: &str,
165        title: &str,
166        choices: Vec<String>,
167    ) -> Option<usize> {
168        let mut form = Form::new(title.to_owned());
169        form.add_field(FormInputField::Choice {
170            prompt: prompt.to_string(),
171            choices,
172            default: None,
173            value: 0,
174        });
175        if !self.get_form_input(&mut form) {
176            return None;
177        }
178        form.get_field_with_name(prompt)
179            .and_then(|f| f.try_value_index())
180    }
181
182    fn get_large_choice_input(
183        &mut self,
184        prompt: &str,
185        title: &str,
186        choices: Vec<String>,
187    ) -> Option<usize> {
188        self.get_choice_input(prompt, title, choices)
189    }
190
191    fn get_open_file_name_input(
192        &mut self,
193        prompt: &str,
194        extension: Option<String>,
195    ) -> Option<String> {
196        let mut form = Form::new(prompt.to_owned());
197        form.add_field(FormInputField::OpenFileName {
198            prompt: prompt.to_string(),
199            default: None,
200            value: None,
201            extension,
202        });
203        if !self.get_form_input(&mut form) {
204            return None;
205        }
206        form.get_field_with_name(prompt)
207            .and_then(|f| f.try_value_string())
208    }
209
210    fn get_save_file_name_input(
211        &mut self,
212        prompt: &str,
213        extension: Option<String>,
214        default: Option<String>,
215    ) -> Option<String> {
216        let mut form = Form::new(prompt.to_owned());
217        form.add_field(FormInputField::SaveFileName {
218            prompt: prompt.to_string(),
219            extension,
220            default,
221            value: None,
222        });
223        if !self.get_form_input(&mut form) {
224            return None;
225        }
226        form.get_field_with_name(prompt)
227            .and_then(|f| f.try_value_string())
228    }
229
230    fn get_directory_name_input(
231        &mut self,
232        prompt: &str,
233        default: Option<String>,
234    ) -> Option<String> {
235        let mut form = Form::new(prompt.to_owned());
236        form.add_field(FormInputField::DirectoryName {
237            prompt: prompt.to_string(),
238            default,
239            value: None,
240        });
241        if !self.get_form_input(&mut form) {
242            return None;
243        }
244        form.get_field_with_name(prompt)
245            .and_then(|f| f.try_value_string())
246    }
247
248    fn get_checkbox_input(
249        &mut self,
250        prompt: &str,
251        title: &str,
252        default: Option<i64>,
253    ) -> Option<i64> {
254        let mut form = Form::new(title.to_owned());
255        form.add_field(FormInputField::Checkbox {
256            prompt: prompt.to_string(),
257            default: default.map(|b| b == 1),
258            value: default.map(|b| b == 1)?,
259        });
260        if !self.get_form_input(&mut form) {
261            return None;
262        }
263        form.get_field_with_name(prompt)
264            .and_then(|f| f.try_value_int())
265            .map(|b| if b != 0 { 1 } else { 0 })
266    }
267}
268
269pub struct InteractionHandlerTask {
270    ctxt: *mut c_void,
271    task: Option<
272        unsafe extern "C" fn(
273            *mut c_void,
274            progress: Option<unsafe extern "C" fn(*mut c_void, cur: usize, max: usize) -> bool>,
275            *mut c_void,
276        ),
277    >,
278}
279
280impl InteractionHandlerTask {
281    pub fn task<P: FnMut(usize, usize) -> bool>(&mut self, progress: &mut P) {
282        let Some(task) = self.task else {
283            // Assuming a nullptr task mean nothing need to be done
284            return;
285        };
286
287        let progress_ctxt = progress as *mut P as *mut c_void;
288        ffi_wrap!("custom_interaction_run_progress_dialog", unsafe {
289            task(
290                self.ctxt,
291                Some(cb_custom_interaction_handler_task::<P>),
292                progress_ctxt,
293            )
294        })
295    }
296}
297
298unsafe extern "C" fn cb_custom_interaction_handler_task<P: FnMut(usize, usize) -> bool>(
299    ctxt: *mut c_void,
300    cur: usize,
301    max: usize,
302) -> bool {
303    let ctxt = ctxt as *mut P;
304    (*ctxt)(cur, max)
305}
306
307unsafe extern "C" fn cb_show_plain_text_report<R: InteractionHandler>(
308    ctxt: *mut c_void,
309    view: *mut BNBinaryView,
310    title: *const c_char,
311    contents: *const c_char,
312) {
313    let ctxt = ctxt as *mut R;
314    let title = raw_to_string(title).unwrap();
315    let contents = raw_to_string(contents).unwrap();
316    let view = match !view.is_null() {
317        true => Some(BinaryView::from_raw(view)),
318        false => None,
319    };
320    (*ctxt).show_plain_text_report(view.as_ref(), &title, &contents)
321}
322
323unsafe extern "C" fn cb_show_markdown_report<R: InteractionHandler>(
324    ctxt: *mut c_void,
325    view: *mut BNBinaryView,
326    title: *const c_char,
327    contents: *const c_char,
328    plaintext: *const c_char,
329) {
330    let ctxt = ctxt as *mut R;
331    let title = raw_to_string(title).unwrap();
332    let contents = raw_to_string(contents).unwrap();
333    let plaintext = raw_to_string(plaintext).unwrap();
334    let view = match !view.is_null() {
335        true => Some(BinaryView::from_raw(view)),
336        false => None,
337    };
338    (*ctxt).show_markdown_report(view.as_ref(), &title, &contents, &plaintext)
339}
340
341unsafe extern "C" fn cb_show_html_report<R: InteractionHandler>(
342    ctxt: *mut c_void,
343    view: *mut BNBinaryView,
344    title: *const c_char,
345    contents: *const c_char,
346    plaintext: *const c_char,
347) {
348    let ctxt = ctxt as *mut R;
349    let title = raw_to_string(title).unwrap();
350    let contents = raw_to_string(contents).unwrap();
351    let plaintext = raw_to_string(plaintext).unwrap();
352    let view = match !view.is_null() {
353        true => Some(BinaryView::from_raw(view)),
354        false => None,
355    };
356    (*ctxt).show_html_report(view.as_ref(), &title, &contents, &plaintext)
357}
358
359unsafe extern "C" fn cb_show_graph_report<R: InteractionHandler>(
360    ctxt: *mut c_void,
361    view: *mut BNBinaryView,
362    title: *const c_char,
363    graph: *mut BNFlowGraph,
364) {
365    let ctxt = ctxt as *mut R;
366    let title = raw_to_string(title).unwrap();
367    let view = match !view.is_null() {
368        true => Some(BinaryView::from_raw(view)),
369        false => None,
370    };
371    (*ctxt).show_graph_report(view.as_ref(), &title, &FlowGraph::from_raw(graph))
372}
373
374unsafe extern "C" fn cb_show_report_collection<R: InteractionHandler>(
375    ctxt: *mut c_void,
376    title: *const c_char,
377    report: *mut BNReportCollection,
378) {
379    let ctxt = ctxt as *mut R;
380    let title = raw_to_string(title).unwrap();
381    (*ctxt).show_report_collection(
382        &title,
383        &ReportCollection::from_raw(ptr::NonNull::new(report).unwrap()),
384    )
385}
386
387unsafe extern "C" fn cb_get_text_line_input<R: InteractionHandler>(
388    ctxt: *mut c_void,
389    result_ffi: *mut *mut c_char,
390    prompt: *const c_char,
391    title: *const c_char,
392) -> bool {
393    let ctxt = ctxt as *mut R;
394    let prompt = raw_to_string(prompt).unwrap();
395    let title = raw_to_string(title).unwrap();
396    let result = (*ctxt).get_text_line_input(&prompt, &title);
397    if let Some(result) = result {
398        unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) };
399        true
400    } else {
401        unsafe { *result_ffi = ptr::null_mut() };
402        false
403    }
404}
405
406unsafe extern "C" fn cb_get_integer_input<R: InteractionHandler>(
407    ctxt: *mut c_void,
408    result_ffi: *mut i64,
409    prompt: *const c_char,
410    title: *const c_char,
411) -> bool {
412    let ctxt = ctxt as *mut R;
413    let prompt = raw_to_string(prompt).unwrap();
414    let title = raw_to_string(title).unwrap();
415    let result = (*ctxt).get_integer_input(&prompt, &title);
416    if let Some(result) = result {
417        unsafe { *result_ffi = result };
418        true
419    } else {
420        unsafe { *result_ffi = 0 };
421        false
422    }
423}
424
425unsafe extern "C" fn cb_get_address_input<R: InteractionHandler>(
426    ctxt: *mut c_void,
427    result_ffi: *mut u64,
428    prompt: *const c_char,
429    title: *const c_char,
430    view: *mut BNBinaryView,
431    current_addr: u64,
432) -> bool {
433    let ctxt = ctxt as *mut R;
434    let prompt = raw_to_string(prompt).unwrap();
435    let title = raw_to_string(title).unwrap();
436    let view = (!view.is_null()).then(|| BinaryView::from_raw(view));
437    let result = (*ctxt).get_address_input(&prompt, &title, view.as_ref(), current_addr);
438    if let Some(result) = result {
439        unsafe { *result_ffi = result };
440        true
441    } else {
442        unsafe { *result_ffi = 0 };
443        false
444    }
445}
446
447unsafe extern "C" fn cb_get_choice_input<R: InteractionHandler>(
448    ctxt: *mut c_void,
449    result_ffi: *mut usize,
450    prompt: *const c_char,
451    title: *const c_char,
452    choices: *mut *const c_char,
453    count: usize,
454) -> bool {
455    let ctxt = ctxt as *mut R;
456    let prompt = raw_to_string(prompt).unwrap();
457    let title = raw_to_string(title).unwrap();
458    let choices = unsafe { core::slice::from_raw_parts(choices, count) };
459    // SAFETY: BnString and *const c_char are transparent
460    let choices = unsafe { core::mem::transmute::<&[*const c_char], &[BnString]>(choices) };
461    let choices: Vec<String> = choices
462        .iter()
463        .map(|x| x.to_string_lossy().to_string())
464        .collect();
465    let result = (*ctxt).get_choice_input(&prompt, &title, choices);
466    if let Some(result) = result {
467        unsafe { *result_ffi = result };
468        true
469    } else {
470        unsafe { *result_ffi = 0 };
471        false
472    }
473}
474
475unsafe extern "C" fn cb_get_large_choice_input<R: InteractionHandler>(
476    ctxt: *mut c_void,
477    result_ffi: *mut usize,
478    prompt: *const c_char,
479    title: *const c_char,
480    choices: *mut *const c_char,
481    count: usize,
482) -> bool {
483    let ctxt = ctxt as *mut R;
484    let prompt = raw_to_string(prompt).unwrap();
485    let title = raw_to_string(title).unwrap();
486    let choices = unsafe { core::slice::from_raw_parts(choices, count) };
487    // SAFETY: BnString and *const c_char are transparent
488    let choices = unsafe { core::mem::transmute::<&[*const c_char], &[BnString]>(choices) };
489    let choices: Vec<String> = choices
490        .iter()
491        .map(|x| x.to_string_lossy().to_string())
492        .collect();
493    let result = (*ctxt).get_large_choice_input(&prompt, &title, choices);
494    if let Some(result) = result {
495        unsafe { *result_ffi = result };
496        true
497    } else {
498        unsafe { *result_ffi = 0 };
499        false
500    }
501}
502
503unsafe extern "C" fn cb_get_open_file_name_input<R: InteractionHandler>(
504    ctxt: *mut c_void,
505    result_ffi: *mut *mut c_char,
506    prompt: *const c_char,
507    ext: *const c_char,
508) -> bool {
509    let ctxt = ctxt as *mut R;
510    let prompt = raw_to_string(prompt).unwrap();
511    let ext = (!ext.is_null()).then(|| unsafe { CStr::from_ptr(ext) });
512    let result =
513        (*ctxt).get_open_file_name_input(&prompt, ext.map(|x| x.to_string_lossy().to_string()));
514    if let Some(result) = result {
515        unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) };
516        true
517    } else {
518        unsafe { *result_ffi = ptr::null_mut() };
519        false
520    }
521}
522
523unsafe extern "C" fn cb_get_save_file_name_input<R: InteractionHandler>(
524    ctxt: *mut c_void,
525    result_ffi: *mut *mut c_char,
526    prompt: *const c_char,
527    ext: *const c_char,
528    default_name: *const c_char,
529) -> bool {
530    let ctxt = ctxt as *mut R;
531    let prompt = raw_to_string(prompt).unwrap();
532    let ext = raw_to_string(ext);
533    let default_name = raw_to_string(default_name);
534    let result = (*ctxt).get_save_file_name_input(&prompt, ext, default_name);
535    if let Some(result) = result {
536        unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) };
537        true
538    } else {
539        unsafe { *result_ffi = ptr::null_mut() };
540        false
541    }
542}
543
544unsafe extern "C" fn cb_get_directory_name_input<R: InteractionHandler>(
545    ctxt: *mut c_void,
546    result_ffi: *mut *mut c_char,
547    prompt: *const c_char,
548    default_name: *const c_char,
549) -> bool {
550    let ctxt = ctxt as *mut R;
551    let prompt = raw_to_string(prompt).unwrap();
552    let default_name = raw_to_string(default_name);
553    let result = (*ctxt).get_directory_name_input(&prompt, default_name);
554    if let Some(result) = result {
555        unsafe { *result_ffi = BnString::into_raw(BnString::new(result)) };
556        true
557    } else {
558        unsafe { *result_ffi = ptr::null_mut() };
559        false
560    }
561}
562
563unsafe extern "C" fn cb_get_checkbox_input<R: InteractionHandler>(
564    ctxt: *mut c_void,
565    result_ffi: *mut i64,
566    prompt: *const c_char,
567    title: *const c_char,
568    default_choice: *const i64,
569) -> bool {
570    let ctxt = ctxt as *mut R;
571    let prompt = raw_to_string(prompt).unwrap();
572    let title = raw_to_string(title).unwrap();
573    let default = (!default_choice.is_null()).then(|| *default_choice);
574    let result = (*ctxt).get_checkbox_input(&prompt, &title, default);
575    if let Some(result) = result {
576        unsafe { *result_ffi = result };
577        true
578    } else {
579        unsafe { *result_ffi = 0 };
580        false
581    }
582}
583
584unsafe extern "C" fn cb_get_form_input<R: InteractionHandler>(
585    ctxt: *mut c_void,
586    fields: *mut BNFormInputField,
587    count: usize,
588    title: *const c_char,
589) -> bool {
590    let ctxt = ctxt as *mut R;
591    let raw_fields = unsafe { core::slice::from_raw_parts_mut(fields, count) };
592    let fields: Vec<_> = raw_fields
593        .iter_mut()
594        .map(|x| FormInputField::from_raw(x))
595        .collect();
596    let title = raw_to_string(title).unwrap();
597    let mut form = Form::new_with_fields(title, fields);
598    let results = (*ctxt).get_form_input(&mut form);
599    // Update the fields with the new values. Freeing the old ones.
600    raw_fields
601        .iter_mut()
602        .enumerate()
603        .for_each(|(idx, raw_field)| {
604            FormInputField::free_raw(*raw_field);
605            *raw_field = form.fields[idx].into_raw();
606        });
607    results
608}
609
610unsafe extern "C" fn cb_show_message_box<R: InteractionHandler>(
611    ctxt: *mut c_void,
612    title: *const c_char,
613    text: *const c_char,
614    buttons: BNMessageBoxButtonSet,
615    icon: BNMessageBoxIcon,
616) -> BNMessageBoxButtonResult {
617    let ctxt = ctxt as *mut R;
618    let title = raw_to_string(title).unwrap();
619    let text = raw_to_string(text).unwrap();
620    (*ctxt).show_message_box(&title, &text, buttons, icon)
621}
622
623unsafe extern "C" fn cb_open_url<R: InteractionHandler>(
624    ctxt: *mut c_void,
625    url: *const c_char,
626) -> bool {
627    let ctxt = ctxt as *mut R;
628    let url = raw_to_string(url).unwrap();
629    (*ctxt).open_url(&url)
630}
631
632unsafe extern "C" fn cb_run_progress_dialog<R: InteractionHandler>(
633    ctxt: *mut c_void,
634    title: *const c_char,
635    can_cancel: bool,
636    task: Option<
637        unsafe extern "C" fn(
638            *mut c_void,
639            Option<unsafe extern "C" fn(*mut c_void, usize, usize) -> bool>,
640            *mut c_void,
641        ),
642    >,
643    task_ctxt: *mut c_void,
644) -> bool {
645    let ctxt = ctxt as *mut R;
646    let title = raw_to_string(title).unwrap();
647    let task = InteractionHandlerTask {
648        ctxt: task_ctxt,
649        task,
650    };
651    (*ctxt).run_progress_dialog(&title, can_cancel, &task)
652}