From c9060e984d974b4e9df6d88a6dd54030a7c0b09b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 29 Apr 2020 16:54:26 +0200 Subject: [PATCH 01/13] Add another flag for enabling a bit bridge build --- cross-wine.conf | 16 +++++++++ cross-wine64.conf | 17 --------- meson.build | 67 ++++++++++++++++++++++++----------- meson_options.txt | 10 +++++- src/common/config/config.h.in | 30 ++++++++++++++++ src/common/config/meson.build | 13 +++++++ src/plugin/host-bridge.cpp | 8 ++--- 7 files changed, 118 insertions(+), 43 deletions(-) create mode 100644 cross-wine.conf delete mode 100644 cross-wine64.conf create mode 100644 src/common/config/config.h.in diff --git a/cross-wine.conf b/cross-wine.conf new file mode 100644 index 00000000..2253803c --- /dev/null +++ b/cross-wine.conf @@ -0,0 +1,16 @@ +[binaries] +c = 'winegcc' +cpp = 'wineg++' +ar = 'ar' +strip = 'strip' + +[properties] +# These would be the arguments for 64-bit compilation. We'll set the `-m64` flag +# (or `-m32`) in our meson.build to allow the 32-bit bitbridge to be built in +# the same build directory as the plugin and the regular 64-bit version of the +# host application to ensure that they are always in sync. +# c_args = ['-m64'] +# cpp_args = ['-m64'] +# cpp_link_args = ['-m64', '-mwindows'] +cpp_link_args = ['-mwindows'] +needs_exe_wrapper = true diff --git a/cross-wine64.conf b/cross-wine64.conf deleted file mode 100644 index fa7247e4..00000000 --- a/cross-wine64.conf +++ /dev/null @@ -1,17 +0,0 @@ -[binaries] -c = 'winegcc' -cpp = 'wineg++' -ar = 'ar' -strip = 'strip' - -[properties] -c_args=['-m64'] -cpp_args=['-m64'] -cpp_link_args=['-m64', '-mwindows'] -needs_exe_wrapper = true - -[host_machine] -system = 'linux' -cpu_family = 'x86_64' -cpu = 'x86_64' -endian = 'little' diff --git a/meson.build b/meson.build index aee1352b..6c05d81e 100644 --- a/meson.build +++ b/meson.build @@ -12,9 +12,19 @@ winelib_check = '''#ifndef __WINE__ #error 1 #endif''' if not meson.get_compiler('cpp').compiles(winelib_check) - error('You need to set up a cross compiler, check the README for more information.') + error('You need to set up a cross compiler, check the README for compilation instructions.') endif +# Depending on the `use-bitbridge` flag we'll enable building a second 32-bit +# host application that can act as a bit bridge for using 32-bit Windows plugins +# in 64-bit Linux VST hsots. The plugin will determine which host application to +# use based on the `.dll` file it's trying to load. +# This setup is necessary until Meson provides a way to have multiple +# cross-builds for a single build directory: +# https://github.com/mesonbuild/meson/issues/5125 +host_name_64bit = 'yabridge-host' +host_name_32bit = 'yabridge-host-32' + # This provides an easy way to start the Wine VST host using winedbg since it # can be quite a pain to set up compiler_options = [] @@ -22,13 +32,8 @@ if get_option('use-winedbg') compiler_options += '-DUSE_WINEDBG' endif -# The application consists of a VST plugin (yabridge) that calls a winelib -# program (yabridge-host) that can host Windows VST plugins. More information -# about the way these two components work together can be found in the readme -# file. - # Generate header files for configuration variables such as the current git tag -# and the last commit hash +# and the name of the host binary subdir('src/common/config') boost_dep = dependency('boost', modules : ['filesystem']) @@ -40,6 +45,11 @@ xcb_dep = dependency('xcb') include_dir = include_directories('src/include') +# The application consists of a VST plugin (yabridge) that calls a winelib +# program (yabridge-host) that can host Windows VST plugins. More information +# about the way these two components work together can be found in the readme +# file. + shared_library( 'yabridge', [ @@ -56,21 +66,38 @@ shared_library( link_args : ['-ldl'] ) +host_sources = [ + 'src/common/logging.cpp', + 'src/common/serialization.cpp', + 'src/wine-host/editor.cpp', + 'src/wine-host/editor.cpp', + 'src/wine-host/plugin-bridge.cpp', + 'src/wine-host/vst-host.cpp', + 'src/wine-host/utils.cpp', + version_header, +] + executable( - 'yabridge-host', - [ - 'src/common/logging.cpp', - 'src/common/serialization.cpp', - 'src/wine-host/editor.cpp', - 'src/wine-host/editor.cpp', - 'src/wine-host/plugin-bridge.cpp', - 'src/wine-host/vst-host.cpp', - 'src/wine-host/utils.cpp', - version_header, - ], + host_name_64bit, + host_sources, native : false, include_directories : include_dir, dependencies : [boost_dep, bitsery_dep, wine_threads_dep, xcb_dep], - cpp_args : compiler_options, - link_args : [] + cpp_args : compiler_options + ['-m64'], + link_args : ['-m64'] ) + +if get_option('use-bitbridge') + compiler_options += '-DUSE_BITBRIDGE' + + # TODO: Use 32 bit libraries + executable( + host_name_32bit, + host_sources, + native : false, + include_directories : include_dir, + dependencies : [boost_dep, bitsery_dep, wine_threads_dep, xcb_dep], + cpp_args : compiler_options + ['-m32'], + link_args : ['-m32'] + ) +endif diff --git a/meson_options.txt b/meson_options.txt index 0cf248c6..fab1ccee 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,6 +1,14 @@ +option( + 'use-bitbridge', + type : 'boolean', + value : false, + description : '''Build a 32-bit host application for hosting 32-bit plugins. + See the readme for full instructions on how to use this.''' +) + option( 'use-winedbg', type : 'boolean', value : false, - description: 'Whether to run the Wien VST host with GDB attached. Might not always be reliable.' + description : 'Whether to run the Wien VST host with GDB attached. Might not always be reliable.' ) diff --git a/src/common/config/config.h.in b/src/common/config/config.h.in new file mode 100644 index 00000000..8f225016 --- /dev/null +++ b/src/common/config/config.h.in @@ -0,0 +1,30 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 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 . + +#pragma once + +/** + * The name of the wine host name, e.g. `yabridge-host.exe` for the regular 64 + * bit build. + */ +constexpr char yabridge_wine_host_name[] = "@host_binary_64bit@"; + +/** + * The name of the 32-bit wine host name, e.g. `yabridge-host-32.exe`.` This is + * used as a bitbridge to be able to load legacy 32-bit only Windows plugins + * from a 64-bit Linux host. + */ +constexpr char yabridge_wine_host_name_32bit[] = "@host_binary_32bit@"; diff --git a/src/common/config/meson.build b/src/common/config/meson.build index 5513065a..eb2cd4f6 100644 --- a/src/common/config/meson.build +++ b/src/common/config/meson.build @@ -1,3 +1,16 @@ +# Contains constants determined while configuring the build. As an alternative +# to preprocessor macros. +config_header = configure_file( + input : 'config.h.in', + output : 'config.h', + configuration : configuration_data( + { + 'host_binary_32bit': host_name_32bit + '.exe', + 'host_binary_64bit': host_name_64bit + '.exe', + } + ) +) + # Generate a file containing the last annotated git tag and possibly also the # amount of commits since then and the hash of the last commit version_header = vcs_tag( diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index 6398dff5..4165e13d 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -30,6 +30,7 @@ #endif // Generated inside of build directory +#include #include #include "../common/communication.h" @@ -40,11 +41,6 @@ namespace bp = boost::process; // boost::filesystem namespace fs = boost::filesystem; -/** - * The name of the wine VST host binary. - */ -constexpr auto yabridge_wine_host_name = "yabridge-host.exe"; - /** * Used for generating random identifiers. */ @@ -500,6 +496,8 @@ fs::path find_wine_vst_host() { return host_path; } + // TODO: First, check whether the plugin is 32-bit or 64-bit, and then + // search for the correct binary accordingly // Bosot will return an empty path if the file could not be found in the // search path const fs::path vst_host_path = bp::search_path(yabridge_wine_host_name); From 027d9a96d562151cabd7c5620580702e2f0b9a84 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 29 Apr 2020 17:17:33 +0200 Subject: [PATCH 02/13] Always use 64-bit pointer types for serialization This way the 32-bit host can cast them down to 32-bit integers when needed, and the serialization pipeline can stay fixed. We're not passing any pointers directly to the other application anyway so this should be safe! --- src/common/events.h | 7 +++++-- src/common/logging.cpp | 4 +++- src/common/serialization.h | 30 +++++++++++++++++++++++------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/common/events.h b/src/common/events.h index ab861d6a..c78403bb 100644 --- a/src/common/events.h +++ b/src/common/events.h @@ -209,13 +209,16 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket, [&](const std::vector& buffer) -> void* { return const_cast(buffer.data()); }, - [&](size_t& window_handle) -> void* { + [&](native_size_t& window_handle) -> void* { // This is the X11 window handle that the editor should // reparent itself to. We have a special wrapper around the // dispatch function that intercepts `effEditOpen` events // and creates a Win32 window and then finally embeds the // X11 window Wine created into this wnidow handle. - return reinterpret_cast(window_handle); + // Make sure to convert the window ID first to `size_t` in + // case this is the 32-bit host. + return reinterpret_cast( + static_cast(window_handle)); }, [&](const AEffect&) -> void* { return nullptr; }, [&](DynamicVstEvents& events) -> void* { diff --git a/src/common/logging.cpp b/src/common/logging.cpp index a9966c90..5d74efc5 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -176,7 +176,9 @@ void Logger::log_event(bool is_dispatch, [&](const std::vector& buffer) { message << "<" << buffer.size() << " byte chunk>"; }, - [&](const intptr_t&) { message << ""; }, + [&](const native_size_t& window_id) { + message << ""; + }, [&](const AEffect&) { message << ""; }, [&](const DynamicVstEvents& events) { message << "<" << events.events.size() << " midi_events>"; diff --git a/src/common/serialization.h b/src/common/serialization.h index 48f111af..1a49ea5e 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -57,6 +57,23 @@ constexpr size_t max_midi_events = max_buffer_size / sizeof(size_t); */ constexpr size_t binary_buffer_size = 50 << 20; +// The plugin should always be compiled to a 64-bit version, but the host +// application can also be 32-bit to allow using 32-bit legacy Windows VST in a +// modern Linux VST host. Because of this we have to make sure to always use +// 64-bit integers in places where we would otherwise use `size_t` and +// `intptr_t`. Otherwise the binary serialization would break. The 64 <-> 32 bit +// conversion for the 32-bit host application won't cause any issues for us +// since we can't directly pass pointers between the plugin and the host anyway. + +#ifndef __WINE__ +// Sanity check for the plugin, both the 64 and 32 bit hosts should follow these +// conventions +static_assert(std::is_same_v); +static_assert(std::is_same_v); +#endif +using native_size_t = uint64_t; +using native_intptr_t = int64_t; + // The cannonical overloading template for `std::visitor`, not sure why this // isn't part of the standard library template @@ -248,7 +265,7 @@ struct WantsString {}; using EventPayload = std::variant, - size_t, + native_size_t, AEffect, DynamicVstEvents, WantsChunkBuffer, @@ -270,7 +287,9 @@ void serialize(S& s, EventPayload& payload) { [](S& s, std::vector& buffer) { s.container1b(buffer, binary_buffer_size); }, - [](S& s, size_t& window_handle) { s.value8b(window_handle); }, + [](S& s, native_size_t& window_handle) { + s.value8b(window_handle); + }, [](S& s, AEffect& effect) { s.object(effect); }, [](S& s, DynamicVstEvents& events) { s.container( @@ -292,10 +311,7 @@ void serialize(S& s, EventPayload& payload) { struct Event { int opcode; int index; - // TODO: This is an intptr_t, if we want to support 32 bit Wine plugins all - // of these these intptr_t types should be replace by `uint64_t` to - // remain compatible with the Linux VST plugin. - intptr_t value; + native_intptr_t value; float option; /** * The event dispatch function has a void pointer parameter that's often @@ -371,7 +387,7 @@ struct EventResult { /** * The result that should be returned from the dispatch function. */ - intptr_t return_value; + native_intptr_t return_value; /** * Events typically either just return their return value or write a string * into the void pointer, but sometimes an event response should forward From dc650b200a9fe78074b6a07e5315021fa8de62fd Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 29 Apr 2020 19:12:08 +0200 Subject: [PATCH 03/13] Link to the correct libraries in the 32-bit build --- meson.build | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 6c05d81e..70fb5c36 100644 --- a/meson.build +++ b/meson.build @@ -36,7 +36,9 @@ endif # and the name of the host binary subdir('src/common/config') -boost_dep = dependency('boost', modules : ['filesystem']) +# Statically link against Boost.Filesystem, otherwise it becomes impossible to +# distribute a prebuilt version of yabridge +boost_dep = dependency('boost', modules : ['filesystem'], static : true) bitsery_dep = subproject('bitsery').get_variable('bitsery_dep') threads_dep = dependency('threads') # The built in threads dependency does not know how to handle winegcc @@ -88,9 +90,18 @@ executable( ) if get_option('use-bitbridge') + message('Bitbridge enabled, configuring a 32-bit host application') + + # Only used to add a notice when the host starts to indicate that we're + # running in 32-bit compatibility mode compiler_options += '-DUSE_BITBRIDGE' - # TODO: Use 32 bit libraries + # I honestly have no idea what the correct way to have `find_dependency()` use + # `/usr/lib32` instead of `/usr/lib` is. If anyone does know, please tell me! + winegcc = meson.get_compiler('cpp', native : false) + boost_dep = [winegcc.find_library('boost_filesystem')] + xcb_dep = [winegcc.find_library('xcb')] + executable( host_name_32bit, host_sources, From e33f4182060eb7a9cb00e0b79fa0269352a93433 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 29 Apr 2020 19:12:29 +0200 Subject: [PATCH 04/13] Add a notice when the host is in 23-bit mode --- src/wine-host/vst-host.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/wine-host/vst-host.cpp b/src/wine-host/vst-host.cpp index ccb18b21..4f57e705 100644 --- a/src/wine-host/vst-host.cpp +++ b/src/wine-host/vst-host.cpp @@ -17,6 +17,7 @@ #include // Generated inside of build directory +#include #include #include "plugin-bridge.h" @@ -26,9 +27,14 @@ int main(int argc, char* argv[]) { // socket to connect to in plugin/bridge.cpp as the first two arguments of // this process. if (argc < 3) { - std::cerr - << "Usage: yabridge-host.exe " - << std::endl; + std::cerr << "Usage: " +#ifdef USE_BITBRIDGE + << yabridge_wine_host_name_32bit +#else + << yabridge_wine_host_name +#endif + << " " << std::endl; + return 1; } @@ -36,6 +42,9 @@ int main(int argc, char* argv[]) { const std::string socket_endpoint_path(argv[2]); std::cerr << "Initializing yabridge host version " << yabridge_git_version +#ifdef USE_BITBRIDGE + << " (32-bit compatibility mode)" +#endif << std::endl; try { PluginBridge bridge(plugin_dll_path, socket_endpoint_path); From 4e2b8345ab3c66b97069df3c75343a20ce20fbec Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 29 Apr 2020 19:35:58 +0200 Subject: [PATCH 05/13] Add a link to Meson's multiple cross builds issue --- cross-wine.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cross-wine.conf b/cross-wine.conf index 2253803c..87b6dc4f 100644 --- a/cross-wine.conf +++ b/cross-wine.conf @@ -9,8 +9,11 @@ strip = 'strip' # (or `-m32`) in our meson.build to allow the 32-bit bitbridge to be built in # the same build directory as the plugin and the regular 64-bit version of the # host application to ensure that they are always in sync. +# https://github.com/mesonbuild/meson/issues/5125 + # c_args = ['-m64'] # cpp_args = ['-m64'] # cpp_link_args = ['-m64', '-mwindows'] + cpp_link_args = ['-mwindows'] needs_exe_wrapper = true From 2e9adbe31049711548c0f2e413e7ef4e94363246 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 12:32:27 +0200 Subject: [PATCH 06/13] Always write message sizes in 64-bit integers This will make sure the sizes match on both the 32 and 64 bit host applications. This way only the 32-bit application will have to convert between 32 and 64 bit integers. --- src/common/communication.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/common/communication.h b/src/common/communication.h index 54c88ef9..4cc5086c 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -33,7 +33,8 @@ template using InputAdapter = bitsery::InputBufferAdapter; /** - * Serialize an object using bitsery and write it to a socket. + * Serialize an object using bitsery and write it to a socket. This will write + * both the size of the serialized object and the object itself over the socket. * * @param socket The Boost.Asio socket to write to. * @param object The object to write to the stream. @@ -53,8 +54,13 @@ inline void write_object( // Tell the other side how large the object is so it can prepare a buffer // large enough before sending the data + // NOTE: We're writing these sizes as a 64 bit integers, **not** as pointer + // sized integers. This is to provide compatibility with the 32-bit + // bit bridge. This won't make any function difference aside from the + // 32-bit host application having to convert between 64 and 32 bit + // integers. boost::asio::write(socket, - boost::asio::buffer(std::array{size})); + boost::asio::buffer(std::array{size})); const size_t bytes_written = boost::asio::write(socket, boost::asio::buffer(buffer, size)); assert(bytes_written == size); @@ -79,7 +85,8 @@ template inline T& read_object(Socket& socket, T& object, std::vector buffer = std::vector(64)) { - std::array message_length; + // See the note above on the use of `uint64_t` instead of `size_t` + std::array message_length; boost::asio::read(socket, boost::asio::buffer(message_length)); // Make sure the buffer is large enough From 6ca7eae16a21c3d31d1bfd5f3a8b68a13005f099 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 12:48:03 +0200 Subject: [PATCH 07/13] Mention 32-bit support in the readme --- README.md | 38 ++++++++++++++++++++++++++++++-------- src/plugin/host-bridge.cpp | 2 ++ src/wine-host/vst-host.cpp | 2 ++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cdb5cb82..9cf56b44 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # yabridge -Yet Another way to use Windows VST2 plugins in Linux VST hosts. This project -aims to be as transparent as possible to achieve the best possible plugin -compatibility while also being easy to debug and maintain. +Yet Another way to use Windows VST2 plugins in Linux VST hosts, including an +optional bitbridge to run 32-bit Windows plugins in 64-bit Linux hosts. This +project aims to be as transparent as possible to achieve the best possible +plugin compatibility while also being easy to debug and maintain. ## TODOs @@ -13,7 +14,8 @@ There are a few things that should be done before releasing this, including: - Add missing details if any to the architecture section. - Rewrite parts of this readme. - Briefly verify that this also works fine in Reaper and Ardour. -- Document wine32 support. +- Finish 32-bit bitbridge. + - Upstream the updated `lib32-boost-libs` PKGBUILD. ## Tested with @@ -114,19 +116,39 @@ the following dependencies: - Boost - xcb -The following dependencies are included as a Meson wrap: +The following dependencies are included in the repository as a Meson wrap: - bitsery The project can then be compiled as follows: ```shell -meson setup --buildtype=release --cross-file cross-wine64.conf build +meson setup --buildtype=release --cross-file cross-wine.conf build ninja -C build ``` -When developing or debugging yabridge you can change the build type to either -`debug` enable debug symbols and disable optimizations. +### 32-bit bitbridge + +It's also possible to compile a 32-bit host application for yabridge that's +compatible with 32 bit plugins such as old SynthEdit plugins. This will allow +yabridge to act as a bitbirdge, allowing you to run old 32-bit only Windows VST2 +plugins in a modern 64-bit Linux VST host. For this you'll need to have +installed the 32 bit versions of the Boost and XCB libraries. This can be +enabled as follows: + +```shell +# On an existing build +meson configure build -Duse-bitbridge=true +# Configure a new build from scratch +meson setup --buildtype=release --cross-file cross-wine.conf -Duse-bitbridge=true build + +ninja -C build +``` + +This will produce two files called `yabridge-host-32.exe` and +`yabridge-host-32.exe.so`. Yabridge will detect whether the plugin you're trying +to load is 32-bit or 64-bit, and will run either `yabridge-host.exe` or +`yabridge-host-32.exe` accordingly. ## Debugging diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index 4165e13d..66457a95 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -118,6 +118,8 @@ HostBridge::HostBridge(audioMasterCallback host_callback) bp::start_dir = vst_plugin_path.parent_path()) #endif { + // TODO: Add a notice here for whether yabridge has been compiled with + // bitbridge support or not logger.log("Initializing yabridge version " + std::string(yabridge_git_version)); logger.log("host: '" + vst_host_path.string() + "'"); diff --git a/src/wine-host/vst-host.cpp b/src/wine-host/vst-host.cpp index 4f57e705..3d9d0ff1 100644 --- a/src/wine-host/vst-host.cpp +++ b/src/wine-host/vst-host.cpp @@ -41,6 +41,8 @@ int main(int argc, char* argv[]) { const std::string plugin_dll_path(argv[1]); const std::string socket_endpoint_path(argv[2]); + // TODO: Maybe just check one of macros defined by Wine when compiling for + // 32-bit instead std::cerr << "Initializing yabridge host version " << yabridge_git_version #ifdef USE_BITBRIDGE << " (32-bit compatibility mode)" From 79e8a37c39bad78e2c3b50df47d6306ee247026d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 12:55:42 +0200 Subject: [PATCH 08/13] Simplify the 32-bit mode detection --- meson.build | 7 +++---- src/wine-host/vst-host.cpp | 10 ++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index 70fb5c36..15ecfb37 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,9 @@ compiler_options = [] if get_option('use-winedbg') compiler_options += '-DUSE_WINEDBG' endif +if get_option('use-bitbridge') + compiler_options += '-DUSE_BITBRIDGE' +endif # Generate header files for configuration variables such as the current git tag # and the name of the host binary @@ -92,10 +95,6 @@ executable( if get_option('use-bitbridge') message('Bitbridge enabled, configuring a 32-bit host application') - # Only used to add a notice when the host starts to indicate that we're - # running in 32-bit compatibility mode - compiler_options += '-DUSE_BITBRIDGE' - # I honestly have no idea what the correct way to have `find_dependency()` use # `/usr/lib32` instead of `/usr/lib` is. If anyone does know, please tell me! winegcc = meson.get_compiler('cpp', native : false) diff --git a/src/wine-host/vst-host.cpp b/src/wine-host/vst-host.cpp index 3d9d0ff1..44e0a53d 100644 --- a/src/wine-host/vst-host.cpp +++ b/src/wine-host/vst-host.cpp @@ -28,10 +28,10 @@ int main(int argc, char* argv[]) { // this process. if (argc < 3) { std::cerr << "Usage: " -#ifdef USE_BITBRIDGE - << yabridge_wine_host_name_32bit -#else +#ifdef _WIN64 << yabridge_wine_host_name +#else + << yabridge_wine_host_name_32bit #endif << " " << std::endl; @@ -41,10 +41,8 @@ int main(int argc, char* argv[]) { const std::string plugin_dll_path(argv[1]); const std::string socket_endpoint_path(argv[2]); - // TODO: Maybe just check one of macros defined by Wine when compiling for - // 32-bit instead std::cerr << "Initializing yabridge host version " << yabridge_git_version -#ifdef USE_BITBRIDGE +#ifndef _WIN64 << " (32-bit compatibility mode)" #endif << std::endl; From 5958f8058a0687ddbb549ce20cbd32fcf91e95a3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 13:40:55 +0200 Subject: [PATCH 09/13] Print out a list of enabled compile time features --- src/plugin/host-bridge.cpp | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index 66457a95..ee83f624 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -118,15 +118,37 @@ HostBridge::HostBridge(audioMasterCallback host_callback) bp::start_dir = vst_plugin_path.parent_path()) #endif { - // TODO: Add a notice here for whether yabridge has been compiled with - // bitbridge support or not logger.log("Initializing yabridge version " + std::string(yabridge_git_version)); - logger.log("host: '" + vst_host_path.string() + "'"); - logger.log("plugin: '" + vst_plugin_path.string() + "'"); - logger.log("socket: '" + socket_endpoint.path() + "'"); - logger.log("wineprefix: '" + + + // Include a list of enabled compile-tiem features, mostly to make debug + // logs more useful + std::string features_str = ""; + std::vector enabled_features; +#ifdef USE_BITBRIDGE + enabled_features.push_back("bitbrige"); +#endif +#ifdef USE_WINEDBG + enabled_features.push_back("winedbg"); +#endif + if (!enabled_features.empty()) { + std::ostringstream features; + features << enabled_features[0]; + for (auto feature = std::next(enabled_features.begin()); + feature != enabled_features.end(); feature++) { + features << ", " << *feature; + } + + features_str = features.str(); + } + logger.log("enabled features: " + features_str); + logger.log(""); + logger.log("host: '" + vst_host_path.string() + "'"); + logger.log("plugin: '" + vst_plugin_path.string() + "'"); + logger.log("socket: '" + socket_endpoint.path() + "'"); + logger.log("wineprefix: '" + find_wineprefix().value_or("").string() + "'"); + logger.log(""); // It's very important that these sockets are connected to in the same // order in the Wine VST host From dcc1a34a41f268361442ec80b94411a101d8c464 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 15:09:13 +0200 Subject: [PATCH 10/13] Use a better check for the 32-bit host --- src/wine-host/vst-host.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wine-host/vst-host.cpp b/src/wine-host/vst-host.cpp index 44e0a53d..274ac272 100644 --- a/src/wine-host/vst-host.cpp +++ b/src/wine-host/vst-host.cpp @@ -28,10 +28,10 @@ int main(int argc, char* argv[]) { // this process. if (argc < 3) { std::cerr << "Usage: " -#ifdef _WIN64 - << yabridge_wine_host_name -#else +#ifdef __i386__ << yabridge_wine_host_name_32bit +#else + << yabridge_wine_host_name #endif << " " << std::endl; @@ -42,7 +42,7 @@ int main(int argc, char* argv[]) { const std::string socket_endpoint_path(argv[2]); std::cerr << "Initializing yabridge host version " << yabridge_git_version -#ifndef _WIN64 +#ifdef __i386__ << " (32-bit compatibility mode)" #endif << std::endl; From a665c54cd46e384a35df7584c5d4d252767c36d1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 15:55:18 +0200 Subject: [PATCH 11/13] Silence ignore attributes warnings in templates For the 32-bit host. --- meson.build | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 15ecfb37..58aa0619 100644 --- a/meson.build +++ b/meson.build @@ -107,7 +107,14 @@ if get_option('use-bitbridge') native : false, include_directories : include_dir, dependencies : [boost_dep, bitsery_dep, wine_threads_dep, xcb_dep], - cpp_args : compiler_options + ['-m32'], + # FIXME: 32-bit winegcc defines `__stdcall` differently than the 64-bit + # version, and one of the changes is the inclusion of + # `__atribute__((__force_align_arg_pointer__))`. For whetever reason + # this causes GCC to complain when using function pointers with the + # `__stdcall` calling convention in template arguments, although it + # otherwise works just fine. We don't ignore any warnings in the + # regular host application so this should not cause any issues! + cpp_args : compiler_options + ['-m32', '-Wno-ignored-attributes'], link_args : ['-m32'] ) endif From c051123796532185cc5fd07dde91f023f99a91be Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 16:44:26 +0200 Subject: [PATCH 12/13] Improve the look of the enabled features list --- src/plugin/host-bridge.cpp | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index ee83f624..9773f09a 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -120,34 +120,25 @@ HostBridge::HostBridge(audioMasterCallback host_callback) { logger.log("Initializing yabridge version " + std::string(yabridge_git_version)); + logger.log("host: '" + vst_host_path.string() + "'"); + logger.log("plugin: '" + vst_plugin_path.string() + "'"); + logger.log("socket: '" + socket_endpoint.path() + "'"); + logger.log("wineprefix: '" + + find_wineprefix().value_or("").string() + "'"); // Include a list of enabled compile-tiem features, mostly to make debug // logs more useful - std::string features_str = ""; - std::vector enabled_features; + logger.log(""); + logger.log("Enabled features:"); #ifdef USE_BITBRIDGE - enabled_features.push_back("bitbrige"); + logger.log("- bitbridge support"); #endif #ifdef USE_WINEDBG - enabled_features.push_back("winedbg"); + logger.log("- winedbg"); +#endif +#if !(defined(USE_BITBRIDGE) || defined(USE_WINEDBG)) + logger.log(" "); #endif - if (!enabled_features.empty()) { - std::ostringstream features; - features << enabled_features[0]; - for (auto feature = std::next(enabled_features.begin()); - feature != enabled_features.end(); feature++) { - features << ", " << *feature; - } - - features_str = features.str(); - } - logger.log("enabled features: " + features_str); - logger.log(""); - logger.log("host: '" + vst_host_path.string() + "'"); - logger.log("plugin: '" + vst_plugin_path.string() + "'"); - logger.log("socket: '" + socket_endpoint.path() + "'"); - logger.log("wineprefix: '" + - find_wineprefix().value_or("").string() + "'"); logger.log(""); // It's very important that these sockets are connected to in the same From 67c3e151d88b4271a0959394e18f834d8ed002c4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 30 Apr 2020 18:45:33 +0200 Subject: [PATCH 13/13] Choose a host application by parsing PE headers Now yabridge can load both 64 and 32 bit plugins without having to specify which are which. --- src/plugin/host-bridge.cpp | 90 ++++++++++++++++++++++++++++++++------ src/plugin/host-bridge.h | 20 +++++++-- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index 9773f09a..e8a07531 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -49,7 +49,8 @@ constexpr char alphanumeric_characters[] = std::string create_logger_prefix(const fs::path& socket_path); fs::path find_vst_plugin(); -fs::path find_wine_vst_host(); +PluginArchitecture find_plugin_architecture(fs::path); +fs::path find_wine_vst_host(PluginArchitecture plugin_arch); std::optional find_wineprefix(); fs::path generate_endpoint_name(); bp::environment set_wineprefix(); @@ -70,8 +71,9 @@ HostBridge& get_bridge_instance(const AEffect& plugin) { } HostBridge::HostBridge(audioMasterCallback host_callback) - : vst_host_path(find_wine_vst_host()), - vst_plugin_path(find_vst_plugin()), + : vst_plugin_path(find_vst_plugin()), + vst_plugin_arch(find_plugin_architecture(vst_plugin_path)), + vst_host_path(find_wine_vst_host(vst_plugin_arch)), // All the fields should be zero initialized because // `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin // bridge will crash otherwise @@ -491,8 +493,8 @@ std::string create_logger_prefix(const fs::path& socket_path) { } /** - * Finds the Wine VST hsot (named `yabridge-host.exe`). For this we will search - * in two places: + * Finds the Wine VST hsot (either `yabridge-host.exe` or `yabridge-host.exe` + * depending on the plugin). For this we will search in two places: * * 1. Alongside libyabridge.so if the file got symlinked. This is useful * when developing, as you can simply symlink the the libyabridge.so @@ -500,25 +502,31 @@ std::string create_logger_prefix(const fs::path& socket_path) { * /usr. * 2. In the regular search path. * + * @param plugin_arch The architecture of the plugin, either 64-bit or 32-bit. + * Used to determine which host application to use, if available. + * * @return The a path to the VST host, if found. * @throw std::runtime_error If the Wine VST host could not be found. */ -fs::path find_wine_vst_host() { +fs::path find_wine_vst_host(PluginArchitecture plugin_arch) { + auto host_name = yabridge_wine_host_name; + if (plugin_arch == PluginArchitecture::vst_32) { + host_name = yabridge_wine_host_name_32bit; + } + fs::path host_path = fs::canonical(boost::dll::this_line_location()).remove_filename() / - yabridge_wine_host_name; + host_name; if (fs::exists(host_path)) { return host_path; } - // TODO: First, check whether the plugin is 32-bit or 64-bit, and then - // search for the correct binary accordingly // Bosot will return an empty path if the file could not be found in the // search path - const fs::path vst_host_path = bp::search_path(yabridge_wine_host_name); + const fs::path vst_host_path = bp::search_path(host_name); if (vst_host_path == "") { - throw std::runtime_error("Could not locate '" + - std::string(yabridge_wine_host_name) + "'"); + throw std::runtime_error("Could not locate '" + std::string(host_name) + + "'"); } return vst_host_path; @@ -568,10 +576,66 @@ fs::path find_vst_plugin() { "VST plugin .dll file."); } - // Also resolve symlinks here, mostly for development purposes + // Also resolve symlinks here return fs::canonical(plugin_path); } +/** + * Determine the architecture of a VST plugin (or rather, a .dll file) based on + * it's header values. + * + * See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for more + * information on the PE32 format. + * + * @param plugin_path The path to the .dll file we're going to check. + * + * @return The detected architecture. + * @throw std::runtime_error If the file is not a .dll file. + */ +PluginArchitecture find_plugin_architecture(fs::path plugin_path) { + std::ifstream file(plugin_path, std::ifstream::binary | std::ifstream::in); + + // The linker will place the offset where the PE signature is placed at the + // end of the MS-DOS stub, at this offset + uint32_t pe_signature_offset; + file.seekg(0x3c); + file.read(reinterpret_cast(&pe_signature_offset), + sizeof(pe_signature_offset)); + + // The PE32 signature will be followed by a magic number. + // file >> pe_signature_offset; + uint32_t pe_signature; + uint16_t machine_type; + file.seekg(pe_signature_offset); + file.read(reinterpret_cast(&pe_signature), sizeof(pe_signature)); + file.read(reinterpret_cast(&machine_type), sizeof(machine_type)); + + constexpr char expected_pe_signature[4] = {'P', 'E', '\0', '\0'}; + if (pe_signature != + *reinterpret_cast(expected_pe_signature)) { + throw std::runtime_error("'" + plugin_path.string() + + "' is not a valid .dll file"); + } + + // These constants are specified in + // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types + switch (machine_type) { + case 0x014c: // IMAGE_FILE_MACHINE_I386 + return PluginArchitecture::vst_32; + break; + case 0x8664: // IMAGE_FILE_MACHINE_AMD64 + case 0x0000: // IMAGE_FILE_MACHINE_UNKNOWN + return PluginArchitecture::vst_64; + break; + default: + throw std::runtime_error( + "'" + plugin_path.string() + + "' does not have a supported architecture: " + + std::to_string(machine_type)); + break; + } +} + /** * Generate a unique name for the Unix domain socket endpoint based on the VST * plugin's name. This will also generate the parent directory if it does not diff --git a/src/plugin/host-bridge.h b/src/plugin/host-bridge.h index 26c65fa3..4b4ed5e3 100644 --- a/src/plugin/host-bridge.h +++ b/src/plugin/host-bridge.h @@ -45,6 +45,12 @@ class patched_async_pipe : public boost::process::async_pipe { typedef typename handle_type::executor_type executor_type; }; +/** + * A tag to differentiate between 32 and 64-bit plugins, used to determine which + * host application to use. + */ +enum class PluginArchitecture { vst_32, vst_64 }; + /** * This handles the communication between the Linux native VST plugin and the * Wine VST host. The functions below should be used as callback functions in an @@ -90,14 +96,20 @@ class HostBridge { float get_parameter(AEffect* plugin, int index); void set_parameter(AEffect* plugin, int index, float value); - /** - * The path to `yabridge-host.exe`. - */ - const boost::filesystem::path vst_host_path; /** * The path to the .dll being loaded in the Wine VST host. */ const boost::filesystem::path vst_plugin_path; + /** + * Whether the plugin is 64-bit or 32-bit. + */ + const PluginArchitecture vst_plugin_arch; + /** + * The path to the host application (i.e. a path to either + * `yabridge-host.exe` or `yabridge-host-32.exe`). The host application will + * be chosen depending on the architecture of the VST plugin .dll file. + */ + const boost::filesystem::path vst_host_path; /** * This AEffect struct will be populated using the data passed by the Wine