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 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 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 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 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}