// Copyright 2021 The Mumble Developers. All rights reserved. // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file at the root of the // Mumble source tree or at . #include "LegacyPlugin.h" #include "MumblePlugin_v_1_0_x.h" #include #include #include #include #include #include #include /// A regular expression used to extract the version from the legacy plugin's description static const QRegularExpression versionRegEx(QString::fromLatin1("(?:v)?(?:ersion)?[ \\t]*(\\d+)\\.(\\d+)(?:\\.(\\d+))?"), QRegularExpression::CaseInsensitiveOption); LegacyPlugin::LegacyPlugin(QString path, bool isBuiltIn, QObject *p) : Plugin(path, isBuiltIn, p), m_name(), m_description(), m_version(VERSION_UNKNOWN), m_mumPlug(0), m_mumPlug2(0), m_mumPlugQt(0) { } LegacyPlugin::~LegacyPlugin() { } bool LegacyPlugin::doInitialize() { if (Plugin::doInitialize()) { // initialization seems to have succeeded so far // This means that mumPlug is initialized m_name = QString::fromStdWString(m_mumPlug->shortname); // Although the MumblePlugin struct has a member called "description", the actual description seems to // always only be returned by the longdesc function (The description member is actually just the name with some // version info) m_description = QString::fromStdWString(m_mumPlug->longdesc()); // The version field in the MumblePlugin2 struct is the positional-audio-plugin-API version and not the version // of the plugin itself. This information is not provided for legacy plugins. // Most of them however provide information about the version of the game they support. Thus we will try to // parse the description and extract this version using it for the plugin's version as well. Some plugins have // the version in the actual description field of the old API (see above comment why these aren't the same) so // we will use a combination of both to search for the version. If multiple version(-like) strings are found, // the last one will be used. QString matchContent = m_description + QChar::Null + QString::fromStdWString(m_mumPlug->description); QRegularExpressionMatchIterator matchIt = versionRegEx.globalMatch(matchContent); // Only consider the last match QRegularExpressionMatch match; while (matchIt.hasNext()) { match = matchIt.next(); } if (match.hasMatch()) { // Store version m_version = { match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt() }; } return true; } else { // initialization has failed // pass on info about failed init return false; } } void LegacyPlugin::resolveFunctionPointers() { // We don't set any functions inside the apiFnc struct variable in order for the default // implementations in the Plugin class to mimic empty default implementations for all functions // not explicitly overwritten by this class if (isValid()) { // The corresponding library was loaded -> try to locate all API functions of the legacy plugin's spec // (for positional audio) and set defaults for the other ones in order to maintain compatibility with // the new plugin system QWriteLocker lock(&m_pluginLock); mumblePluginFunc pluginFunc = reinterpret_cast< mumblePluginFunc >(m_lib.resolve("getMumblePlugin")); mumblePlugin2Func plugin2Func = reinterpret_cast< mumblePlugin2Func >(m_lib.resolve("getMumblePlugin2")); mumblePluginQtFunc pluginQtFunc = reinterpret_cast< mumblePluginQtFunc >(m_lib.resolve("getMumblePluginQt")); if (pluginFunc) { m_mumPlug = pluginFunc(); } if (plugin2Func) { m_mumPlug2 = plugin2Func(); } if (pluginQtFunc) { m_mumPlugQt = pluginQtFunc(); } // A legacy plugin is valid as long as there is a function to get the MumblePlugin struct from it // and the plugin has been compiled by the same compiler as this client (determined by the plugin's // "magic") and it isn't retracted bool suitableMagic = m_mumPlug && m_mumPlug->magic == MUMBLE_PLUGIN_MAGIC; bool retracted = m_mumPlug && m_mumPlug->shortname == L"Retracted"; m_pluginIsValid = pluginFunc && suitableMagic && !retracted; #ifdef MUMBLE_PLUGIN_DEBUG if (!m_pluginIsValid) { if (!pluginFunc) { qDebug("Plugin \"%s\" is missing the getMumblePlugin() function", qPrintable(m_pluginPath)); } else if (!suitableMagic) { qDebug("Plugin \"%s\" was compiled with a different compiler (magic differs)", qPrintable(m_pluginPath)); } else { qDebug("Plugin \"%s\" is retracted", qPrintable(m_pluginPath)); } } #endif } } mumble_error_t LegacyPlugin::init() { { QWriteLocker lock(&m_pluginLock); m_pluginIsLoaded = true; } // No-op as legacy plugins never have anything to initialize // The only init function they care about is the one that inits positional audio return STATUS_OK; } QString LegacyPlugin::getName() const { PluginReadLocker lock(&m_pluginLock); if (!m_name.isEmpty()) { return m_name; } else { return QString::fromLatin1(""); } } QString LegacyPlugin::getDescription() const { PluginReadLocker lock(&m_pluginLock); if (!m_description.isEmpty()) { return m_description; } else { return QString::fromLatin1(""); } } bool LegacyPlugin::showAboutDialog(QWidget *parent) const { if (m_mumPlugQt && m_mumPlugQt->about) { m_mumPlugQt->about(parent); return true; } if (m_mumPlug->about) { // the original implementation in Mumble would pass nullptr to the about-function in the mumPlug struct // so we'll mimic that behaviour for compatibility m_mumPlug->about(nullptr); return true; } return false; } bool LegacyPlugin::showConfigDialog(QWidget *parent) const { if (m_mumPlugQt && m_mumPlugQt->config) { m_mumPlugQt->config(parent); return true; } if (m_mumPlug->config) { // the original implementation in Mumble would pass nullptr to the about-function in the mumPlug struct // so we'll mimic that behaviour for compatibility m_mumPlug->config(nullptr); return true; } return false; } uint8_t LegacyPlugin::initPositionalData(const char *const *programNames, const uint64_t *programPIDs, size_t programCount) { int retCode; if (m_mumPlug2) { // Create and populate a multimap holding the names and PIDs to pass to the tryLock-function std::multimap< std::wstring, unsigned long long int > pidMap; for (size_t i = 0; i < programCount; i++) { std::string currentName = programNames[i]; std::wstring currentNameWstr = std::wstring_convert< std::codecvt_utf8< wchar_t > >().from_bytes(currentName); pidMap.insert(std::pair< std::wstring, unsigned long long int >(currentNameWstr, programPIDs[i])); } retCode = m_mumPlug2->trylock(pidMap); } else { // The default MumblePlugin doesn't take the name and PID arguments retCode = m_mumPlug->trylock(); } // ensure that only expected return codes are being returned from this function // the legacy plugins return 1 on successfull locking and 0 on failure if (retCode) { QWriteLocker wLock(&m_pluginLock); m_positionalDataIsActive = true; return PDEC_OK; } else { // legacy plugins don't have the concept of indicating a permanent error // so we'll return a temporary error for them return PDEC_ERROR_TEMP; } } bool LegacyPlugin::fetchPositionalData(Position3D &avatarPos, Vector3D &avatarDir, Vector3D &avatarAxis, Position3D &cameraPos, Vector3D &cameraDir, Vector3D &cameraAxis, QString &context, QString &identity) const { std::wstring identityWstr; std::string contextStr; int retCode = m_mumPlug->fetch(static_cast< float * >(avatarPos), static_cast< float * >(avatarDir), static_cast< float * >(avatarAxis), static_cast< float * >(cameraPos), static_cast< float * >(cameraDir), static_cast< float * >(cameraAxis), contextStr, identityWstr); context = QString::fromStdString(contextStr); identity = QString::fromStdWString(identityWstr); // The fetch-function should return if it is "still locked on" meaning that it can continue providing // positional audio return retCode == 1; } void LegacyPlugin::shutdownPositionalData() { QWriteLocker lock(&m_pluginLock); m_positionalDataIsActive = false; m_mumPlug->unlock(); } uint32_t LegacyPlugin::getFeatures() const { return FEATURE_POSITIONAL; } mumble_version_t LegacyPlugin::getVersion() const { return m_version; } bool LegacyPlugin::providesAboutDialog() const { return m_mumPlug->about || (m_mumPlugQt && m_mumPlugQt->about); } bool LegacyPlugin::providesConfigDialog() const { return m_mumPlug->config || (m_mumPlugQt && m_mumPlugQt->config); } mumble_version_t LegacyPlugin::getAPIVersion() const { // Legacy plugins are always on most recent API as they don't use it in any case -> no need to perform // backwards compatibility stuff return MUMBLE_PLUGIN_API_VERSION; }