// yabridge: a Wine plugin bridge // Copyright (C) 2020-2023 Robbert van der Helm // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include // Generated inside of the build directory #include #include "../common/linking.h" #include "../common/utils.h" #include "utils.h" // These chainloader libraries are tiny, mostly dependencyless libraries that // `dlopen()` the actual `libyabridge-{clap,vst2,vst3}.so` files and forward the // entry point function calls from this library to those. Or technically, these // libraries use dedicated entry point functions because multiple chainloader // libraries may all dynamically link to the exact same plugin library, so we // can't store any bridge information in a global there. This approach avoids // wasting disk space on copies on file systems that don't support reflinking, // but more importantly it also avoids the need to rerun `yabridgectl sync` // whenever yabridge is updated. This is even more important when considering // distro packaging, because updates to Boost might require the package to be // rebuilt, which in turn would also require a resync. namespace fs = ghc::filesystem; using audioMasterCallback = void*; using AEffect = void; // These functions are loaded from `libyabridge-vst3.so` the first time // `VSTPluginMain` gets called AEffect* (*yabridge_plugin_init)(audioMasterCallback host_callback, const char* plugin_path) = nullptr; // This bridges the `yabridge_version()` call from the plugin library. This // function was added later, so through weird version mixing it may be missing // on the yabridge library. const char* (*remote_yabridge_version)() = nullptr; /** * The first time one of the exported functions from this library gets called, * we'll need to load the corresponding `libyabridge-*.so` file and fetch the * the entry point functions from that file. */ bool initialize_library() { static void* library_handle = nullptr; static std::mutex library_handle_mutex; std::lock_guard lock(library_handle_mutex); // There should be no situation where this library gets loaded and then two // threads immediately start calling functions, but we'll handle that // situation just in case it does happen if (library_handle) { return true; } library_handle = find_plugin_library(yabridge_vst2_plugin_name); if (!library_handle) { return false; } #define LOAD_FUNCTION(name) \ do { \ (name) = \ reinterpret_cast(dlsym(library_handle, #name)); \ if (!(name)) { \ log_failing_dlsym(yabridge_vst2_plugin_name, #name); \ return false; \ } \ } while (false) LOAD_FUNCTION(yabridge_plugin_init); // This one can be a null pointer if the function does not yet exist in this // yabridge version remote_yabridge_version = reinterpret_cast( dlsym(library_handle, "yabridge_version")); #undef LOAD_FUNCTION return true; } extern "C" YABRIDGE_EXPORT AEffect* VSTPluginMain( audioMasterCallback host_callback) { assert(host_callback); if (!initialize_library()) { return nullptr; } const fs::path this_plugin_path = get_this_file_location(); return yabridge_plugin_init(host_callback, this_plugin_path.c_str()); } // XXX: GCC doens't seem to have a clean way to let you define an arbitrary // function called 'main'. Even JUCE does it this way, so it should be // safe. extern "C" YABRIDGE_EXPORT AEffect* deprecated_main( audioMasterCallback audioMaster) asm("main"); YABRIDGE_EXPORT AEffect* deprecated_main(audioMasterCallback audioMaster) { return VSTPluginMain(audioMaster); } /** * This returns the actual yabridge library's version through * `yabridge_version()`. Reporting the version associated with this chainloader * wouldn't be very useful, and that would also cause the chainloader to be * rebuilt on every git commit in development. */ extern "C" YABRIDGE_EXPORT const char* yabridge_version() { if (!initialize_library() || !remote_yabridge_version) { return nullptr; } return remote_yabridge_version(); }