1use std::ffi::{c_char, c_void, CStr};
4use std::mem::MaybeUninit;
5use std::path::Path;
6use std::pin::Pin;
7
8use binaryninjacore_sys::*;
9
10use crate::basic_block::BasicBlock;
11use crate::binary_view::BinaryView;
12use crate::function::{Function, NativeBlock};
13use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable};
14use crate::string::{strings_to_string_list, BnString, IntoCStr};
15
16pub type ScriptingProviderExecuteResult = BNScriptingProviderExecuteResult;
17pub type ScriptingProviderInputReadyState = BNScriptingProviderInputReadyState;
18
19pub fn register_scripting_provider<S>(provider: S) -> (&'static S, ScriptingProvider)
21where
22 S: CustomScriptingProvider,
23{
24 let name = S::NAME.to_cstr();
25 let api_name = S::API_NAME.to_cstr();
26 let leaked_provider = Box::leak(Box::new(provider));
27 let result = unsafe {
28 BNRegisterScriptingProvider(
29 name.as_ptr(),
30 api_name.as_ptr(),
31 &mut BNScriptingProviderCallbacks {
32 context: leaked_provider as *mut _ as *mut c_void,
33 createInstance: Some(cb_create_instance::<S>),
34 loadModule: Some(cb_load_module::<S>),
35 installModules: Some(cb_install_modules::<S>),
36 },
37 )
38 };
39 assert!(
40 !result.is_null(),
41 "Should always be able to register a provider"
42 );
43 let provider_core = unsafe { ScriptingProvider::from_raw(result) };
44 (leaked_provider, provider_core)
45}
46
47pub trait CustomScriptingProvider {
48 type Instance: CustomScriptingInstance;
49
50 const NAME: &'static str;
51 const API_NAME: &'static str;
52
53 fn load_module(&self, repo_path: &str, plugin_path: &str, force: bool) -> bool;
54
55 fn install_modules(&self, modules: &str) -> bool;
56
57 fn create_instance(&self) -> Self::Instance;
58}
59
60pub trait CustomScriptingInstance {
61 fn execute_script_input(
62 &self,
63 instance: &ScriptingInstance,
64 input: &str,
65 ) -> ScriptingProviderExecuteResult;
66
67 fn execute_script_input_from_file(
68 &self,
69 instance: &ScriptingInstance,
70 file_path: &Path,
71 ) -> ScriptingProviderExecuteResult;
72
73 fn cancel_script_input(&self) {}
75
76 fn release_binary_view(&self, _view: &BinaryView) {}
80
81 fn set_current_binary_view(&self, _view: Option<&BinaryView>) {}
85
86 fn set_current_function(&self, _func: Option<&Function>) {}
90
91 fn set_current_basic_block(&self, _block: Option<&BasicBlock<NativeBlock>>) {}
95
96 fn set_current_selection(&self, _begin: u64, _end: u64) {}
98
99 fn complete_input(&self, _text: &str, _state: u64) -> String {
100 "".to_string()
101 }
102
103 fn can_complete_arguments(&self, _text: &str) -> bool {
107 false
108 }
109
110 fn complete_arguments(&self, _text: &str) -> (String, u64) {
116 ("".to_string(), 0)
117 }
118
119 fn stop(&self) {}
125}
126
127pub trait ScriptingOutputListener: Sync + Send {
128 fn output(&self, text: &str);
129 fn warning(&self, text: &str);
130 fn error(&self, text: &str);
131 fn input_ready_state_changed(&self, state: ScriptingProviderInputReadyState);
132}
133
134#[derive(Clone, Copy, Eq, PartialEq, Hash)]
135#[repr(transparent)]
136pub struct ScriptingProvider {
137 handle: *mut BNScriptingProvider,
138}
139
140impl ScriptingProvider {
141 pub(crate) unsafe fn from_raw(handle: *mut BNScriptingProvider) -> Self {
142 Self { handle }
143 }
144
145 pub fn all() -> Array<Self> {
146 let mut count = 0;
147 let result = unsafe { BNGetScriptingProviderList(&mut count) };
148 assert!(!result.is_null());
149 unsafe { Array::new(result, count, ()) }
150 }
151
152 pub fn by_name(name: &str) -> Option<ScriptingProvider> {
153 let name = name.to_cstr();
154 let result = unsafe { BNGetScriptingProviderByName(name.as_ptr()) };
155 if result.is_null() {
156 None
157 } else {
158 unsafe { Some(Self::from_raw(result)) }
159 }
160 }
161
162 pub fn by_api_name(name: &str) -> Option<ScriptingProvider> {
163 let name = name.to_cstr();
164 let result = unsafe { BNGetScriptingProviderByAPIName(name.as_ptr()) };
165 if result.is_null() {
166 None
167 } else {
168 unsafe { Some(Self::from_raw(result)) }
169 }
170 }
171
172 pub fn name(&self) -> String {
173 let result = unsafe { BNGetScriptingProviderName(self.handle) };
174 assert!(!result.is_null());
175 unsafe { BnString::into_string(result) }
176 }
177
178 pub fn api_name(&self) -> String {
179 let result = unsafe { BNGetScriptingProviderAPIName(self.handle) };
180 assert!(!result.is_null());
181 unsafe { BnString::into_string(result) }
182 }
183
184 pub fn load_module(&self, repository: &str, module: &str, force: bool) -> bool {
185 let repository = repository.to_cstr();
186 let module = module.to_cstr();
187 unsafe {
188 BNLoadScriptingProviderModule(self.handle, repository.as_ptr(), module.as_ptr(), force)
189 }
190 }
191
192 pub fn install_modules(&self, modules: &[&str]) -> bool {
193 let modules_raw = strings_to_string_list(modules);
194 let result =
195 unsafe { BNInstallScriptingProviderModules(self.handle, modules_raw as *const _) };
196 unsafe { BNFreeStringList(modules_raw, modules.len()) };
197 result
198 }
199
200 pub fn create_instance(&self) -> Ref<ScriptingInstance> {
201 let instance = unsafe { BNCreateScriptingProviderInstance(self.handle) };
202 unsafe { ScriptingInstance::ref_from_raw(instance) }
203 }
204}
205
206unsafe impl Sync for ScriptingProvider {}
207unsafe impl Send for ScriptingProvider {}
208
209impl CoreArrayProvider for ScriptingProvider {
210 type Raw = *mut BNScriptingProvider;
211 type Context = ();
212 type Wrapped<'a> = Self;
213}
214
215unsafe impl CoreArrayProviderInner for ScriptingProvider {
216 unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
217 BNFreeScriptingProviderList(raw)
218 }
219
220 unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
221 Self::from_raw(*raw)
222 }
223}
224
225#[repr(transparent)]
226pub struct ScriptingInstance {
227 handle: *mut BNScriptingInstance,
228}
229
230impl ScriptingInstance {
231 pub(crate) unsafe fn ref_from_raw(handle: *mut BNScriptingInstance) -> Ref<Self> {
232 Ref::new(Self { handle })
233 }
234
235 pub fn from_custom<C: CustomScriptingInstance>(
237 provider: &ScriptingProvider,
238 instance: C,
239 ) -> Ref<Self> {
240 let context = CustomScriptingInstanceContext {
242 core_instance: MaybeUninit::uninit(),
243 instance,
244 };
245 let leaked_context = Box::leak(Box::new(context));
246 let mut callbacks = BNScriptingInstanceCallbacks {
247 context: leaked_context as *mut _ as *mut c_void,
248 destroyInstance: Some(cb_destroy_instance::<C>),
249 externalRefTaken: None,
250 externalRefReleased: None,
251 executeScriptInput: Some(cb_execute_script_input::<C>),
252 executeScriptInputFromFilename: Some(cb_execute_script_input_from_filename::<C>),
253 cancelScriptInput: Some(cb_cancel_script_input::<C>),
254 releaseBinaryView: Some(cb_release_binary_view::<C>),
255 setCurrentBinaryView: Some(cb_set_current_binary_view::<C>),
256 setCurrentFunction: Some(cb_set_current_function::<C>),
257 setCurrentBasicBlock: Some(cb_set_current_basic_block::<C>),
258 setCurrentAddress: Some(cb_set_current_address::<C>),
259 setCurrentSelection: Some(cb_set_current_selection::<C>),
260 completeInput: Some(cb_complete_input::<C>),
261 stop: Some(cb_stop::<C>),
262 canCompleteArguments: Some(cb_can_complete_arguments::<C>),
263 completeArguments: Some(cb_complete_arguments::<C>),
264 };
265 let handle = unsafe { BNInitScriptingInstance(provider.handle, &mut callbacks) };
266 assert!(!handle.is_null(), "Handle should always be valid");
267 leaked_context.core_instance = MaybeUninit::new(Self { handle });
268 unsafe { ScriptingInstance::ref_from_raw(handle) }
269 }
270
271 pub fn notify_output(&self, text: &str) {
273 let text = text.to_cstr();
274 unsafe { BNNotifyOutputForScriptingInstance(self.handle, text.as_ptr()) }
275 }
276
277 pub fn notify_warning(&self, text: &str) {
279 let text = text.to_cstr();
280 unsafe { BNNotifyWarningForScriptingInstance(self.handle, text.as_ptr()) }
281 }
282
283 pub fn notify_error(&self, text: &str) {
285 let text = text.to_cstr();
286 unsafe { BNNotifyErrorForScriptingInstance(self.handle, text.as_ptr()) }
287 }
288
289 pub fn notify_input_ready_state(&self, state: ScriptingProviderInputReadyState) {
294 unsafe { BNNotifyInputReadyStateForScriptingInstance(self.handle, state) }
295 }
296
297 pub fn register_output_listener<L: ScriptingOutputListener>(
299 &self,
300 listener: L,
301 ) -> ScriptingInstanceWithListener<'_, L> {
302 let mut listener = Box::pin(listener);
303 let mut callbacks = BNScriptingOutputListener {
304 context: unsafe { listener.as_mut().get_unchecked_mut() } as *mut _ as *mut c_void,
305 output: Some(cb_output::<L>),
306 warning: Some(cb_warning::<L>),
307 error: Some(cb_error::<L>),
308 inputReadyStateChanged: Some(cb_input_ready_state_changed::<L>),
309 };
310 unsafe { BNRegisterScriptingInstanceOutputListener(self.handle, &mut callbacks) }
311
312 ScriptingInstanceWithListener {
313 instance: self,
314 listener,
315 }
316 }
317
318 pub fn delimiters(&self) -> String {
319 let result = unsafe { BNGetScriptingInstanceDelimiters(self.handle) };
320 assert!(!result.is_null());
321 unsafe { BnString::into_string(result) }
322 }
323
324 pub fn set_delimiters(&self, delimiters: &str) {
325 let delimiters = delimiters.to_cstr();
326 unsafe { BNSetScriptingInstanceDelimiters(self.handle, delimiters.as_ptr()) }
327 }
328
329 pub fn input_ready_state(&self) -> ScriptingProviderInputReadyState {
335 unsafe { BNGetScriptingInstanceInputReadyState(self.handle) }
336 }
337
338 pub fn execute_script_input(&self, input: &str) -> ScriptingProviderExecuteResult {
342 let input = input.to_cstr();
343 unsafe { BNExecuteScriptInput(self.handle, input.as_ptr()) }
344 }
345
346 pub fn execute_script_input_from_filename(
347 &self,
348 filename: &str,
349 ) -> ScriptingProviderExecuteResult {
350 let filename = filename.to_cstr();
351 unsafe { BNExecuteScriptInputFromFilename(self.handle, filename.as_ptr()) }
352 }
353
354 pub fn cancel_script_input(&self) {
356 unsafe { BNCancelScriptInput(self.handle) }
357 }
358
359 pub fn release_binary_view(&self, view: &BinaryView) {
360 unsafe { BNScriptingInstanceReleaseBinaryView(self.handle, view.handle) }
361 }
362
363 pub fn set_current_binary_view(&self, view: &BinaryView) {
364 unsafe { BNSetScriptingInstanceCurrentBinaryView(self.handle, view.handle) }
365 }
366
367 pub fn set_current_function(&self, view: &Function) {
368 unsafe { BNSetScriptingInstanceCurrentFunction(self.handle, view.handle) }
369 }
370
371 pub fn set_current_basic_block(&self, view: &BasicBlock<NativeBlock>) {
372 unsafe { BNSetScriptingInstanceCurrentBasicBlock(self.handle, view.handle) }
373 }
374
375 pub fn set_current_address(&self, address: u64) {
376 unsafe { BNSetScriptingInstanceCurrentAddress(self.handle, address) }
377 }
378
379 pub fn set_current_selection(&self, begin: u64, end: u64) {
380 unsafe { BNSetScriptingInstanceCurrentSelection(self.handle, begin, end) }
381 }
382
383 pub fn complete_input(&self, text: &str, state: u64) -> String {
384 let text = text.to_cstr();
385 let result = unsafe { BNScriptingInstanceCompleteInput(self.handle, text.as_ptr(), state) };
386 assert!(!result.is_null());
387 unsafe { BnString::into_string(result) }
388 }
389
390 pub fn can_complete_arguments(&self, text: &str) -> bool {
392 let text = text.to_cstr();
393 unsafe { BNScriptingInstanceCanCompleteArguments(self.handle, text.as_ptr()) }
394 }
395
396 pub fn complete_arguments(&self, text: &str) -> (String, u64) {
398 let text = text.to_cstr();
399 let mut pos = 0;
400 let result =
401 unsafe { BNScriptingInstanceCompleteArguments(self.handle, text.as_ptr(), &mut pos) };
402 if result.is_null() {
403 ("".to_string(), 0)
404 } else {
405 let result_str = unsafe { BnString::into_string(result) };
406 (result_str, pos)
407 }
408 }
409
410 pub fn stop(&self) {
411 unsafe { BNStopScriptingInstance(self.handle) }
412 }
413}
414
415unsafe impl Sync for ScriptingInstance {}
416unsafe impl Send for ScriptingInstance {}
417
418impl ToOwned for ScriptingInstance {
419 type Owned = Ref<Self>;
420
421 fn to_owned(&self) -> Self::Owned {
422 unsafe { Self::inc_ref(self) }
423 }
424}
425
426unsafe impl RefCountable for ScriptingInstance {
427 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
428 Ref::new(Self {
429 handle: BNNewScriptingInstanceReference(handle.handle),
430 })
431 }
432
433 unsafe fn dec_ref(handle: &Self) {
434 BNFreeScriptingInstance(handle.handle);
435 }
436}
437
438struct CustomScriptingInstanceContext<C: CustomScriptingInstance> {
441 core_instance: MaybeUninit<ScriptingInstance>,
444 instance: C,
445}
446
447pub struct ScriptingInstanceWithListener<'a, L: ScriptingOutputListener> {
448 instance: &'a ScriptingInstance,
449 listener: Pin<Box<L>>,
450}
451
452impl<L: ScriptingOutputListener> AsRef<L> for ScriptingInstanceWithListener<'_, L> {
453 fn as_ref(&self) -> &L {
454 &self.listener
455 }
456}
457
458impl<L: ScriptingOutputListener> core::ops::Deref for ScriptingInstanceWithListener<'_, L> {
459 type Target = L;
460 fn deref(&self) -> &Self::Target {
461 &self.listener
462 }
463}
464
465impl<L: ScriptingOutputListener> ScriptingInstanceWithListener<'_, L> {
466 pub fn unregister(mut self) {
467 let mut callbacks = BNScriptingOutputListener {
468 context: unsafe { self.listener.as_mut().get_unchecked_mut() } as *mut _ as *mut c_void,
469 output: Some(cb_output::<L>),
470 warning: Some(cb_warning::<L>),
471 error: Some(cb_error::<L>),
472 inputReadyStateChanged: Some(cb_input_ready_state_changed::<L>),
473 };
474 unsafe {
475 BNUnregisterScriptingInstanceOutputListener(self.instance.handle, &mut callbacks)
476 };
477 }
478}
479
480unsafe extern "C" fn cb_create_instance<S: CustomScriptingProvider>(
481 ctxt: *mut c_void,
482) -> *mut BNScriptingInstance {
483 let ctxt = &mut *(ctxt as *mut S);
484 let instance = ctxt.create_instance();
485 let provider = ScriptingProvider::by_name(S::NAME).expect("Provider should always exist");
487 let core_instance = ScriptingInstance::from_custom(&provider, instance);
488 Ref::into_raw(core_instance).handle
489}
490
491unsafe extern "C" fn cb_load_module<S: CustomScriptingProvider>(
492 ctxt: *mut c_void,
493 repo_path: *const c_char,
494 plugin_path: *const c_char,
495 force: bool,
496) -> bool {
497 let ctxt = &mut *(ctxt as *mut S);
498 let repo_path = CStr::from_ptr(repo_path);
499 let plugin_path = CStr::from_ptr(plugin_path);
500 ctxt.load_module(
501 &repo_path.to_string_lossy(),
502 &plugin_path.to_string_lossy(),
503 force,
504 )
505}
506
507unsafe extern "C" fn cb_install_modules<S: CustomScriptingProvider>(
508 ctxt: *mut c_void,
509 modules: *const c_char,
510) -> bool {
511 let ctxt = &mut *(ctxt as *mut S);
512 let modules = CStr::from_ptr(modules);
513 ctxt.install_modules(&modules.to_string_lossy())
514}
515
516unsafe extern "C" fn cb_destroy_instance<S: CustomScriptingInstance>(ctxt: *mut c_void) {
517 let _ = Box::from_raw(ctxt as *mut CustomScriptingInstanceContext<S>);
518}
519
520unsafe extern "C" fn cb_execute_script_input<S: CustomScriptingInstance>(
521 ctxt: *mut c_void,
522 input: *const c_char,
523) -> BNScriptingProviderExecuteResult {
524 let input = CStr::from_ptr(input);
525 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
526 let result = ctxt.instance.execute_script_input(
527 ctxt.core_instance.assume_init_ref(),
528 &input.to_string_lossy(),
529 );
530 result
531}
532
533unsafe extern "C" fn cb_execute_script_input_from_filename<S: CustomScriptingInstance>(
534 ctxt: *mut c_void,
535 input: *const c_char,
536) -> BNScriptingProviderExecuteResult {
537 let input = CStr::from_ptr(input);
538 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
539 ctxt.instance.execute_script_input(
540 ctxt.core_instance.assume_init_ref(),
541 &input.to_string_lossy(),
542 )
543}
544
545unsafe extern "C" fn cb_cancel_script_input<S: CustomScriptingInstance>(ctxt: *mut c_void) {
546 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
547 ctxt.instance.cancel_script_input()
548}
549
550unsafe extern "C" fn cb_release_binary_view<S: CustomScriptingInstance>(
551 ctxt: *mut c_void,
552 view: *mut BNBinaryView,
553) {
554 let view = BinaryView::from_raw(view);
555 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
556 ctxt.instance.release_binary_view(&view)
557}
558
559unsafe extern "C" fn cb_set_current_binary_view<S: CustomScriptingInstance>(
560 ctxt: *mut c_void,
561 view: *mut BNBinaryView,
562) {
563 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
564 match view.is_null() {
565 true => ctxt.instance.set_current_binary_view(None),
566 false => {
567 let view = BinaryView::from_raw(view);
568 ctxt.instance.set_current_binary_view(Some(&view))
569 }
570 }
571}
572
573unsafe extern "C" fn cb_set_current_function<S: CustomScriptingInstance>(
574 ctxt: *mut c_void,
575 func: *mut BNFunction,
576) {
577 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
578 match func.is_null() {
579 true => ctxt.instance.set_current_function(None),
580 false => {
581 let func = Function::from_raw(func);
582 ctxt.instance.set_current_function(Some(&func))
583 }
584 }
585}
586
587unsafe extern "C" fn cb_set_current_basic_block<S: CustomScriptingInstance>(
588 ctxt: *mut c_void,
589 block: *mut BNBasicBlock,
590) {
591 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
592 match block.is_null() {
593 true => ctxt.instance.set_current_basic_block(None),
594 false => {
595 let block = BasicBlock::from_raw(block, NativeBlock::new());
596 ctxt.instance.set_current_basic_block(Some(&block))
597 }
598 }
599}
600
601unsafe extern "C" fn cb_set_current_address<S: CustomScriptingInstance>(
602 ctxt: *mut c_void,
603 addr: u64,
604) {
605 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
606 ctxt.instance.set_current_selection(addr, addr)
607}
608
609unsafe extern "C" fn cb_set_current_selection<S: CustomScriptingInstance>(
610 ctxt: *mut c_void,
611 begin: u64,
612 end: u64,
613) {
614 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
615 ctxt.instance.set_current_selection(begin, end)
616}
617
618unsafe extern "C" fn cb_complete_input<S: CustomScriptingInstance>(
619 ctxt: *mut c_void,
620 text: *const c_char,
621 state: u64,
622) -> *mut c_char {
623 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
624 let text = CStr::from_ptr(text);
625 let result = ctxt.instance.complete_input(&text.to_string_lossy(), state);
626 BnString::into_raw(BnString::new(result))
627}
628
629unsafe extern "C" fn cb_stop<S: CustomScriptingInstance>(ctxt: *mut c_void) {
630 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
631 ctxt.instance.stop()
632}
633
634unsafe extern "C" fn cb_output<S: ScriptingOutputListener>(ctxt: *mut c_void, text: *const c_char) {
635 let ctxt = &mut *(ctxt as *mut S);
636 let text = CStr::from_ptr(text);
637 ctxt.output(&text.to_string_lossy())
638}
639
640unsafe extern "C" fn cb_warning<S: ScriptingOutputListener>(
641 ctxt: *mut c_void,
642 text: *const c_char,
643) {
644 let ctxt = &mut *(ctxt as *mut S);
645 let text = CStr::from_ptr(text);
646 ctxt.warning(&text.to_string_lossy())
647}
648
649unsafe extern "C" fn cb_error<S: ScriptingOutputListener>(ctxt: *mut c_void, text: *const c_char) {
650 let ctxt = &mut *(ctxt as *mut S);
651 let text = CStr::from_ptr(text);
652 ctxt.error(&text.to_string_lossy())
653}
654
655unsafe extern "C" fn cb_input_ready_state_changed<S: ScriptingOutputListener>(
656 ctxt: *mut c_void,
657 state: BNScriptingProviderInputReadyState,
658) {
659 let ctxt = &mut *(ctxt as *mut S);
660 ctxt.input_ready_state_changed(state)
661}
662
663unsafe extern "C" fn cb_can_complete_arguments<S: CustomScriptingInstance>(
664 ctxt: *mut c_void,
665 text: *const c_char,
666) -> bool {
667 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
668 let text = CStr::from_ptr(text);
669 ctxt.instance
670 .can_complete_arguments(&text.to_string_lossy())
671}
672
673unsafe extern "C" fn cb_complete_arguments<S: CustomScriptingInstance>(
674 ctxt: *mut c_void,
675 text: *const c_char,
676 arg_pos: *mut u64,
677) -> *mut c_char {
678 let ctxt = &mut *(ctxt as *mut CustomScriptingInstanceContext<S>);
679 let text = CStr::from_ptr(text);
680 let (result, pos) = ctxt.instance.complete_arguments(&text.to_string_lossy());
681 *arg_pos = pos;
682 BnString::into_raw(BnString::new(result))
683}