pluginloader.cpp
1 //------------------------------------------------------------------------------
2 // pluginloader.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2011 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "pluginloader.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "ini/inisection.h"
27 #include "ini/inivariable.h"
28 #include "log.h"
29 #include "plugins/engineplugin.h"
30 #include "serverapi/masterclient.h"
31 #include "strings.hpp"
32 #include <cassert>
33 #include <QDir>
34 
35 #ifdef Q_OS_WIN32
36  #include <windows.h>
37 #define dlopen(a, b) LoadLibrary(a)
38 #define dlsym(a, b) GetProcAddress(a, b)
39 #define dlclose(a) FreeLibrary(a)
40 #define dlerror() PluginLoader::Plugin::getDllWindowsErrorMessage()
41  #ifdef _MSC_VER
42  #pragma warning(disable: 4251)
43  #endif
44 #else
45  #include <dirent.h>
46  #include <dlfcn.h>
47 #endif
48 
50 DClass<PluginLoader::Plugin>
51 {
52 public:
53  EnginePlugin *info;
54  QString file;
55  #ifdef Q_OS_WIN32
56  HMODULE library;
57  #else
58  void *library;
59  #endif
60 
61  static bool isForbiddenPlugin(QString file)
62  {
63  return QFileInfo(file).fileName().toLower().contains("vavoom");
64  }
65 };
66 
67 DPointered(PluginLoader::Plugin)
68 
69 PluginLoader::Plugin::Plugin(unsigned int type, QString file)
70 {
71  Q_UNUSED(type); // THIS SHOULD BE REVIEWED. TYPE SEEMS USELESS.
72  d->file = file;
73  d->library = nullptr;
74  d->info = nullptr;
76  {
77  gLog << QObject::tr("Skipping loading of forbidden plugin: %1").arg(file);
78  return;
79  }
80 
81  // Load the library
82  #ifdef Q_OS_WIN32
83  UINT oldErrorMode = SetErrorMode(0);
84  SetErrorMode(oldErrorMode | SEM_FAILCRITICALERRORS);
85  #endif
86  d->library = dlopen(d->file.toUtf8().constData(), RTLD_NOW);
87  #ifdef Q_OS_WIN32
88  SetErrorMode(oldErrorMode);
89  #endif
90 
91  if (d->library != nullptr)
92  {
93  auto doomSeekerABI = (unsigned int (*)())(dlsym(d->library, "doomSeekerABI"));
94  if (!doomSeekerABI || doomSeekerABI() != DOOMSEEKER_ABI_VERSION)
95  {
96  // Unsupported version
97  QString reason;
98  if (doomSeekerABI != nullptr)
99  {
100  reason = QObject::tr(
101  "plugin ABI version mismatch; plugin: %1, Doomseeker: %2").arg(
102  doomSeekerABI()).arg(DOOMSEEKER_ABI_VERSION);
103  }
104  else
105  {
106  reason = QObject::tr("plugin doesn't report its ABI version");
107  }
108  gLog << QObject::tr("Cannot load plugin %1, reason: %2.").arg(file, reason);
109  unload();
110  return;
111  }
112 
113  auto doomSeekerInit = (EnginePlugin * (*)())(dlsym(d->library, "doomSeekerInit"));
114  if (doomSeekerInit == nullptr)
115  { // This is not a valid plugin.
116  unload();
117  return;
118  }
119 
120  d->info = doomSeekerInit();
121  if (!info()->data()->valid)
122  {
123  unload();
124  return;
125  }
126 
127  gLog << QObject::tr("Loaded plugin: \"%1\"!").arg(info()->data()->name);
128  d->info->start();
129  }
130  else
131  {
132  gLog << QObject::tr("Failed to open plugin: %1").arg(file);
133  gLog << QString("Last error was: %1").arg(dlerror());
134  }
135 }
136 
137 PluginLoader::Plugin::~Plugin()
138 {
139  unload();
140 }
141 
142 void *PluginLoader::Plugin::function(const char *func) const
143 {
144  return (void *) dlsym(d->library, func);
145 }
146 
148 {
149  return d->info;
150 }
151 
152 void PluginLoader::Plugin::initConfig()
153 {
154  if (isValid())
155  {
156  IniSection cfgSection = gConfig.iniSectionForPlugin(info()->data()->name);
157  info()->setConfig(cfgSection);
158  }
159 }
160 
161 bool PluginLoader::Plugin::isValid() const
162 {
163  return d->library != nullptr;
164 }
165 
166 void PluginLoader::Plugin::unload()
167 {
168  if (d->library != nullptr)
169  {
170  dlclose(d->library);
171  d->library = nullptr;
172  }
173 }
174 
175 #ifdef Q_OS_WIN32
176 QString PluginLoader::Plugin::getDllWindowsErrorMessage()
177 {
178  QString baseErrorString("%1 (%2)");
179 
180  DWORD errorId = GetLastError();
181 
182  if (errorId == 127)
183  {
184  return baseErrorString.arg("Procedure not found. Perhaps this plugin is for a different Doomseeker version?",
185  QString::number(errorId));
186  }
187  if (errorId != 0)
188  {
189  LPVOID lpMsgBuf;
190  DWORD bufLen = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
191  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
192  (LPWSTR) &lpMsgBuf, 0, NULL );
193 
194  LPCWSTR lpMsgStr = (LPCWSTR)lpMsgBuf;
195  QString result = QString::fromWCharArray(lpMsgStr, bufLen);
196  if (result.contains("%1"))
197  {
198  // Try to cover the formatting placeholder in all WinAPI
199  // messages with a lowest common denominator fit.
200  result = result.arg("Plugin");
201  }
202  result = result.trimmed();
203 
204  LocalFree(lpMsgBuf);
205 
206  return baseErrorString.arg(result, QString::number(errorId));
207  }
208  return QString();
209 }
210 #endif
211 
213 DClass<PluginLoader>
214 {
215 public:
216  unsigned int type;
217  QString pluginsDirectory;
218  QList<PluginLoader::Plugin *> plugins;
219 };
220 
221 DPointered(PluginLoader)
222 
223 PluginLoader *PluginLoader::staticInstance = nullptr;
224 
225 PluginLoader::PluginLoader(unsigned int type, const QStringList &directories)
226 {
227  d->type = type;
228  for (const QString &dir : directories)
229  {
230  d->pluginsDirectory = dir;
231  if (filesInDir())
232  {
233  break;
234  }
235  }
236  if (numPlugins() == 0) // No plugins?!
237  {
238  gLog << QObject::tr("Failed to locate plugins.");
239  }
240 }
241 
242 PluginLoader::~PluginLoader()
243 {
244  qDeleteAll(d->plugins);
245 }
246 
248 {
249  qDeleteAll(d->plugins);
250  d->plugins.clear();
251 }
252 
254 {
255  if (staticInstance != nullptr)
256  {
257  delete staticInstance;
258  staticInstance = nullptr;
259  }
260 }
261 
262 bool PluginLoader::filesInDir()
263 {
264  gLog << QString("Attempting to load plugins from directory: %1").arg(d->pluginsDirectory);
265  QDir dir(d->pluginsDirectory);
266  if (!dir.exists())
267  {
268  return false;
269  }
270  #ifdef Q_OS_WIN32
271  QStringList windowsNamesFilter;
272  windowsNamesFilter << "*.dll";
273  dir.setNameFilters(windowsNamesFilter);
274  #endif
275  for (const QString &entry : dir.entryList(QDir::Files))
276  {
277  QString pluginFilePath = Strings::combinePaths(d->pluginsDirectory, entry);
278  Plugin *plugin = new Plugin(d->type, pluginFilePath);
279  if (plugin->isValid())
280  d->plugins << plugin;
281  else
282  delete plugin;
283  }
284  return numPlugins() != 0;
285 }
286 
287 EnginePlugin *PluginLoader::info(int pluginIndex) const
288 {
289  const Plugin *p = plugin(pluginIndex);
290  if (p != nullptr)
291  {
292  return p->info();
293  }
294  return nullptr;
295 }
296 
297 void PluginLoader::init(const QStringList &directories)
298 {
299  if (staticInstance != nullptr)
300  {
301  qDebug() << "Attempting to re-init PluginLoader";
302  assert(false);
303  return;
304  }
305  staticInstance = new PluginLoader(MAKEID('E', 'N', 'G', 'N'), directories);
306 }
307 
309 {
310  for (Plugin *plugin : d->plugins)
311  {
312  plugin->initConfig();
313  }
314 }
315 
317 {
318  assert(staticInstance != nullptr);
319  return staticInstance;
320 }
321 
322 unsigned int PluginLoader::numPlugins() const
323 {
324  return d->plugins.size();
325 }
326 
327 const QList<PluginLoader::Plugin *> &PluginLoader::plugins() const
328 {
329  return d->plugins;
330 }
331 
332 const PluginLoader::Plugin *PluginLoader::plugin(unsigned int index) const
333 {
334  return d->plugins[index];
335 }
336 
337 int PluginLoader::pluginIndexFromName(const QString &name) const
338 {
339  // Why the mangling?
340  // Ever since version 0.8.1b there was a bug that removed all spacebars
341  // from plugin names. This bug is fixed in a commit made on 2013-11-01,
342  // but the fix breaks at least some parts of configuration for plugins
343  // that have spacebars in their names. For example, all server
344  // configurations for Chocolate Doom won't load anymore. To prevent that,
345  // we need to treat spacebars as non-existent here. Simply put:
346  // "Chocolate Doom" == "ChocolateDoom"
347  QString mangledName = QString(name).replace(" ", "");
348  for (int i = 0; i < d->plugins.size(); ++i)
349  {
350  QString mangledCandidate = QString(d->plugins[i]->info()->data()->name).replace(" ", "");
351  if (mangledName.compare(mangledCandidate) == 0)
352  {
353  return i;
354  }
355  }
356 
357  return -1;
358 }
359 
360 void PluginLoader::resetPluginsDirectory(const QString &pluginsDirectory)
361 {
362  d->pluginsDirectory = pluginsDirectory;
363  clearPlugins();
364  filesInDir();
365 }
366 
367 const PluginLoader::Plugin *PluginLoader::operator[] (unsigned int index) const
368 {
369  return d->plugins[index];
370 }
static QString combinePaths(QString pathFront, QString pathEnd)
Definition: strings.cpp:147
void resetPluginsDirectory(const QString &pluginsDirectory)
Resets the plugins directory, clearing the loaded plugins and getting new loaded plugins in the proce...
unsigned int numPlugins() const
Gets the number of loaded plugins.
const Plugin * plugin(unsigned int index) const
Returns the requested plugin or nullptr.
void clearPlugins()
Clears the plugins list.
int pluginIndexFromName(const QString &name) const
Looks for a plugin which name equals to parameter.
EnginePlugin * info(int pluginIndex) const
Convenience method - calls Plugin::info() for specified plugin.
static void deinit()
Destroys the init() instance.
static void init(const QStringList &directories)
Attempts to load plugins from given set of directories.
static PluginLoader * instance()
Accesses instance of the class after init().
void initConfig()
Inits configuration for plugins.
EnginePlugin * info() const
Main plugin interface.
const Plugin * operator[](unsigned int index) const
Returns the requested plugin or nullptr.
Definition: dptr.h:31
INI section representation.
Definition: inisection.h:40
Plugin(unsigned int type, QString file)
Inits a plugin.
void * function(const char *func) const
Returns a pointer to the requested function or nullptr.