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 #include <QLibrary>
35 #include <QScopedPointer>
36 
37 /*
38  PluginLoader::Plugin
39 */
40 DClass<PluginLoader::Plugin>
41 {
42 public:
43  EnginePlugin *info;
44  QString file;
45  QScopedPointer<QLibrary> library;
46 };
47 
48 DPointeredNoCopy(PluginLoader::Plugin)
49 
50 PluginLoader::Plugin::Plugin(unsigned int type, QString file)
51 {
52  Q_UNUSED(type); // THIS SHOULD BE REVIEWED. TYPE SEEMS USELESS.
53  d->file = file;
54  d->info = nullptr;
55 
56  // Load the library
57  d->library.reset(new QLibrary(file));
58  if (d->library->load())
59  {
60  auto doomSeekerABI = (unsigned int (*)())(d->library->resolve("doomSeekerABI"));
61  if (!doomSeekerABI || doomSeekerABI() != DOOMSEEKER_ABI_VERSION)
62  {
63  // Unsupported version
64  QString reason;
65  if (doomSeekerABI != nullptr)
66  {
67  reason = QObject::tr(
68  "plugin ABI version mismatch; plugin: %1, Doomseeker: %2").arg(
69  doomSeekerABI()).arg(DOOMSEEKER_ABI_VERSION);
70  }
71  else
72  {
73  reason = QObject::tr("plugin doesn't report its ABI version");
74  }
75  gLog << QObject::tr("Cannot load plugin %1, reason: %2.").arg(file, reason);
76  unload();
77  return;
78  }
79 
80  auto doomSeekerInit = (EnginePlugin * (*)())(d->library->resolve("doomSeekerInit"));
81  if (doomSeekerInit == nullptr)
82  { // This is not a valid plugin.
83  unload();
84  return;
85  }
86 
87  d->info = doomSeekerInit();
88  if (!info()->data()->valid)
89  {
90  unload();
91  return;
92  }
93 
94  gLog << QObject::tr("Loaded plugin: \"%1\"!").arg(info()->data()->name);
95  d->info->start();
96  }
97  else
98  {
99  gLog << QObject::tr("Failed to open plugin: %1").arg(file);
100  gLog << QString("Last error was: %1").arg(d->library->errorString());
101  }
102 }
103 
104 PluginLoader::Plugin::~Plugin()
105 {
106  unload();
107 }
108 
109 void *PluginLoader::Plugin::function(const char *func) const
110 {
111  return (void *) d->library->resolve(func);
112 }
113 
115 {
116  return d->info;
117 }
118 
119 void PluginLoader::Plugin::initConfig()
120 {
121  if (isValid())
122  {
123  IniSection cfgSection = gConfig.iniSectionForPlugin(info()->data()->name);
124  info()->setConfig(cfgSection);
125  }
126 }
127 
128 bool PluginLoader::Plugin::isValid() const
129 {
130  return d->library != nullptr && d->library->isLoaded();
131 }
132 
133 void PluginLoader::Plugin::unload()
134 {
135  d->library.reset();
136 }
137 
138 /*
139  PluginLoader
140 */
141 
142 DClass<PluginLoader>
143 {
144 public:
145  unsigned int type;
146  QMap<QString, bool> pluginsEnabled;
147  QString pluginsDirectory;
148  QList<PluginLoader::Plugin *> plugins;
149 
150  bool isForbiddenPlugin(const QString &file) const
151  {
152  static const QString PREFIX = "lib";
153  QString name = QFileInfo(file).baseName().toLower();
154  if (name.startsWith(PREFIX))
155  name = name.mid(PREFIX.size());
156  if (pluginsEnabled.contains(name))
157  return !pluginsEnabled[name];
158  return name.contains("srb2")
159  || name.contains("vavoom");
160  }
161 };
162 
163 DPointeredNoCopy(PluginLoader)
164 
165 PluginLoader *PluginLoader::staticInstance = nullptr;
166 
167 PluginLoader::PluginLoader(unsigned int type, const QStringList &directories,
168  const QMap<QString, bool> &pluginsEnabled)
169 {
170  d->type = type;
171  d->pluginsEnabled = pluginsEnabled;
172  for (const QString &dir : directories)
173  {
174  d->pluginsDirectory = dir;
175  if (filesInDir())
176  {
177  break;
178  }
179  }
180  if (numPlugins() == 0) // No plugins?!
181  {
182  gLog << QObject::tr("Failed to locate plugins.");
183  }
184 }
185 
186 PluginLoader::~PluginLoader()
187 {
188  qDeleteAll(d->plugins);
189 }
190 
192 {
193  qDeleteAll(d->plugins);
194  d->plugins.clear();
195 }
196 
198 {
199  if (staticInstance != nullptr)
200  {
201  delete staticInstance;
202  staticInstance = nullptr;
203  }
204 }
205 
206 bool PluginLoader::filesInDir()
207 {
208  gLog << QString("Attempting to load plugins from directory: %1").arg(d->pluginsDirectory);
209  QDir dir(d->pluginsDirectory);
210  if (!dir.exists())
211  {
212  return false;
213  }
214  #ifdef Q_OS_WIN32
215  QStringList windowsNamesFilter;
216  windowsNamesFilter << "*.dll";
217  dir.setNameFilters(windowsNamesFilter);
218  #endif
219  for (const QString &entry : dir.entryList(QDir::Files))
220  {
221  QString pluginFilePath = dir.filePath(entry);
222  if (d->isForbiddenPlugin(pluginFilePath))
223  {
224  gLog << QObject::tr("Skipping loading of forbidden plugin: %1").arg(pluginFilePath);
225  continue;
226  }
227 
228  Plugin *plugin = new Plugin(d->type, pluginFilePath);
229  if (plugin->isValid())
230  d->plugins << plugin;
231  else
232  delete plugin;
233  }
234  return numPlugins() != 0;
235 }
236 
237 EnginePlugin *PluginLoader::info(int pluginIndex) const
238 {
239  const Plugin *p = plugin(pluginIndex);
240  if (p != nullptr)
241  {
242  return p->info();
243  }
244  return nullptr;
245 }
246 
247 void PluginLoader::init(const QStringList &directories, const QMap<QString, bool> &pluginsEnabled)
248 {
249  if (staticInstance != nullptr)
250  {
251  qDebug() << "Attempting to re-init PluginLoader";
252  assert(false);
253  return;
254  }
255  staticInstance = new PluginLoader(MAKEID('E', 'N', 'G', 'N'), directories, pluginsEnabled);
256 }
257 
259 {
260  for (Plugin *plugin : d->plugins)
261  {
262  plugin->initConfig();
263  }
264 }
265 
267 {
268  assert(staticInstance != nullptr);
269  return staticInstance;
270 }
271 
272 unsigned int PluginLoader::numPlugins() const
273 {
274  return d->plugins.size();
275 }
276 
277 const QList<PluginLoader::Plugin *> &PluginLoader::plugins() const
278 {
279  return d->plugins;
280 }
281 
282 const PluginLoader::Plugin *PluginLoader::plugin(unsigned int index) const
283 {
284  return d->plugins[index];
285 }
286 
287 int PluginLoader::pluginIndexFromName(const QString &name) const
288 {
289  // Why the mangling?
290  // Ever since version 0.8.1b there was a bug that removed all spacebars
291  // from plugin names. This bug is fixed in a commit made on 2013-11-01,
292  // but the fix breaks at least some parts of configuration for plugins
293  // that have spacebars in their names. For example, all server
294  // configurations for Chocolate Doom won't load anymore. To prevent that,
295  // we need to treat spacebars as non-existent here. Simply put:
296  // "Chocolate Doom" == "ChocolateDoom"
297  QString mangledName = QString(name).replace(" ", "");
298  for (int i = 0; i < d->plugins.size(); ++i)
299  {
300  QString mangledCandidate = QString(d->plugins[i]->info()->data()->name).replace(" ", "");
301  if (mangledName.compare(mangledCandidate) == 0)
302  {
303  return i;
304  }
305  }
306 
307  return -1;
308 }
309 
310 void PluginLoader::resetPluginsDirectory(const QString &pluginsDirectory)
311 {
312  d->pluginsDirectory = pluginsDirectory;
313  clearPlugins();
314  filesInDir();
315 }
316 
317 const PluginLoader::Plugin *PluginLoader::operator[] (unsigned int index) const
318 {
319  return d->plugins[index];
320 }