Skip to main content

maolan_engine/plugins/vst3/
interfaces.rs

1// VST3 COM interface wrappers using vst3 crate
2//
3// This module provides safe Rust wrappers around VST3 COM interfaces
4// using the vst3 crate's trait-based API.
5
6use std::ffi::c_void;
7use std::path::Path;
8use std::sync::atomic::{AtomicU32, Ordering};
9use std::sync::{Mutex, OnceLock};
10use std::time::{Duration, Instant};
11use vst3::Steinberg::Vst::ProcessModes_::kRealtime;
12use vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32;
13use vst3::Steinberg::Vst::*;
14use vst3::Steinberg::*;
15use vst3::{ComPtr, Interface};
16
17static HOST_RUN_LOOP_STATE: OnceLock<Mutex<HostRunLoopState>> = OnceLock::new();
18
19struct HostRunLoopState {
20    event_handlers: Vec<RunLoopEventHandler>,
21    timer_handlers: Vec<RunLoopTimerHandler>,
22}
23
24struct RunLoopEventHandler {
25    handler: usize,
26    fd: i32,
27}
28
29struct RunLoopTimerHandler {
30    handler: usize,
31    interval: Duration,
32    next_tick: Instant,
33}
34
35fn run_loop_state() -> &'static Mutex<HostRunLoopState> {
36    HOST_RUN_LOOP_STATE.get_or_init(|| {
37        Mutex::new(HostRunLoopState {
38            event_handlers: Vec::new(),
39            timer_handlers: Vec::new(),
40        })
41    })
42}
43
44pub fn pump_host_run_loop() {
45    // Linux VST3 UIs can rely on host-owned timers/fd callbacks.
46    // Drive those callbacks from the GUI-side UI loop.
47    let (event_calls, timer_calls): (Vec<(usize, i32)>, Vec<usize>) = {
48        let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
49        let now = Instant::now();
50        let event_calls = state
51            .event_handlers
52            .iter()
53            .map(|h| (h.handler, h.fd))
54            .collect::<Vec<_>>();
55        let mut timer_calls = Vec::new();
56        for timer in &mut state.timer_handlers {
57            if now >= timer.next_tick {
58                timer_calls.push(timer.handler);
59                timer.next_tick = now + timer.interval;
60            }
61        }
62        (event_calls, timer_calls)
63    };
64
65    for (handler, fd) in event_calls {
66        let handler_ptr = handler as *mut Linux::IEventHandler;
67        if handler_ptr.is_null() {
68            continue;
69        }
70        unsafe {
71            ((*(*handler_ptr).vtbl).onFDIsSet)(handler_ptr, fd);
72        }
73    }
74    for handler in timer_calls {
75        let handler_ptr = handler as *mut Linux::ITimerHandler;
76        if handler_ptr.is_null() {
77            continue;
78        }
79        unsafe {
80            ((*(*handler_ptr).vtbl).onTimer)(handler_ptr);
81        }
82    }
83}
84
85/// Safe wrapper around VST3 plugin factory
86pub struct PluginFactory {
87    // Keep COM objects before the module so they are released before dlclose.
88    factory: ComPtr<IPluginFactory>,
89    module: libloading::Library,
90    module_inited: bool,
91}
92
93impl std::fmt::Debug for PluginFactory {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("PluginFactory")
96            .field("factory", &"<COM ptr>")
97            .field("module", &"<library>")
98            .finish()
99    }
100}
101
102impl PluginFactory {
103    /// Load a VST3 plugin bundle and create a factory
104    pub fn from_module(bundle_path: &Path) -> Result<Self, String> {
105        // Determine the actual module path based on platform
106        let module_path = get_module_path(bundle_path)?;
107
108        // Load the shared library
109        let library = unsafe {
110            libloading::Library::new(&module_path)
111                .map_err(|e| format!("Failed to load VST3 module {:?}: {}", module_path, e))?
112        };
113
114        // Many Windows plugins (including iPlug2-based VST3) rely on InitDll to
115        // initialize module globals used for resource lookup and UI setup.
116        let module_inited = unsafe {
117            match library.get::<unsafe extern "system" fn() -> bool>(b"InitDll") {
118                Ok(init_dll) => init_dll(),
119                Err(_) => false,
120            }
121        };
122
123        // Get the factory function
124        // VST3 plugins export: extern "system" fn GetPluginFactory() -> *mut c_void
125        let get_factory: libloading::Symbol<unsafe extern "system" fn() -> *mut c_void> = unsafe {
126            library
127                .get(b"GetPluginFactory")
128                .map_err(|e| format!("Failed to find GetPluginFactory: {}", e))?
129        };
130
131        // Call it to get the factory
132        let factory_ptr = unsafe { get_factory() };
133        if factory_ptr.is_null() {
134            return Err("GetPluginFactory returned null".to_string());
135        }
136
137        // Wrap in ComPtr - the vst3 crate provides this smart pointer
138        let factory = unsafe { ComPtr::from_raw(factory_ptr as *mut IPluginFactory) }
139            .ok_or("Failed to create ComPtr for IPluginFactory")?;
140
141        Ok(Self {
142            factory,
143            module: library,
144            module_inited,
145        })
146    }
147
148    /// Get information about a plugin class using the trait
149    pub fn get_class_info(&self, index: i32) -> Option<ClassInfo> {
150        use vst3::Steinberg::IPluginFactoryTrait;
151
152        let mut info = PClassInfo {
153            cid: [0; 16],
154            cardinality: 0,
155            category: [0; 32],
156            name: [0; 64],
157        };
158
159        let result = unsafe { self.factory.getClassInfo(index, &mut info) };
160
161        if result == kResultOk {
162            Some(ClassInfo {
163                name: extract_cstring(&info.name),
164                category: extract_cstring(&info.category),
165                cid: info.cid,
166            })
167        } else {
168            None
169        }
170    }
171
172    /// Count the number of classes using the trait
173    pub fn count_classes(&self) -> i32 {
174        use vst3::Steinberg::IPluginFactoryTrait;
175        unsafe { self.factory.countClasses() }
176    }
177
178    /// Create an instance of a plugin using the trait
179    pub fn create_instance(&self, class_id: &[i8; 16]) -> Result<PluginInstance, String> {
180        use vst3::Steinberg::IPluginFactoryTrait;
181
182        let mut instance_ptr: *mut c_void = std::ptr::null_mut();
183
184        let result = unsafe {
185            self.factory.createInstance(
186                class_id.as_ptr(),
187                IComponent::IID.as_ptr() as *const i8,
188                &mut instance_ptr,
189            )
190        };
191
192        if result != kResultOk || instance_ptr.is_null() {
193            return Err(format!(
194                "Failed to create plugin instance (result: {})",
195                result
196            ));
197        }
198
199        let component = unsafe { ComPtr::from_raw(instance_ptr as *mut IComponent) }
200            .ok_or("Failed to create ComPtr for IComponent")?;
201
202        Ok(PluginInstance::new(component))
203    }
204
205    pub fn create_edit_controller(
206        &self,
207        class_id: &[i8; 16],
208    ) -> Result<ComPtr<IEditController>, String> {
209        use vst3::Steinberg::IPluginFactoryTrait;
210
211        let mut instance_ptr: *mut c_void = std::ptr::null_mut();
212
213        let result = unsafe {
214            self.factory.createInstance(
215                class_id.as_ptr(),
216                IEditController::IID.as_ptr() as *const i8,
217                &mut instance_ptr,
218            )
219        };
220
221        if result != kResultOk || instance_ptr.is_null() {
222            return Err(format!(
223                "Failed to create edit controller instance (result: {})",
224                result
225            ));
226        }
227
228        unsafe { ComPtr::from_raw(instance_ptr as *mut IEditController) }
229            .ok_or("Failed to create ComPtr for IEditController".to_string())
230    }
231}
232
233impl Drop for PluginFactory {
234    fn drop(&mut self) {
235        if !self.module_inited {
236            return;
237        }
238
239        unsafe {
240            if let Ok(exit_dll) = self
241                .module
242                .get::<unsafe extern "system" fn() -> bool>(b"ExitDll")
243            {
244                let _ = exit_dll();
245            }
246        }
247    }
248}
249
250/// Information about a plugin class
251pub struct ClassInfo {
252    pub name: String,
253    pub category: String,
254    pub cid: [i8; 16],
255}
256
257/// Safe wrapper around a VST3 plugin instance
258pub struct PluginInstance {
259    pub component: ComPtr<IComponent>,
260    pub audio_processor: Option<ComPtr<IAudioProcessor>>,
261    pub edit_controller: Option<ComPtr<IEditController>>,
262    host_context: Box<HostApplicationContext>,
263    component_handler: Box<HostComponentHandlerContext>,
264}
265
266impl std::fmt::Debug for PluginInstance {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268        f.debug_struct("PluginInstance")
269            .field("component", &"<COM ptr>")
270            .field("audio_processor", &self.audio_processor.is_some())
271            .field("edit_controller", &self.edit_controller.is_some())
272            .finish()
273    }
274}
275
276impl PluginInstance {
277    fn new(component: ComPtr<IComponent>) -> Self {
278        Self {
279            component,
280            audio_processor: None,
281            edit_controller: None,
282            host_context: Box::new(HostApplicationContext::new()),
283            component_handler: Box::new(HostComponentHandlerContext::new()),
284        }
285    }
286
287    pub fn audio_bus_counts(&self) -> (usize, usize) {
288        use vst3::Steinberg::Vst::{BusDirections_, IComponentTrait, MediaTypes_};
289
290        let input_count = unsafe {
291            self.component
292                .getBusCount(MediaTypes_::kAudio as i32, BusDirections_::kInput as i32)
293        }
294        .max(0) as usize;
295        let output_count = unsafe {
296            self.component
297                .getBusCount(MediaTypes_::kAudio as i32, BusDirections_::kOutput as i32)
298        }
299        .max(0) as usize;
300        (input_count, output_count)
301    }
302
303    pub fn event_bus_counts(&self) -> (usize, usize) {
304        use vst3::Steinberg::Vst::{BusDirections_, IComponentTrait, MediaTypes_};
305
306        let input_count = unsafe {
307            self.component
308                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kInput as i32)
309        }
310        .max(0) as usize;
311        let output_count = unsafe {
312            self.component
313                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kOutput as i32)
314        }
315        .max(0) as usize;
316        (input_count, output_count)
317    }
318
319    pub fn main_audio_channel_counts(&self) -> (usize, usize) {
320        use vst3::Steinberg::Vst::{BusDirections_, BusTypes_, IComponentTrait, MediaTypes_};
321
322        let main_channels_for_direction = |direction: i32| -> usize {
323            let bus_count = unsafe {
324                self.component
325                    .getBusCount(MediaTypes_::kAudio as i32, direction)
326            }
327            .max(0) as usize;
328            if bus_count == 0 {
329                return 0;
330            }
331
332            let mut first_nonzero = 0usize;
333            for idx in 0..bus_count {
334                let mut info: vst3::Steinberg::Vst::BusInfo = unsafe { std::mem::zeroed() };
335                let result = unsafe {
336                    self.component.getBusInfo(
337                        MediaTypes_::kAudio as i32,
338                        direction,
339                        idx as i32,
340                        &mut info,
341                    )
342                };
343                if result != kResultOk {
344                    continue;
345                }
346                let channels = info.channelCount.max(0) as usize;
347                if channels > 0 && first_nonzero == 0 {
348                    first_nonzero = channels;
349                }
350                if info.busType == BusTypes_::kMain as i32 {
351                    return channels.max(1);
352                }
353            }
354
355            first_nonzero.max(1)
356        };
357
358        (
359            main_channels_for_direction(BusDirections_::kInput as i32),
360            main_channels_for_direction(BusDirections_::kOutput as i32),
361        )
362    }
363
364    /// Initialize the component
365    pub fn initialize(&mut self, factory: &PluginFactory) -> Result<(), String> {
366        use vst3::Steinberg::IPluginBaseTrait;
367        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
368
369        // Pass a stable host application context for plugins that require IHostApplication.
370        let context = &mut self.host_context.host as *mut IHostApplication as *mut FUnknown;
371        let result = unsafe { self.component.initialize(context) };
372
373        if result != kResultOk {
374            return Err(format!(
375                "Failed to initialize component (result: {})",
376                result
377            ));
378        }
379
380        // Query for IAudioProcessor
381        let mut processor_ptr: *mut c_void = std::ptr::null_mut();
382        let result = unsafe {
383            // Access the vtable through the raw pointer
384            let component_raw = self.component.as_ptr();
385            let vtbl = (*component_raw).vtbl;
386            let query_interface = (*vtbl).base.base.queryInterface;
387            // Cast IID from [u8; 16] to [i8; 16]
388            let iid = std::mem::transmute::<&[u8; 16], &[i8; 16]>(&IAudioProcessor::IID);
389            query_interface(component_raw as *mut _, iid, &mut processor_ptr)
390        };
391
392        if result == kResultOk && !processor_ptr.is_null() {
393            self.audio_processor =
394                unsafe { ComPtr::from_raw(processor_ptr as *mut IAudioProcessor) };
395        }
396
397        // Query for IEditController directly from component first.
398        let mut controller_ptr: *mut c_void = std::ptr::null_mut();
399        let query_result = unsafe {
400            let component_raw = self.component.as_ptr();
401            let vtbl = (*component_raw).vtbl;
402            let query_interface = (*vtbl).base.base.queryInterface;
403            let iid = std::mem::transmute::<&[u8; 16], &[i8; 16]>(&IEditController::IID);
404            query_interface(component_raw as *mut _, iid, &mut controller_ptr)
405        };
406        if query_result == kResultOk && !controller_ptr.is_null() {
407            self.edit_controller =
408                unsafe { ComPtr::from_raw(controller_ptr as *mut IEditController) };
409        }
410
411        // If not available directly, instantiate the dedicated controller class.
412        if self.edit_controller.is_none() {
413            let mut controller_cid: TUID = [0; 16];
414            let cid_result = unsafe { self.component.getControllerClassId(&mut controller_cid) };
415            if cid_result == kResultOk {
416                let mut maybe_controller = factory.create_edit_controller(&controller_cid).ok();
417                if let Some(controller) = maybe_controller.as_mut() {
418                    let controller_context =
419                        &mut self.host_context.host as *mut IHostApplication as *mut FUnknown;
420                    let init_result = unsafe { controller.initialize(controller_context) };
421                    if init_result != kResultOk {
422                        maybe_controller = None;
423                    }
424                }
425                self.edit_controller = maybe_controller;
426            }
427        }
428
429        if let Some(controller) = self.edit_controller.as_ref() {
430            let handler =
431                &mut self.component_handler.handler as *mut IComponentHandler as *mut FUnknown;
432            let _ = unsafe { controller.setComponentHandler(handler as *mut IComponentHandler) };
433        }
434
435        Ok(())
436    }
437
438    /// Set the component active/inactive
439    pub fn set_active(&mut self, active: bool) -> Result<(), String> {
440        let result = unsafe { self.component.setActive(if active { 1 } else { 0 }) };
441
442        if result != kResultOk {
443            return Err(format!("Failed to set active state (result: {})", result));
444        }
445
446        Ok(())
447    }
448
449    /// Setup processing parameters
450    pub fn setup_processing(
451        &mut self,
452        sample_rate: f64,
453        max_samples: i32,
454        input_channels: i32,
455        output_channels: i32,
456    ) -> Result<(), String> {
457        use vst3::Steinberg::Vst::{
458            BusDirections_, BusInfo, BusInfo_::BusFlags_ as BusFlags, BusTypes_,
459            IAudioProcessorTrait, IComponentTrait, MediaTypes_, SpeakerArr,
460        };
461
462        let processor = self
463            .audio_processor
464            .as_ref()
465            .ok_or("No audio processor available")?;
466
467        let sample_size_result = unsafe { processor.canProcessSampleSize(kSample32 as i32) };
468        if sample_size_result != kResultOk {
469            return Err(format!(
470                "Plugin does not support 32-bit sample size (result: {})",
471                sample_size_result
472            ));
473        }
474
475        let configure_audio_buses = |direction: i32, requested_channels: i32| {
476            let bus_count = unsafe {
477                self.component
478                    .getBusCount(MediaTypes_::kAudio as i32, direction)
479            }
480            .max(0) as usize;
481            if bus_count == 0 {
482                return Vec::new();
483            }
484
485            let mut infos: Vec<BusInfo> = Vec::with_capacity(bus_count);
486            for idx in 0..bus_count {
487                let mut info: BusInfo = unsafe { std::mem::zeroed() };
488                let r = unsafe {
489                    self.component.getBusInfo(
490                        MediaTypes_::kAudio as i32,
491                        direction,
492                        idx as i32,
493                        &mut info,
494                    )
495                };
496                if r != kResultOk {
497                    info.channelCount = if idx == 0 { 2 } else { 0 };
498                    info.busType = if idx == 0 {
499                        BusTypes_::kMain as i32
500                    } else {
501                        BusTypes_::kAux as i32
502                    };
503                    info.flags = if idx == 0 {
504                        BusFlags::kDefaultActive as u32
505                    } else {
506                        0
507                    };
508                }
509                infos.push(info);
510            }
511
512            let mut remaining = requested_channels.max(0);
513            let mut active = vec![false; bus_count];
514            let mut arrangements = vec![SpeakerArr::kEmpty; bus_count];
515
516            let mut ordered: Vec<usize> = (0..bus_count)
517                .filter(|&idx| infos[idx].busType == BusTypes_::kMain as i32)
518                .collect();
519            ordered.extend(
520                (0..bus_count).filter(|&idx| infos[idx].busType != BusTypes_::kMain as i32),
521            );
522
523            for idx in ordered {
524                if remaining <= 0 {
525                    break;
526                }
527                let bus_channels = infos[idx].channelCount.max(1);
528                let allocate = remaining.min(bus_channels);
529                if allocate > 0 {
530                    active[idx] = true;
531                    arrangements[idx] = if allocate > 1 {
532                        SpeakerArr::kStereo
533                    } else {
534                        SpeakerArr::kMono
535                    };
536                    remaining -= allocate;
537                }
538            }
539
540            if requested_channels > 0 && !active.iter().any(|v| *v) {
541                active[0] = true;
542                arrangements[0] = if requested_channels > 1 {
543                    SpeakerArr::kStereo
544                } else {
545                    SpeakerArr::kMono
546                };
547            }
548
549            for idx in 0..bus_count {
550                let _ = unsafe {
551                    self.component.activateBus(
552                        MediaTypes_::kAudio as i32,
553                        direction,
554                        idx as i32,
555                        if active[idx] { 1 } else { 0 },
556                    )
557                };
558            }
559
560            arrangements
561        };
562
563        let mut input_arrangements =
564            configure_audio_buses(BusDirections_::kInput as i32, input_channels);
565        let mut output_arrangements =
566            configure_audio_buses(BusDirections_::kOutput as i32, output_channels);
567        if !input_arrangements.is_empty() || !output_arrangements.is_empty() {
568            let _ = unsafe {
569                processor.setBusArrangements(
570                    if input_arrangements.is_empty() {
571                        std::ptr::null_mut()
572                    } else {
573                        input_arrangements.as_mut_ptr()
574                    },
575                    input_arrangements.len() as i32,
576                    if output_arrangements.is_empty() {
577                        std::ptr::null_mut()
578                    } else {
579                        output_arrangements.as_mut_ptr()
580                    },
581                    output_arrangements.len() as i32,
582                )
583            };
584        }
585
586        let event_in_buses = unsafe {
587            self.component
588                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kInput as i32)
589        }
590        .max(0) as usize;
591        for idx in 0..event_in_buses {
592            let _ = unsafe {
593                self.component.activateBus(
594                    MediaTypes_::kEvent as i32,
595                    BusDirections_::kInput as i32,
596                    idx as i32,
597                    1,
598                )
599            };
600        }
601        let event_out_buses = unsafe {
602            self.component
603                .getBusCount(MediaTypes_::kEvent as i32, BusDirections_::kOutput as i32)
604        }
605        .max(0) as usize;
606        for idx in 0..event_out_buses {
607            let _ = unsafe {
608                self.component.activateBus(
609                    MediaTypes_::kEvent as i32,
610                    BusDirections_::kOutput as i32,
611                    idx as i32,
612                    1,
613                )
614            };
615        }
616
617        let mut setup = ProcessSetup {
618            processMode: kRealtime as i32,
619            symbolicSampleSize: kSample32 as i32,
620            maxSamplesPerBlock: max_samples,
621            sampleRate: sample_rate,
622        };
623
624        let result = unsafe { processor.setupProcessing(&mut setup) };
625
626        if result != kResultOk {
627            return Err(format!("Failed to setup processing (result: {})", result));
628        }
629
630        Ok(())
631    }
632
633    pub fn start_processing(&mut self) -> Result<(), String> {
634        use vst3::Steinberg::Vst::IAudioProcessorTrait;
635
636        let Some(processor) = &self.audio_processor else {
637            return Ok(());
638        };
639        let result = unsafe { processor.setProcessing(1) };
640        if result != kResultOk {
641            return Err(format!(
642                "Failed to enable processing state (result: {})",
643                result
644            ));
645        }
646        Ok(())
647    }
648
649    pub fn stop_processing(&mut self) {
650        use vst3::Steinberg::Vst::IAudioProcessorTrait;
651
652        if let Some(processor) = &self.audio_processor {
653            unsafe {
654                let _ = processor.setProcessing(0);
655            }
656        }
657    }
658
659    /// Terminate the component
660    pub fn terminate(&mut self) -> Result<(), String> {
661        use vst3::Steinberg::IPluginBaseTrait;
662
663        let result = unsafe { self.component.terminate() };
664
665        if result != kResultOk {
666            return Err(format!(
667                "Failed to terminate component (result: {})",
668                result
669            ));
670        }
671
672        Ok(())
673    }
674}
675
676#[repr(C)]
677struct HostApplicationContext {
678    host: IHostApplication,
679    run_loop: HostRunLoopContext,
680    ref_count: AtomicU32,
681}
682
683impl HostApplicationContext {
684    fn new() -> Self {
685        Self {
686            host: IHostApplication {
687                vtbl: &HOST_APPLICATION_VTBL,
688            },
689            run_loop: HostRunLoopContext::new(),
690            ref_count: AtomicU32::new(1),
691        }
692    }
693}
694
695#[repr(C)]
696struct HostRunLoopContext {
697    iface: Linux::IRunLoop,
698    ref_count: AtomicU32,
699}
700
701impl HostRunLoopContext {
702    fn new() -> Self {
703        Self {
704            iface: Linux::IRunLoop {
705                vtbl: &HOST_RUN_LOOP_VTBL,
706            },
707            ref_count: AtomicU32::new(1),
708        }
709    }
710}
711
712#[repr(C)]
713struct HostComponentHandlerContext {
714    handler: IComponentHandler,
715    ref_count: AtomicU32,
716}
717
718impl HostComponentHandlerContext {
719    fn new() -> Self {
720        Self {
721            handler: IComponentHandler {
722                vtbl: &HOST_COMPONENT_HANDLER_VTBL,
723            },
724            ref_count: AtomicU32::new(1),
725        }
726    }
727}
728
729unsafe extern "system" fn component_handler_query_interface(
730    this: *mut FUnknown,
731    iid: *const TUID,
732    obj: *mut *mut c_void,
733) -> tresult {
734    if this.is_null() || iid.is_null() {
735        if !obj.is_null() {
736            unsafe { *obj = std::ptr::null_mut() };
737        }
738        return kNoInterface;
739    }
740
741    let iid_bytes = unsafe { &*iid };
742    let requested_handler = iid_bytes
743        .iter()
744        .zip(IComponentHandler::IID.iter())
745        .all(|(a, b)| (*a as u8) == *b);
746    let requested_unknown = iid_bytes
747        .iter()
748        .zip(FUnknown::IID.iter())
749        .all(|(a, b)| (*a as u8) == *b);
750    if !(requested_handler || requested_unknown) {
751        if !obj.is_null() {
752            unsafe { *obj = std::ptr::null_mut() };
753        }
754        return kNoInterface;
755    }
756
757    let ctx = this as *mut HostComponentHandlerContext;
758    unsafe {
759        (*ctx).ref_count.fetch_add(1, Ordering::Relaxed);
760        if !obj.is_null() {
761            *obj = this.cast::<c_void>();
762        }
763    }
764    kResultOk
765}
766
767unsafe extern "system" fn component_handler_add_ref(this: *mut FUnknown) -> uint32 {
768    if this.is_null() {
769        return 0;
770    }
771    let ctx = this as *mut HostComponentHandlerContext;
772    unsafe { (*ctx).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
773}
774
775unsafe extern "system" fn component_handler_release(this: *mut FUnknown) -> uint32 {
776    if this.is_null() {
777        return 0;
778    }
779    let ctx = this as *mut HostComponentHandlerContext;
780    unsafe { (*ctx).ref_count.fetch_sub(1, Ordering::Relaxed) - 1 }
781}
782
783unsafe extern "system" fn component_handler_begin_edit(
784    _this: *mut IComponentHandler,
785    _id: ParamID,
786) -> tresult {
787    kResultOk
788}
789
790unsafe extern "system" fn component_handler_perform_edit(
791    _this: *mut IComponentHandler,
792    _id: ParamID,
793    _value_normalized: ParamValue,
794) -> tresult {
795    kResultOk
796}
797
798unsafe extern "system" fn component_handler_end_edit(
799    _this: *mut IComponentHandler,
800    _id: ParamID,
801) -> tresult {
802    kResultOk
803}
804
805unsafe extern "system" fn component_handler_restart_component(
806    _this: *mut IComponentHandler,
807    _flags: i32,
808) -> tresult {
809    kResultOk
810}
811
812static HOST_COMPONENT_HANDLER_VTBL: IComponentHandlerVtbl = IComponentHandlerVtbl {
813    base: FUnknownVtbl {
814        queryInterface: component_handler_query_interface,
815        addRef: component_handler_add_ref,
816        release: component_handler_release,
817    },
818    beginEdit: component_handler_begin_edit,
819    performEdit: component_handler_perform_edit,
820    endEdit: component_handler_end_edit,
821    restartComponent: component_handler_restart_component,
822};
823
824unsafe extern "system" fn host_query_interface(
825    this: *mut FUnknown,
826    iid: *const TUID,
827    obj: *mut *mut c_void,
828) -> tresult {
829    if this.is_null() || iid.is_null() {
830        if !obj.is_null() {
831            // SAFETY: Caller provides output storage.
832            unsafe {
833                *obj = std::ptr::null_mut();
834            }
835        }
836        return kNoInterface;
837    }
838
839    let iid_bytes = unsafe { &*iid };
840    let requested_host = iid_bytes
841        .iter()
842        .zip(IHostApplication::IID.iter())
843        .all(|(a, b)| (*a as u8) == *b);
844    let requested_unknown = iid_bytes
845        .iter()
846        .zip(FUnknown::IID.iter())
847        .all(|(a, b)| (*a as u8) == *b);
848    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
849    let requested_run_loop = iid_bytes
850        .iter()
851        .zip(Linux::IRunLoop::IID.iter())
852        .all(|(a, b)| (*a as u8) == *b);
853    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
854    let requested_run_loop = false;
855    if !(requested_host || requested_unknown || requested_run_loop) {
856        if !obj.is_null() {
857            // SAFETY: Caller provides output storage.
858            unsafe {
859                *obj = std::ptr::null_mut();
860            }
861        }
862        return kNoInterface;
863    }
864
865    let ctx = this as *mut HostApplicationContext;
866    if !ctx.is_null() {
867        unsafe {
868            if requested_run_loop {
869                (*ctx).run_loop.ref_count.fetch_add(1, Ordering::Relaxed);
870            } else {
871                // SAFETY: `this` is the first field in HostApplicationContext.
872                (*ctx).ref_count.fetch_add(1, Ordering::Relaxed);
873            }
874        }
875    }
876
877    if !obj.is_null() {
878        unsafe {
879            if requested_run_loop {
880                *obj = (&mut (*ctx).run_loop.iface as *mut Linux::IRunLoop).cast::<c_void>();
881            } else {
882                // SAFETY: Caller supplies storage for out-pointer.
883                *obj = this.cast::<c_void>();
884            }
885        }
886    }
887    kResultOk
888}
889
890unsafe extern "system" fn host_add_ref(this: *mut FUnknown) -> uint32 {
891    if this.is_null() {
892        return 0;
893    }
894    let ctx = this as *mut HostApplicationContext;
895    // SAFETY: `this` points to embedded host interface at offset 0.
896    unsafe { (*ctx).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
897}
898
899unsafe extern "system" fn host_release(this: *mut FUnknown) -> uint32 {
900    if this.is_null() {
901        return 0;
902    }
903    let ctx = this as *mut HostApplicationContext;
904    // SAFETY: `this` points to embedded host interface at offset 0.
905    unsafe { (*ctx).ref_count.fetch_sub(1, Ordering::Relaxed) - 1 }
906}
907
908unsafe extern "system" fn host_get_name(
909    _this: *mut IHostApplication,
910    name: *mut String128,
911) -> tresult {
912    if name.is_null() {
913        return kNoInterface;
914    }
915    let encoded: Vec<u16> = "Maolan".encode_utf16().collect();
916    // SAFETY: `name` points to writable `String128`.
917    unsafe {
918        (*name).fill(0);
919        for (idx, ch) in encoded
920            .iter()
921            .take((*name).len().saturating_sub(1))
922            .enumerate()
923        {
924            (*name)[idx] = *ch;
925        }
926    }
927    kResultOk
928}
929
930unsafe extern "system" fn host_create_instance(
931    _this: *mut IHostApplication,
932    cid: *mut TUID,
933    iid: *mut TUID,
934    obj: *mut *mut c_void,
935) -> tresult {
936    if obj.is_null() {
937        return kInvalidArgument;
938    }
939    // SAFETY: caller provided out pointer.
940    unsafe {
941        *obj = std::ptr::null_mut();
942    }
943
944    let wants_message =
945        iid_ptr_matches(cid, &IMessage::IID) || iid_ptr_matches(iid, &IMessage::IID);
946    let wants_attributes =
947        iid_ptr_matches(cid, &IAttributeList::IID) || iid_ptr_matches(iid, &IAttributeList::IID);
948    if wants_message {
949        let message = Box::new(HostMessage::new());
950        let raw = Box::into_raw(message);
951        // SAFETY: `iface` is first field and valid for COM client usage.
952        unsafe {
953            *obj = (&mut (*raw).iface as *mut IMessage).cast::<c_void>();
954        }
955        return kResultOk;
956    }
957
958    if wants_attributes {
959        let attrs = Box::new(HostAttributeList::new());
960        let raw = Box::into_raw(attrs);
961        // SAFETY: `iface` is first field and valid for COM client usage.
962        unsafe {
963            *obj = (&mut (*raw).iface as *mut IAttributeList).cast::<c_void>();
964        }
965        return kResultOk;
966    }
967
968    kNotImplemented
969}
970
971static HOST_APPLICATION_VTBL: IHostApplicationVtbl = IHostApplicationVtbl {
972    base: FUnknownVtbl {
973        queryInterface: host_query_interface,
974        addRef: host_add_ref,
975        release: host_release,
976    },
977    getName: host_get_name,
978    createInstance: host_create_instance,
979};
980
981unsafe extern "system" fn run_loop_query_interface(
982    this: *mut FUnknown,
983    iid: *const TUID,
984    obj: *mut *mut c_void,
985) -> tresult {
986    if this.is_null() || iid.is_null() || obj.is_null() {
987        return kInvalidArgument;
988    }
989    let requested_run_loop = iid_ptr_matches(iid, &Linux::IRunLoop::IID);
990    let requested_unknown = iid_ptr_matches(iid, &FUnknown::IID);
991    if !(requested_run_loop || requested_unknown) {
992        unsafe { *obj = std::ptr::null_mut() };
993        return kNoInterface;
994    }
995    let ctx = this as *mut HostRunLoopContext;
996    unsafe {
997        (*ctx).ref_count.fetch_add(1, Ordering::Relaxed);
998        *obj = this.cast::<c_void>();
999    }
1000    kResultOk
1001}
1002
1003unsafe extern "system" fn run_loop_add_ref(this: *mut FUnknown) -> uint32 {
1004    if this.is_null() {
1005        return 0;
1006    }
1007    let ctx = this as *mut HostRunLoopContext;
1008    unsafe { (*ctx).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
1009}
1010
1011unsafe extern "system" fn run_loop_release(this: *mut FUnknown) -> uint32 {
1012    if this.is_null() {
1013        return 0;
1014    }
1015    let ctx = this as *mut HostRunLoopContext;
1016    unsafe { (*ctx).ref_count.fetch_sub(1, Ordering::Relaxed) - 1 }
1017}
1018
1019unsafe extern "system" fn run_loop_register_event_handler(
1020    _this: *mut Linux::IRunLoop,
1021    handler: *mut Linux::IEventHandler,
1022    fd: i32,
1023) -> tresult {
1024    if handler.is_null() {
1025        return kInvalidArgument;
1026    }
1027    let unknown = handler as *mut FUnknown;
1028    unsafe {
1029        let _ = ((*(*unknown).vtbl).addRef)(unknown);
1030    }
1031    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1032    state.event_handlers.push(RunLoopEventHandler {
1033        handler: handler as usize,
1034        fd,
1035    });
1036    kResultOk
1037}
1038
1039unsafe extern "system" fn run_loop_unregister_event_handler(
1040    _this: *mut Linux::IRunLoop,
1041    handler: *mut Linux::IEventHandler,
1042) -> tresult {
1043    if handler.is_null() {
1044        return kInvalidArgument;
1045    }
1046    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1047    state.event_handlers.retain(|h| {
1048        if h.handler == handler as usize {
1049            let unknown = handler as *mut FUnknown;
1050            unsafe {
1051                let _ = ((*(*unknown).vtbl).release)(unknown);
1052            }
1053            false
1054        } else {
1055            true
1056        }
1057    });
1058    kResultOk
1059}
1060
1061unsafe extern "system" fn run_loop_register_timer(
1062    _this: *mut Linux::IRunLoop,
1063    handler: *mut Linux::ITimerHandler,
1064    milliseconds: u64,
1065) -> tresult {
1066    if handler.is_null() {
1067        return kInvalidArgument;
1068    }
1069    let unknown = handler as *mut FUnknown;
1070    unsafe {
1071        let _ = ((*(*unknown).vtbl).addRef)(unknown);
1072    }
1073    let interval = Duration::from_millis(milliseconds.max(1));
1074    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1075    state.timer_handlers.push(RunLoopTimerHandler {
1076        handler: handler as usize,
1077        interval,
1078        next_tick: Instant::now() + interval,
1079    });
1080    unsafe {
1081        // Kick timer once so plugins that defer first paint to timer callbacks can initialize.
1082        ((*(*handler).vtbl).onTimer)(handler);
1083    }
1084    kResultOk
1085}
1086
1087unsafe extern "system" fn run_loop_unregister_timer(
1088    _this: *mut Linux::IRunLoop,
1089    handler: *mut Linux::ITimerHandler,
1090) -> tresult {
1091    if handler.is_null() {
1092        return kInvalidArgument;
1093    }
1094    let mut state = run_loop_state().lock().expect("run loop mutex poisoned");
1095    state.timer_handlers.retain(|t| {
1096        if t.handler == handler as usize {
1097            let unknown = handler as *mut FUnknown;
1098            unsafe {
1099                let _ = ((*(*unknown).vtbl).release)(unknown);
1100            }
1101            false
1102        } else {
1103            true
1104        }
1105    });
1106    kResultOk
1107}
1108
1109static HOST_RUN_LOOP_VTBL: Linux::IRunLoopVtbl = Linux::IRunLoopVtbl {
1110    base: FUnknownVtbl {
1111        queryInterface: run_loop_query_interface,
1112        addRef: run_loop_add_ref,
1113        release: run_loop_release,
1114    },
1115    registerEventHandler: run_loop_register_event_handler,
1116    unregisterEventHandler: run_loop_unregister_event_handler,
1117    registerTimer: run_loop_register_timer,
1118    unregisterTimer: run_loop_unregister_timer,
1119};
1120
1121#[repr(C)]
1122struct HostMessage {
1123    iface: IMessage,
1124    ref_count: AtomicU32,
1125    message_id: FIDString,
1126    attributes: *mut IAttributeList,
1127}
1128
1129impl HostMessage {
1130    fn new() -> Self {
1131        let attrs = Box::new(HostAttributeList::new());
1132        let attrs_raw = Box::into_raw(attrs);
1133        Self {
1134            iface: IMessage {
1135                vtbl: &HOST_MESSAGE_VTBL,
1136            },
1137            ref_count: AtomicU32::new(1),
1138            message_id: c"".as_ptr(),
1139            // SAFETY: `iface` is first field.
1140            attributes: unsafe { &mut (*attrs_raw).iface as *mut IAttributeList },
1141        }
1142    }
1143}
1144
1145#[repr(C)]
1146struct HostAttributeList {
1147    iface: IAttributeList,
1148    ref_count: AtomicU32,
1149}
1150
1151impl HostAttributeList {
1152    fn new() -> Self {
1153        Self {
1154            iface: IAttributeList {
1155                vtbl: &HOST_ATTRIBUTE_LIST_VTBL,
1156            },
1157            ref_count: AtomicU32::new(1),
1158        }
1159    }
1160}
1161
1162fn iid_ptr_matches(iid_ptr: *const TUID, guid: &[u8; 16]) -> bool {
1163    if iid_ptr.is_null() {
1164        return false;
1165    }
1166    // SAFETY: caller provides valid IID pointer for the duration of call.
1167    let iid = unsafe { &*iid_ptr };
1168    iid.iter()
1169        .zip(guid.iter())
1170        .all(|(lhs, rhs)| (*lhs as u8) == *rhs)
1171}
1172
1173unsafe extern "system" fn host_message_query_interface(
1174    this: *mut FUnknown,
1175    iid: *const TUID,
1176    obj: *mut *mut c_void,
1177) -> tresult {
1178    if this.is_null() || iid.is_null() || obj.is_null() {
1179        return kInvalidArgument;
1180    }
1181    let requested_message = iid_ptr_matches(iid, &IMessage::IID);
1182    let requested_unknown = iid_ptr_matches(iid, &FUnknown::IID);
1183    if !(requested_message || requested_unknown) {
1184        // SAFETY: out pointer valid.
1185        unsafe { *obj = std::ptr::null_mut() };
1186        return kNoInterface;
1187    }
1188    let msg = this as *mut HostMessage;
1189    // SAFETY: message context pointer is valid.
1190    unsafe {
1191        (*msg).ref_count.fetch_add(1, Ordering::Relaxed);
1192        *obj = this.cast::<c_void>();
1193    }
1194    kResultOk
1195}
1196
1197unsafe extern "system" fn host_message_add_ref(this: *mut FUnknown) -> uint32 {
1198    if this.is_null() {
1199        return 0;
1200    }
1201    let msg = this as *mut HostMessage;
1202    // SAFETY: pointer valid for atomic update.
1203    unsafe { (*msg).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
1204}
1205
1206unsafe extern "system" fn host_message_release(this: *mut FUnknown) -> uint32 {
1207    if this.is_null() {
1208        return 0;
1209    }
1210    let msg = this as *mut HostMessage;
1211    // SAFETY: pointer valid for atomic update.
1212    let remaining = unsafe { (*msg).ref_count.fetch_sub(1, Ordering::AcqRel) - 1 };
1213    if remaining == 0 {
1214        // SAFETY: Release the message-owned reference to attributes before freeing message.
1215        unsafe {
1216            if !(*msg).attributes.is_null() {
1217                let attrs_unknown = (*msg).attributes.cast::<FUnknown>();
1218                let _ = host_attr_release(attrs_unknown);
1219                (*msg).attributes = std::ptr::null_mut();
1220            }
1221            let _ = Box::from_raw(msg);
1222        }
1223    }
1224    remaining
1225}
1226
1227unsafe extern "system" fn host_message_get_id(this: *mut IMessage) -> FIDString {
1228    if this.is_null() {
1229        return c"".as_ptr();
1230    }
1231    let msg = this as *mut HostMessage;
1232    // SAFETY: valid pointer for message context.
1233    unsafe { (*msg).message_id }
1234}
1235
1236unsafe extern "system" fn host_message_set_id(this: *mut IMessage, id: FIDString) {
1237    if this.is_null() {
1238        return;
1239    }
1240    let msg = this as *mut HostMessage;
1241    // SAFETY: we only keep borrowed pointer; plugin controls lifetime for call scope.
1242    unsafe {
1243        (*msg).message_id = if id.is_null() { c"".as_ptr() } else { id };
1244    }
1245}
1246
1247unsafe extern "system" fn host_message_get_attributes(this: *mut IMessage) -> *mut IAttributeList {
1248    if this.is_null() {
1249        return std::ptr::null_mut();
1250    }
1251    let msg = this as *mut HostMessage;
1252    // SAFETY: return a referenced COM pointer to caller.
1253    unsafe {
1254        if !(*msg).attributes.is_null() {
1255            let attrs_unknown = (*msg).attributes.cast::<FUnknown>();
1256            let _ = host_attr_add_ref(attrs_unknown);
1257        }
1258        (*msg).attributes
1259    }
1260}
1261
1262static HOST_MESSAGE_VTBL: IMessageVtbl = IMessageVtbl {
1263    base: FUnknownVtbl {
1264        queryInterface: host_message_query_interface,
1265        addRef: host_message_add_ref,
1266        release: host_message_release,
1267    },
1268    getMessageID: host_message_get_id,
1269    setMessageID: host_message_set_id,
1270    getAttributes: host_message_get_attributes,
1271};
1272
1273unsafe extern "system" fn host_attr_query_interface(
1274    this: *mut FUnknown,
1275    iid: *const TUID,
1276    obj: *mut *mut c_void,
1277) -> tresult {
1278    if this.is_null() || iid.is_null() || obj.is_null() {
1279        return kInvalidArgument;
1280    }
1281    let requested_attr = iid_ptr_matches(iid, &IAttributeList::IID);
1282    let requested_unknown = iid_ptr_matches(iid, &FUnknown::IID);
1283    if !(requested_attr || requested_unknown) {
1284        // SAFETY: out pointer valid.
1285        unsafe { *obj = std::ptr::null_mut() };
1286        return kNoInterface;
1287    }
1288    let attrs = this as *mut HostAttributeList;
1289    // SAFETY: pointer valid.
1290    unsafe {
1291        (*attrs).ref_count.fetch_add(1, Ordering::Relaxed);
1292        *obj = this.cast::<c_void>();
1293    }
1294    kResultOk
1295}
1296
1297unsafe extern "system" fn host_attr_add_ref(this: *mut FUnknown) -> uint32 {
1298    if this.is_null() {
1299        return 0;
1300    }
1301    let attrs = this as *mut HostAttributeList;
1302    // SAFETY: pointer valid.
1303    unsafe { (*attrs).ref_count.fetch_add(1, Ordering::Relaxed) + 1 }
1304}
1305
1306unsafe extern "system" fn host_attr_release(this: *mut FUnknown) -> uint32 {
1307    if this.is_null() {
1308        return 0;
1309    }
1310    let attrs = this as *mut HostAttributeList;
1311    // SAFETY: pointer valid.
1312    let remaining = unsafe { (*attrs).ref_count.fetch_sub(1, Ordering::AcqRel) - 1 };
1313    if remaining == 0 {
1314        // SAFETY: pointer was allocated with Box::into_raw.
1315        unsafe {
1316            let _ = Box::from_raw(attrs);
1317        }
1318    }
1319    remaining
1320}
1321
1322unsafe extern "system" fn host_attr_set_int(
1323    _this: *mut IAttributeList,
1324    _id: IAttrID,
1325    _value: i64,
1326) -> tresult {
1327    kResultOk
1328}
1329
1330unsafe extern "system" fn host_attr_get_int(
1331    _this: *mut IAttributeList,
1332    _id: IAttrID,
1333    value: *mut i64,
1334) -> tresult {
1335    if value.is_null() {
1336        return kInvalidArgument;
1337    }
1338    // SAFETY: caller provides writable pointer.
1339    unsafe { *value = 0 };
1340    kResultFalse
1341}
1342
1343unsafe extern "system" fn host_attr_set_float(
1344    _this: *mut IAttributeList,
1345    _id: IAttrID,
1346    _value: f64,
1347) -> tresult {
1348    kResultOk
1349}
1350
1351unsafe extern "system" fn host_attr_get_float(
1352    _this: *mut IAttributeList,
1353    _id: IAttrID,
1354    value: *mut f64,
1355) -> tresult {
1356    if value.is_null() {
1357        return kInvalidArgument;
1358    }
1359    // SAFETY: caller provides writable pointer.
1360    unsafe { *value = 0.0 };
1361    kResultFalse
1362}
1363
1364unsafe extern "system" fn host_attr_set_string(
1365    _this: *mut IAttributeList,
1366    _id: IAttrID,
1367    _string: *const TChar,
1368) -> tresult {
1369    kResultOk
1370}
1371
1372unsafe extern "system" fn host_attr_get_string(
1373    _this: *mut IAttributeList,
1374    _id: IAttrID,
1375    string: *mut TChar,
1376    size_in_bytes: u32,
1377) -> tresult {
1378    if string.is_null() || size_in_bytes < std::mem::size_of::<TChar>() as u32 {
1379        return kInvalidArgument;
1380    }
1381    // SAFETY: buffer has at least one TChar cell.
1382    unsafe { *string = 0 };
1383    kResultFalse
1384}
1385
1386unsafe extern "system" fn host_attr_set_binary(
1387    _this: *mut IAttributeList,
1388    _id: IAttrID,
1389    _data: *const c_void,
1390    _size_in_bytes: u32,
1391) -> tresult {
1392    kResultOk
1393}
1394
1395unsafe extern "system" fn host_attr_get_binary(
1396    _this: *mut IAttributeList,
1397    _id: IAttrID,
1398    data: *mut *const c_void,
1399    size_in_bytes: *mut u32,
1400) -> tresult {
1401    if data.is_null() || size_in_bytes.is_null() {
1402        return kInvalidArgument;
1403    }
1404    // SAFETY: caller provides writable pointers.
1405    unsafe {
1406        *data = std::ptr::null();
1407        *size_in_bytes = 0;
1408    }
1409    kResultFalse
1410}
1411
1412static HOST_ATTRIBUTE_LIST_VTBL: IAttributeListVtbl = IAttributeListVtbl {
1413    base: FUnknownVtbl {
1414        queryInterface: host_attr_query_interface,
1415        addRef: host_attr_add_ref,
1416        release: host_attr_release,
1417    },
1418    setInt: host_attr_set_int,
1419    getInt: host_attr_get_int,
1420    setFloat: host_attr_set_float,
1421    getFloat: host_attr_get_float,
1422    setString: host_attr_set_string,
1423    getString: host_attr_get_string,
1424    setBinary: host_attr_set_binary,
1425    getBinary: host_attr_get_binary,
1426};
1427
1428/// Get the actual module path from a VST3 bundle path
1429fn get_module_path(bundle_path: &Path) -> Result<std::path::PathBuf, String> {
1430    #[cfg(target_os = "macos")]
1431    {
1432        // macOS: .vst3/Contents/MacOS/plugin
1433        let module = bundle_path.join("Contents").join("MacOS").join(
1434            bundle_path
1435                .file_stem()
1436                .and_then(|s| s.to_str())
1437                .unwrap_or("plugin"),
1438        );
1439        if module.exists() {
1440            Ok(module)
1441        } else {
1442            Err(format!("VST3 module not found at {:?}", module))
1443        }
1444    }
1445
1446    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
1447    {
1448        // Linux/FreeBSD: .vst3/Contents/x86_64-linux/plugin.so
1449        let module = bundle_path
1450            .join("Contents")
1451            .join("x86_64-linux")
1452            .join(format!(
1453                "{}.so",
1454                bundle_path
1455                    .file_stem()
1456                    .and_then(|s| s.to_str())
1457                    .unwrap_or("plugin")
1458            ));
1459        if module.exists() {
1460            Ok(module)
1461        } else {
1462            Err(format!("VST3 module not found at {:?}", module))
1463        }
1464    }
1465
1466    #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "freebsd")))]
1467    {
1468        Err("Unsupported platform".to_string())
1469    }
1470}
1471
1472fn extract_cstring(bytes: &[i8]) -> String {
1473    let len = bytes.iter().position(|&c| c == 0).unwrap_or(bytes.len());
1474    let u8_bytes: Vec<u8> = bytes[..len].iter().map(|&b| b as u8).collect();
1475    String::from_utf8_lossy(&u8_bytes).to_string()
1476}