1use std::ffi::c_char;
2
3use binaryninjacore_sys::*;
4
5use crate::binary_view::BinaryView;
6use crate::rc::{Array, Ref};
7use crate::string::{raw_to_string, strings_to_string_list, BnString, IntoCStr};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Form {
11 pub title: String,
12 pub fields: Vec<FormInputField>,
13}
14
15impl Form {
16 pub fn new(title: impl Into<String>) -> Self {
17 Self::new_with_fields(title, vec![])
18 }
19
20 pub fn new_with_fields(title: impl Into<String>, fields: Vec<FormInputField>) -> Self {
21 Self {
22 title: title.into(),
23 fields,
24 }
25 }
26
27 pub fn add_field(&mut self, field: FormInputField) {
28 self.fields.push(field);
29 }
30
31 pub fn prompt(&mut self) -> bool {
35 let title = self.title.clone().to_cstr();
36 let mut raw_fields = self
37 .fields
38 .iter()
39 .map(FormInputField::into_raw)
40 .collect::<Vec<_>>();
41 let success =
42 unsafe { BNGetFormInput(raw_fields.as_mut_ptr(), raw_fields.len(), title.as_ptr()) };
43 self.fields = raw_fields
45 .into_iter()
46 .map(FormInputField::from_owned_raw)
47 .collect();
48 success
49 }
50
51 pub fn get_field_with_name(&self, name: &str) -> Option<&FormInputField> {
52 self.fields
53 .iter()
54 .find(|field| field.try_prompt() == Some(name.to_string()))
55 }
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
60pub enum FormInputField {
61 Label {
62 prompt: String,
63 },
64 Separator,
65 TextLine {
66 prompt: String,
67 default: Option<String>,
68 value: Option<String>,
69 },
70 MultilineText {
71 prompt: String,
72 default: Option<String>,
73 value: Option<String>,
74 },
75 Integer {
76 prompt: String,
77 default: Option<i64>,
78 value: i64,
79 },
80 Address {
81 prompt: String,
82 view: Option<Ref<BinaryView>>,
83 current_address: u64,
84 default: Option<u64>,
85 value: u64,
86 },
87 Choice {
88 prompt: String,
89 choices: Vec<String>,
90 default: Option<usize>,
91 value: usize,
92 },
93 OpenFileName {
94 prompt: String,
95 extension: Option<String>,
97 default: Option<String>,
98 value: Option<String>,
99 },
100 SaveFileName {
101 prompt: String,
102 extension: Option<String>,
104 default: Option<String>,
105 value: Option<String>,
106 },
107 DirectoryName {
108 prompt: String,
109 default: Option<String>,
110 value: Option<String>,
111 },
112 Checkbox {
113 prompt: String,
114 default: Option<bool>,
115 value: bool,
116 },
117}
118
119impl FormInputField {
120 pub fn from_raw(value: &BNFormInputField) -> Self {
121 let prompt = raw_to_string(value.prompt).unwrap_or_default();
122 let view = match value.view.is_null() {
123 false => Some(unsafe { BinaryView::from_raw(value.view) }.to_owned()),
124 true => None,
125 };
126 let name_default = value
127 .hasDefault
128 .then_some(raw_to_string(value.defaultName).unwrap_or_default());
129 let string_default = value
130 .hasDefault
131 .then_some(raw_to_string(value.stringDefault).unwrap_or_default());
132 let int_default = value.hasDefault.then_some(value.intDefault);
133 let address_default = value.hasDefault.then_some(value.addressDefault);
134 let index_default = value.hasDefault.then_some(value.indexDefault);
135 let bool_default = int_default.map(|i| i != 0);
136 let extension = raw_to_string(value.ext);
137 let current_address = value.currentAddress;
138 let string_result = raw_to_string(value.stringResult);
139 let int_result = value.intResult;
140 let address_result = value.addressResult;
141 let index_result = value.indexResult;
142 match value.type_ {
143 BNFormInputFieldType::LabelFormField => Self::Label { prompt },
144 BNFormInputFieldType::SeparatorFormField => Self::Separator,
145 BNFormInputFieldType::TextLineFormField => Self::TextLine {
146 prompt,
147 default: string_default,
148 value: string_result,
149 },
150 BNFormInputFieldType::MultilineTextFormField => Self::MultilineText {
151 prompt,
152 default: string_default,
153 value: string_result,
154 },
155 BNFormInputFieldType::IntegerFormField => Self::Integer {
156 prompt,
157 default: int_default,
158 value: int_result,
159 },
160 BNFormInputFieldType::AddressFormField => Self::Address {
161 prompt,
162 view,
163 current_address,
164 default: address_default,
165 value: address_result,
166 },
167 BNFormInputFieldType::ChoiceFormField => Self::Choice {
168 prompt,
169 choices: vec![],
170 default: index_default,
171 value: index_result,
172 },
173 BNFormInputFieldType::OpenFileNameFormField => Self::OpenFileName {
174 prompt,
175 extension,
176 default: string_default,
177 value: string_result,
178 },
179 BNFormInputFieldType::SaveFileNameFormField => Self::SaveFileName {
180 prompt,
181 extension,
182 default: name_default,
183 value: string_result,
184 },
185 BNFormInputFieldType::DirectoryNameFormField => Self::DirectoryName {
186 prompt,
187 default: name_default,
188 value: string_result,
189 },
190 BNFormInputFieldType::CheckboxFormField => Self::Checkbox {
191 prompt,
192 default: bool_default,
193 value: value.intResult != 0,
194 },
195 }
196 }
197
198 pub fn from_owned_raw(value: BNFormInputField) -> Self {
199 let owned = Self::from_raw(&value);
200 Self::free_raw(value);
201 owned
202 }
203
204 pub fn into_raw(&self) -> BNFormInputField {
205 let bn_prompt = BnString::new(self.try_prompt().unwrap_or_default());
206 let bn_extension = BnString::new(self.try_extension().unwrap_or_default());
207 let bn_default_string = BnString::new(self.try_default_string().unwrap_or_default());
208 let bn_default_name = BnString::new(self.try_default_name().unwrap_or_default());
209 let bn_value_string = BnString::new(self.try_value_string().unwrap_or_default());
210 BNFormInputField {
212 type_: self.as_type(),
213 prompt: BnString::into_raw(bn_prompt),
214 view: match self.try_view() {
215 None => std::ptr::null_mut(),
216 Some(view) => unsafe { Ref::into_raw(view) }.handle,
217 },
218 currentAddress: self.try_current_address().unwrap_or_default(),
219 choices: match self.try_choices() {
220 None => std::ptr::null_mut(),
221 Some(choices) => strings_to_string_list(choices.as_slice()) as *mut *const c_char,
222 },
223 count: self.try_choices().unwrap_or_default().len(),
225 ext: BnString::into_raw(bn_extension),
226 defaultName: BnString::into_raw(bn_default_name),
227 intResult: self.try_value_int().unwrap_or_default(),
228 addressResult: self.try_value_address().unwrap_or_default(),
229 stringResult: BnString::into_raw(bn_value_string),
230 indexResult: self.try_value_index().unwrap_or_default(),
231 hasDefault: self.try_has_default().unwrap_or_default(),
232 intDefault: self.try_default_int().unwrap_or_default(),
233 addressDefault: self.try_default_address().unwrap_or_default(),
234 stringDefault: BnString::into_raw(bn_default_string),
235 indexDefault: self.try_default_index().unwrap_or_default(),
236 }
237 }
238
239 pub fn free_raw(value: BNFormInputField) {
240 unsafe {
241 BnString::free_raw(value.defaultName as *mut c_char);
242 BnString::free_raw(value.prompt as *mut c_char);
243 BnString::free_raw(value.ext as *mut c_char);
244 BnString::free_raw(value.stringDefault as *mut c_char);
245 BnString::free_raw(value.stringResult);
246 Array::<BnString>::new(value.choices as *mut *mut c_char, value.count, ());
248 if !value.view.is_null() {
250 BinaryView::ref_from_raw(value.view);
251 }
252 }
253 }
254
255 pub fn as_type(&self) -> BNFormInputFieldType {
256 match self {
257 FormInputField::Label { .. } => BNFormInputFieldType::LabelFormField,
258 FormInputField::Separator => BNFormInputFieldType::SeparatorFormField,
259 FormInputField::TextLine { .. } => BNFormInputFieldType::TextLineFormField,
260 FormInputField::MultilineText { .. } => BNFormInputFieldType::MultilineTextFormField,
261 FormInputField::Integer { .. } => BNFormInputFieldType::IntegerFormField,
262 FormInputField::Address { .. } => BNFormInputFieldType::AddressFormField,
263 FormInputField::Choice { .. } => BNFormInputFieldType::ChoiceFormField,
264 FormInputField::OpenFileName { .. } => BNFormInputFieldType::OpenFileNameFormField,
265 FormInputField::SaveFileName { .. } => BNFormInputFieldType::SaveFileNameFormField,
266 FormInputField::DirectoryName { .. } => BNFormInputFieldType::DirectoryNameFormField,
267 FormInputField::Checkbox { .. } => BNFormInputFieldType::CheckboxFormField,
268 }
269 }
270
271 pub fn try_prompt(&self) -> Option<String> {
273 match self {
274 FormInputField::Label { prompt, .. } => Some(prompt.clone()),
275 FormInputField::Separator => None,
276 FormInputField::TextLine { prompt, .. } => Some(prompt.clone()),
277 FormInputField::MultilineText { prompt, .. } => Some(prompt.clone()),
278 FormInputField::Integer { prompt, .. } => Some(prompt.clone()),
279 FormInputField::Address { prompt, .. } => Some(prompt.clone()),
280 FormInputField::Choice { prompt, .. } => Some(prompt.clone()),
281 FormInputField::OpenFileName { prompt, .. } => Some(prompt.clone()),
282 FormInputField::SaveFileName { prompt, .. } => Some(prompt.clone()),
283 FormInputField::DirectoryName { prompt, .. } => Some(prompt.clone()),
284 FormInputField::Checkbox { prompt, .. } => Some(prompt.clone()),
285 }
286 }
287
288 pub fn try_view(&self) -> Option<Ref<BinaryView>> {
290 match self {
291 FormInputField::Address { view, .. } => view.clone(),
292 _ => None,
293 }
294 }
295
296 pub fn try_current_address(&self) -> Option<u64> {
298 match self {
299 FormInputField::Address {
300 current_address, ..
301 } => Some(*current_address),
302 _ => None,
303 }
304 }
305
306 pub fn try_choices(&self) -> Option<Vec<String>> {
308 match self {
309 FormInputField::Choice { choices, .. } => Some(choices.clone()),
310 _ => None,
311 }
312 }
313
314 pub fn try_extension(&self) -> Option<String> {
316 match self {
317 Self::SaveFileName { extension, .. } => extension.clone(),
318 Self::OpenFileName { extension, .. } => extension.clone(),
319 _ => None,
320 }
321 }
322
323 pub fn try_has_default(&self) -> Option<bool> {
325 match self {
326 FormInputField::Label { .. } => None,
327 FormInputField::Separator => None,
328 FormInputField::TextLine { default, .. } => Some(default.is_some()),
329 FormInputField::MultilineText { default, .. } => Some(default.is_some()),
330 FormInputField::Integer { default, .. } => Some(default.is_some()),
331 FormInputField::Address { default, .. } => Some(default.is_some()),
332 FormInputField::Choice { default, .. } => Some(default.is_some()),
333 FormInputField::OpenFileName { default, .. } => Some(default.is_some()),
334 FormInputField::SaveFileName { default, .. } => Some(default.is_some()),
335 FormInputField::DirectoryName { default, .. } => Some(default.is_some()),
336 FormInputField::Checkbox { default, .. } => Some(default.is_some()),
337 }
338 }
339
340 pub fn try_default_name(&self) -> Option<String> {
342 match self {
343 Self::SaveFileName { default, .. } => default.clone(),
344 Self::DirectoryName { default, .. } => default.clone(),
345 _ => None,
346 }
347 }
348
349 pub fn try_default_int(&self) -> Option<i64> {
351 match self {
352 FormInputField::Integer { default, .. } => *default,
353 FormInputField::Checkbox { default, .. } => default.map(|b| b as i64),
354 _ => None,
355 }
356 }
357
358 pub fn try_default_address(&self) -> Option<u64> {
360 match self {
361 FormInputField::Address { default, .. } => *default,
362 _ => None,
363 }
364 }
365
366 pub fn try_default_string(&self) -> Option<String> {
368 match self {
369 FormInputField::TextLine { default, .. } => default.clone(),
370 FormInputField::MultilineText { default, .. } => default.clone(),
371 FormInputField::OpenFileName { default, .. } => default.clone(),
372 FormInputField::SaveFileName { default, .. } => default.clone(),
373 FormInputField::DirectoryName { default, .. } => default.clone(),
374 _ => None,
375 }
376 }
377
378 pub fn try_default_index(&self) -> Option<usize> {
380 match self {
381 FormInputField::Choice { default, .. } => *default,
382 _ => None,
383 }
384 }
385
386 pub fn try_value_int(&self) -> Option<i64> {
388 match self {
389 FormInputField::Integer { value, .. } => Some(*value),
390 FormInputField::Checkbox { value, .. } => Some(*value as i64),
391 _ => None,
392 }
393 }
394
395 pub fn try_value_address(&self) -> Option<u64> {
397 match self {
398 FormInputField::Address { value, .. } => Some(*value),
399 _ => None,
400 }
401 }
402
403 pub fn try_value_string(&self) -> Option<String> {
405 match self {
406 FormInputField::TextLine { value, .. } => value.clone(),
407 FormInputField::MultilineText { value, .. } => value.clone(),
408 FormInputField::OpenFileName { value, .. } => value.clone(),
409 FormInputField::SaveFileName { value, .. } => value.clone(),
410 FormInputField::DirectoryName { value, .. } => value.clone(),
411 _ => None,
412 }
413 }
414
415 pub fn try_value_index(&self) -> Option<usize> {
417 match self {
418 FormInputField::Choice { value, .. } => Some(*value),
419 _ => None,
420 }
421 }
422}