1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
// 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 <https://www.mumble.info/LICENSE>.
#include "LegacyPlugin.h"
#include "MumblePlugin_v_1_0_x.h"
#include <cstdlib>
#include <wchar.h>
#include <map>
#include <string.h>
#include <codecvt>
#include <locale>
#include <QRegularExpression>
/// 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("<Unknown Legacy Plugin>");
}
}
QString LegacyPlugin::getDescription() const {
PluginReadLocker lock(&m_pluginLock);
if (!m_description.isEmpty()) {
return m_description;
} else {
return QString::fromLatin1("<No description provided by the legacy plugin>");
}
}
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;
}
|