diff --git a/src/wine-host/host.cpp b/src/wine-host/host.cpp
new file mode 100644
index 00000000..5961f4d1
--- /dev/null
+++ b/src/wine-host/host.cpp
@@ -0,0 +1,210 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 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
+
+// Generated inside of the build directory
+#include
+#include
+
+#include "../common/utils.h"
+#include "bridges/group.h"
+#include "bridges/vst2.h"
+#ifdef WITH_VST3
+#include "bridges/vst3.h"
+#endif
+
+static const std::string host_name = "yabridge host version " +
+ std::string(yabridge_git_version)
+#ifdef __i386__
+ + " (32-bit compatibility mode)"
+#endif
+ ;
+
+/**
+ * This is the universal plugin host application. This can either load an
+ * individual plugin, or spawn a group host server This can either load a
+ * plugin, or spawn a group host server.
+ *
+ * For the individual plugin situation this process will load the specified
+ * plugin plugin, and then connect back to the `libyabridge-{vst2,vst3}.so`
+ * instance that spawned this over the socket.
+ *
+ * For the group host case this process will act as a daemon yabridge plugin
+ * binaries can connect to and request it to host plugins for them. After this
+ * host request everything works exactly the same as with individually hosted
+ * plugins.
+ */
+int YABRIDGE_EXPORT
+#ifdef WINE_USE_CDECL
+ __cdecl
+#endif
+ main(int argc, char* argv[]) {
+ // For individually hosted plugins we'll pass along the plugin format, the
+ // name of the VST2 plugin .dll file or VST3 bundle to load, the base
+ // directory for the Unix domain socket endpoints to connect to and the
+ // process ID of the process the native plugin is being hosted in as
+ // arguments for yabridge-host.exe. Group host processes receive only a unix
+ // domain socket it should listen on.
+ const bool is_group_host = (argc >= 3 && strcmp(argv[1], "group") == 0);
+ if (!(is_group_host || argc >= 5)) {
+ std::cerr << host_name << std::endl;
+ std::cerr << "Usage: "
+#ifdef __i386__
+ << yabridge_host_name_32bit
+#else
+ << yabridge_host_name
+#endif
+ << " "
+ " "
+ << std::endl;
+ std::cerr << " "
+#ifdef __i386__
+ << yabridge_host_name_32bit
+#else
+ << yabridge_host_name
+#endif
+ << " group " << std::endl;
+
+ return 1;
+ }
+ // asdf
+
+ std::cerr << "Initializing " << host_name << std::endl;
+
+ // NOTE: Some plugins use Microsoft COM, but don't initialize it first and
+ // just pray the host does it for them. Examples of this are
+ // PSPaudioware's InfiniStrip and Shattered Glass Audio Code Red Free.
+ OleInitialize(nullptr);
+
+ // The first argument is either a plugin type or 'group', in which case
+ // we'll spawn a plugin group host process. In the past this was a separate
+ // binary, but they have been merged since they share 95% of the same code.
+ if (is_group_host) {
+ const std::string group_socket_endpoint_path(argv[2]);
+
+ try {
+ GroupBridge bridge(group_socket_endpoint_path);
+
+ // Blocks the main thread until all plugins have exited
+ bridge.handle_incoming_connections();
+ } catch (const std::system_error& error) {
+ // If another process is already listening on the socket, we'll just
+ // print a message and exit quietly. This could happen if the host
+ // starts multiple yabridge instances that all use the same plugin
+ // group at the same time. The same error is also used if we could
+ // not create a pipe. Since that error is so rare, we'll just print
+ // to STDERR before that happens to differentiate the two cases.
+ std::cerr << "Another process is already listening on this group's "
+ "socket, connecting to the existing process:"
+ << std::endl;
+ std::cerr << error.what() << std::endl;
+
+ return 0;
+ }
+
+ // This shouldn't be needed, but sometimes with Wine background threads
+ // will be kept alive while this process exits
+ TerminateProcess(GetCurrentProcess(), 0);
+ } else {
+ const std::string plugin_type_str(argv[1]);
+ const PluginType plugin_type = plugin_type_from_string(plugin_type_str);
+ const std::string plugin_location(argv[2]);
+ const std::string socket_endpoint_path(argv[3]);
+ const pid_t parent_pid = std::stoi(argv[4]);
+
+ std::cerr << "Preparing to load " << plugin_type_to_string(plugin_type)
+ << " plugin at '" << plugin_location << "'" << std::endl;
+
+ // As explained in `Vst2Bridge`, the plugin has to be initialized in the
+ // same thread as the one that calls `io_context.run()`. This setup is
+ // slightly more convoluted than it has to be, but doing it this way we
+ // don't need to differentiate between individually hosted plugins and
+ // plugin groups when it comes to event handling.
+ MainContext main_context{};
+ std::unique_ptr bridge;
+ try {
+ switch (plugin_type) {
+ case PluginType::vst2:
+ bridge = std::make_unique(
+ main_context, plugin_location, socket_endpoint_path,
+ parent_pid);
+ break;
+ case PluginType::vst3:
+#ifdef WITH_VST3
+ bridge = std::make_unique(
+ main_context, plugin_location, socket_endpoint_path,
+ parent_pid);
+#else
+ std::cerr
+ << "This version of yabridge has not been compiled "
+ "with VST3 support"
+ << std::endl;
+ return 1;
+#endif
+ break;
+ case PluginType::unknown:
+ std::cerr << "Unknown plugin type '" << plugin_type_str
+ << "'" << std::endl;
+ return 1;
+ break;
+ };
+ } catch (const std::exception& error) {
+ std::cerr << "Error while initializing the Wine plugin host:"
+ << std::endl;
+ std::cerr << error.what() << std::endl;
+
+ // See below, just returning from `main()` isn't enough to terminate
+ // the process
+ TerminateProcess(GetCurrentProcess(), 0);
+
+ return 1;
+ }
+
+ // Let the plugin receive and handle its events on its own thread. Some
+ // potentially unsafe events that should always be run from the UI
+ // thread will be posted to `main_context`.
+ Win32Thread worker_thread([&]() {
+ pthread_setname_np(pthread_self(), "worker");
+
+ bridge->run();
+
+ // // When the sockets get closed, this application should
+ // // terminate gracefully
+ // main_context.stop();
+ // FIXME: So some of the background threads spawned by the plugin
+ // may get stuck if the host got terminated abruptly. After
+ // an entire day of debugging I still have no idea whether
+ // this is a bug in yabridge, Wine, or those plugins, but
+ // just killing off this process and all of its threads
+ // 'fixes' the issue.
+ //
+ // https://github.com/robbert-vdh/yabridge/issues/69
+ TerminateProcess(GetCurrentProcess(), 0);
+ });
+
+ std::cerr << "Finished initializing '" << plugin_location << "'"
+ << std::endl;
+
+ // Handle Win32 messages and X11 events on a timer, just like in
+ // `GroupBridge::async_handle_events()``
+ main_context.async_handle_events(
+ [&]() { bridge->handle_events(); },
+ [&]() { return !bridge->inhibits_event_loop(); });
+ main_context.run();
+ }
+}