diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c0be84..c5cf2e51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). ### Fixed +- Added a workaround for a bug present in every Bluecat Audio VST3 plugin where + those plugins don't expose the `IPluginBase` interface through their query + interface. - Worked around a regression in Wine 6.5 that would prevent yabridge from shutting down. With Wine 6.5 terminating a Wine process no longer terminates its threads, which would cause yabridge's plugin and host components to wait diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 2c3686c2..abef7c66 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -35,6 +35,19 @@ #define __IFileOperation_INTERFACE_DEFINED__ #include +/** + * This is a workaround for Bluecat Audio plugins that don't expose their + * `IPluginBase` interface through the query interface. Even though every plugin + * object _must_ support `IPlugBase`, these plugins only expose those functions + * through `IComponent` (which derives from `IPluginBase`). So if we do + * encounter one of those plugins, then we'll just have to coerce an + * `IComponent` pointer into an `IPluginBase` smart pointer. This way we can + * keep the rest of yabridge's design in tact. + */ +Steinberg::FUnknownPtr hack_init_plugin_base( + Steinberg::IPtr object, + Steinberg::IPtr component); + InstancePlugView::InstancePlugView() {} InstancePlugView::InstancePlugView( @@ -62,7 +75,7 @@ InstanceInterfaces::InstanceInterfaces( midi_mapping(object), note_expression_controller(object), note_expression_physical_ui_mapping(object), - plugin_base(object), + plugin_base(hack_init_plugin_base(object, component)), unit_data(object), parameter_function_name(object), prefetchable_support(object), @@ -1297,3 +1310,42 @@ void Vst3Bridge::unregister_object_instance(size_t instance_id) { }) .wait(); } + +Steinberg::FUnknownPtr hack_init_plugin_base( + Steinberg::IPtr object, + Steinberg::IPtr component) { + // See the docstring for more information + Steinberg::FUnknownPtr plugin_base(object); + if (plugin_base) { + return plugin_base; + } else if (component) { + // HACK: So this should never be hit, because every object + // initializeable from a plugin's factory must inherit from + // `IPluginBase`. But, the Bluecat Audio plugins seem to have an + // implementation issue where they don't expose this interface. So + // instead we'll coerce from `IComponent` instead if this is the + // case, since `IComponent` derives from `IPluginBase`. Doing + // these manual pointer casts should be perfectly safe, even if + // they go against the very idea of having a query interface. + static_assert(sizeof(Steinberg::FUnknownPtr) == + sizeof(Steinberg::IPtr)); + + std::cerr << "WARNING: This plugin doesn't expose the IPluginBase" + << std::endl; + std::cerr << " interface and is broken. We will attempt an" + << std::endl; + std::cerr << " unsafe coercion from IComponent instead." + << std::endl; + + Steinberg::IPtr coerced_plugin_base( + component.get()); + + return *static_cast*>( + &coerced_plugin_base); + } else { + // This isn't really needed because the VST3 smart pointers can already + // deal with null pointers, but might as well drive the point of this + // hack home even further + return nullptr; + } +}