binaryninja/
interaction.rs

1// Copyright 2022-2025 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Interfaces for asking the user for information: forms, opening files, etc.
16
17use std::ffi::{c_char, c_void};
18use std::path::PathBuf;
19
20use binaryninjacore_sys::*;
21
22use crate::string::{BnString, IntoCStr};
23
24pub mod form;
25pub mod handler;
26pub mod report;
27
28// Re-export the public items from the submodules, for convenience.
29pub use form::*;
30pub use handler::*;
31pub use report::*;
32
33pub type MessageBoxButtonSet = BNMessageBoxButtonSet;
34pub type MessageBoxIcon = BNMessageBoxIcon;
35pub type MessageBoxButtonResult = BNMessageBoxButtonResult;
36
37pub fn get_text_line_input(prompt: &str, title: &str) -> Option<String> {
38    let mut value: *mut c_char = std::ptr::null_mut();
39
40    let prompt = prompt.to_cstr();
41    let title = title.to_cstr();
42    let result = unsafe { BNGetTextLineInput(&mut value, prompt.as_ptr(), title.as_ptr()) };
43    if !result {
44        return None;
45    }
46
47    Some(unsafe { BnString::into_string(value) })
48}
49
50pub fn get_integer_input(prompt: &str, title: &str) -> Option<i64> {
51    let mut value: i64 = 0;
52
53    let prompt = prompt.to_cstr();
54    let title = title.to_cstr();
55    let result = unsafe { BNGetIntegerInput(&mut value, prompt.as_ptr(), title.as_ptr()) };
56
57    if !result {
58        return None;
59    }
60
61    Some(value)
62}
63
64pub fn get_address_input(prompt: &str, title: &str) -> Option<u64> {
65    let mut value: u64 = 0;
66
67    let prompt = prompt.to_cstr();
68    let title = title.to_cstr();
69    let result = unsafe {
70        BNGetAddressInput(
71            &mut value,
72            prompt.as_ptr(),
73            title.as_ptr(),
74            std::ptr::null_mut(),
75            0,
76        )
77    };
78
79    if !result {
80        return None;
81    }
82
83    Some(value)
84}
85
86pub fn get_choice_input(prompt: &str, title: &str, choices: &[&str]) -> Option<usize> {
87    let prompt = prompt.to_cstr();
88    let title = title.to_cstr();
89    let mut choices_inner: Vec<BnString> = choices.iter().copied().map(BnString::new).collect();
90    // SAFETY BnString and *const c_char are transparent
91    let choices: &mut [*const c_char] = unsafe {
92        core::mem::transmute::<&mut [BnString], &mut [*const c_char]>(&mut choices_inner[..])
93    };
94    let mut result = 0;
95    let succ = unsafe {
96        BNGetChoiceInput(
97            &mut result,
98            prompt.as_ptr(),
99            title.as_ptr(),
100            choices.as_mut_ptr(),
101            choices.len(),
102        )
103    };
104    succ.then_some(result)
105}
106
107pub fn get_large_choice_input(prompt: &str, title: &str, choices: &[&str]) -> Option<usize> {
108    let prompt = prompt.to_cstr();
109    let title = title.to_cstr();
110    let mut choices_inner: Vec<BnString> = choices.iter().copied().map(BnString::new).collect();
111    // SAFETY BnString and *const c_char are transparent
112    let choices: &mut [*const c_char] = unsafe {
113        core::mem::transmute::<&mut [BnString], &mut [*const c_char]>(&mut choices_inner[..])
114    };
115    let mut result = 0;
116    let succ = unsafe {
117        BNGetLargeChoiceInput(
118            &mut result,
119            prompt.as_ptr(),
120            title.as_ptr(),
121            choices.as_mut_ptr(),
122            choices.len(),
123        )
124    };
125    succ.then_some(result)
126}
127
128pub fn get_open_filename_input(prompt: &str, extension: &str) -> Option<PathBuf> {
129    let mut value: *mut c_char = std::ptr::null_mut();
130
131    let prompt = prompt.to_cstr();
132    let extension = extension.to_cstr();
133    let result = unsafe { BNGetOpenFileNameInput(&mut value, prompt.as_ptr(), extension.as_ptr()) };
134    if !result {
135        return None;
136    }
137
138    let path = unsafe { BnString::into_string(value) };
139    Some(PathBuf::from(path))
140}
141
142pub fn get_save_filename_input(
143    prompt: &str,
144    extension: &str,
145    default_name: &str,
146) -> Option<PathBuf> {
147    let mut value: *mut c_char = std::ptr::null_mut();
148
149    let prompt = prompt.to_cstr();
150    let extension = extension.to_cstr();
151    let default_name = default_name.to_cstr();
152    let result = unsafe {
153        BNGetSaveFileNameInput(
154            &mut value,
155            prompt.as_ptr(),
156            extension.as_ptr(),
157            default_name.as_ptr(),
158        )
159    };
160    if !result {
161        return None;
162    }
163
164    let path = unsafe { BnString::into_string(value) };
165    Some(PathBuf::from(path))
166}
167
168pub fn get_directory_name_input(prompt: &str, default_name: &str) -> Option<PathBuf> {
169    let mut value: *mut c_char = std::ptr::null_mut();
170
171    let prompt = prompt.to_cstr();
172    let default_name = default_name.to_cstr();
173    let result =
174        unsafe { BNGetDirectoryNameInput(&mut value, prompt.as_ptr(), default_name.as_ptr()) };
175    if !result {
176        return None;
177    }
178
179    let path = unsafe { BnString::into_string(value) };
180    Some(PathBuf::from(path))
181}
182
183pub fn show_message_box(
184    title: &str,
185    text: &str,
186    buttons: MessageBoxButtonSet,
187    icon: MessageBoxIcon,
188) -> MessageBoxButtonResult {
189    let title = title.to_cstr();
190    let text = text.to_cstr();
191    unsafe { BNShowMessageBox(title.as_ptr(), text.as_ptr(), buttons, icon) }
192}
193
194struct TaskContext<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(F);
195
196pub fn run_progress_dialog<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(
197    title: &str,
198    can_cancel: bool,
199    task: F,
200) -> Result<(), ()> {
201    let mut ctxt = TaskContext::<F>(task);
202
203    unsafe extern "C" fn cb_task<F: Fn(Box<dyn Fn(usize, usize) -> Result<(), ()>>)>(
204        ctxt: *mut c_void,
205        progress: Option<unsafe extern "C" fn(*mut c_void, usize, usize) -> bool>,
206        progress_ctxt: *mut c_void,
207    ) {
208        ffi_wrap!("run_progress_dialog", {
209            let context = ctxt as *mut TaskContext<F>;
210            let progress_fn = Box::new(move |cur: usize, max: usize| -> Result<(), ()> {
211                match progress {
212                    Some(func) => {
213                        if (func)(progress_ctxt, cur, max) {
214                            Ok(())
215                        } else {
216                            Err(())
217                        }
218                    }
219                    None => Ok(()),
220                }
221            });
222            ((*context).0)(progress_fn);
223        })
224    }
225
226    let title = title.to_cstr();
227    if unsafe {
228        BNRunProgressDialog(
229            title.as_ptr(),
230            can_cancel,
231            Some(cb_task::<F>),
232            &mut ctxt as *mut _ as *mut c_void,
233        )
234    } {
235        Ok(())
236    } else {
237        Err(())
238    }
239}