binaryninja/
scripting_provider.rs

1//! Interface for registering and using scripting providers.
2
3use 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
19/// Register a new scripting provider.
20pub 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    /// Called when the user requests that the current executing input be canceled.
74    fn cancel_script_input(&self) {}
75
76    /// Called when the binary view is no longer available.
77    ///
78    /// This function should release any resources associated with the binary view.
79    fn release_binary_view(&self, _view: &BinaryView) {}
80
81    /// Called when the current binary view is changed.
82    ///
83    /// The `view` will be `None` if there is no current binary view.
84    fn set_current_binary_view(&self, _view: Option<&BinaryView>) {}
85
86    /// Called when the current function is changed.
87    ///
88    /// The `func` will be `None` if there is no current function.
89    fn set_current_function(&self, _func: Option<&Function>) {}
90
91    /// Called when the current basic block is changed.
92    ///
93    /// The `block` will be `None` if there is no current basic block.
94    fn set_current_basic_block(&self, _block: Option<&BasicBlock<NativeBlock>>) {}
95
96    /// Called when the current selected address range is changed.
97    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    /// Called to check whether the user can complete arguments from the given string.
104    ///
105    /// Make sure to override this _and_ [`CustomScriptingInstance::complete_arguments`] to enable argument completion.
106    fn can_complete_arguments(&self, _text: &str) -> bool {
107        false
108    }
109
110    /// Called when the user requests completion of arguments.
111    ///
112    /// Make sure to override this _and_ [`CustomScriptingInstance::can_complete_arguments`] to enable argument completion.
113    ///
114    /// Returns the completion string with the character position in the string to place the popup at.
115    fn complete_arguments(&self, _text: &str) -> (String, u64) {
116        ("".to_string(), 0)
117    }
118
119    // TODO: What is the point of this? There is not difference I can ascertain from just impl Drop on the instance type.
120    /// Perform cleanup of resources, this is called before the instance destructor is called.
121    ///
122    /// NOTE: Due to usage being potentially identical to when the instance is dropped, this **may be
123    /// removed in the future**.
124    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    /// Create a core instance of the [`CustomScriptingInstance`].
236    pub fn from_custom<C: CustomScriptingInstance>(
237        provider: &ScriptingProvider,
238        instance: C,
239    ) -> Ref<Self> {
240        // Leaked to be freed by `cb_destroy_instance`.
241        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    /// Notifies the active scripting instance listeners of output, typically called in [`ScriptingInstance::execute_script_input`].
272    pub fn notify_output(&self, text: &str) {
273        let text = text.to_cstr();
274        unsafe { BNNotifyOutputForScriptingInstance(self.handle, text.as_ptr()) }
275    }
276
277    /// Notifies the active scripting instance listeners of a warning, typically called in [`ScriptingInstance::execute_script_input`].
278    pub fn notify_warning(&self, text: &str) {
279        let text = text.to_cstr();
280        unsafe { BNNotifyWarningForScriptingInstance(self.handle, text.as_ptr()) }
281    }
282
283    /// Notifies the active scripting instance listeners of an error, typically called in [`ScriptingInstance::execute_script_input`].
284    pub fn notify_error(&self, text: &str) {
285        let text = text.to_cstr();
286        unsafe { BNNotifyErrorForScriptingInstance(self.handle, text.as_ptr()) }
287    }
288
289    /// Notify the scripting instance that the input state has changed.
290    ///
291    /// When constructing an instance, the default state is [`ScriptingProviderInputReadyState::NotReadyForInput`]
292    /// which prevents the scripting instance from accepting input until explicitly notified otherwise.
293    pub fn notify_input_ready_state(&self, state: ScriptingProviderInputReadyState) {
294        unsafe { BNNotifyInputReadyStateForScriptingInstance(self.handle, state) }
295    }
296
297    /// Listen for output from this scripting instance.
298    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    /// The current input ready state of the scripting instance.
330    ///
331    /// If this is set to [`ScriptingProviderInputReadyState::NotReadyForInput`], the scripting instance will not accept input.
332    ///
333    /// To interact with the scripting instance, ensure it is in the correct state using [`ScriptingInstance::notify_input_ready_state`].
334    pub fn input_ready_state(&self) -> ScriptingProviderInputReadyState {
335        unsafe { BNGetScriptingInstanceInputReadyState(self.handle) }
336    }
337
338    /// Execute the input within this instance.
339    ///
340    /// Before calling this, make sure the scripting instance is ready for input with [`ScriptingInstance::input_ready_state`].
341    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    /// Request that the current executing input be canceled.
355    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    /// Checks to see if there are any argument completions available for the given text.
391    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    /// Returns the completion string and the position of the argument being completed (in the utf8 string).
397    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
438/// Wrapper around `C` when being passed to the custom instance constructor so that we have the core
439/// instance available to [`CustomScriptingInstance`] functions.
440struct CustomScriptingInstanceContext<C: CustomScriptingInstance> {
441    // This is not ref-counted because we do not want to impact the lifetime of the core view, the lifetime
442    // of which is already bound to the lifetime of the custom view (to be freed when the custom view is freed).
443    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    // TODO: Bit of a hack, but we need the core provider handle here.
486    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}