1 /*
2 * Plugins.cpp - plugin manager
3
4 Copyright (C) 2009 and beyond by Gregory Smith
5 and the "Aleph One" developers.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 This license is contained in the file "COPYING",
18 which is included with this source code; it is available online at
19 http://www.gnu.org/licenses/gpl.html
20
21 */
22
23 #include "cseries.h"
24 #include "Plugins.h"
25
26 #include <algorithm>
27
28 #include "alephversion.h"
29 #include "FileHandler.h"
30 #include "game_errors.h"
31 #include "Logging.h"
32 #include "preferences.h"
33 #include "InfoTree.h"
34 #include "XML_ParseTreeRoot.h"
35 #include "Scenario.h"
36
37 #ifdef HAVE_ZZIP
38 #include <zzip/lib.h>
39 #endif
40
41 #include <boost/algorithm/string/predicate.hpp>
42
43 namespace algo = boost::algorithm;
44
45 class PluginLoader {
46 public:
PluginLoader()47 PluginLoader() { }
~PluginLoader()48 ~PluginLoader() { }
49
50 bool ParsePlugin(FileSpecifier& file);
51 bool ParseDirectory(FileSpecifier& dir);
52 };
53
compatible() const54 bool Plugin::compatible() const {
55 if (required_version.size() > 0 && A1_DATE_VERSION < required_version)
56 return false;
57
58 if (required_scenarios.size() == 0)
59 return true;
60 std::string scenName = Scenario::instance()->GetName();
61 std::string scenID = Scenario::instance()->GetID();
62 std::string scenVers = Scenario::instance()->GetVersion();
63 for (std::vector<ScenarioInfo>::const_iterator it = required_scenarios.begin(); it != required_scenarios.end(); ++it)
64 {
65 if ((it->name.empty() || it->name == scenName) &&
66 (it->scenario_id.empty() || it->scenario_id == scenID) &&
67 (it->version.empty() || it->version == scenVers))
68 return true;
69 }
70 return false;
71 }
allowed() const72 bool Plugin::allowed() const {
73 if (stats_lua.empty() || network_preferences->allow_stats)
74 return true;
75
76 return false;
77 }
valid() const78 bool Plugin::valid() const {
79 if (!enabled)
80 return false;
81
82 if (!environment_preferences->use_solo_lua &&
83 Plugins::instance()->mode() == Plugins::kMode_Solo)
84 return !overridden_solo;
85
86 return !overridden;
87 }
88
instance()89 Plugins* Plugins::instance() {
90 static Plugins* m_instance = nullptr;
91 if (!m_instance) {
92 m_instance = new Plugins;
93 }
94
95 return m_instance;
96 }
97
disable(const std::string & path)98 void Plugins::disable(const std::string& path) {
99 for (std::vector<Plugin>::iterator it = m_plugins.begin(); it != m_plugins.end(); ++it) {
100 if (it->directory == path) {
101 it->enabled = false;
102 m_validated = false;
103 return;
104 }
105 }
106 }
107
load_mmls(const Plugin & plugin)108 static void load_mmls(const Plugin& plugin)
109 {
110 ScopedSearchPath ssp(plugin.directory);
111 for (std::vector<std::string>::const_iterator it = plugin.mmls.begin(); it != plugin.mmls.end(); ++it)
112 {
113 FileSpecifier file;
114 if (file.SetNameWithPath(it->c_str()))
115 {
116 ParseMMLFromFile(file);
117 }
118 else
119 {
120 logWarning("%s Plugin: %s not found; ignoring", plugin.name.c_str(), it->c_str());
121 }
122 }
123 }
124
load_mml()125 void Plugins::load_mml() {
126 validate();
127
128 for (std::vector<Plugin>::iterator it = m_plugins.begin(); it != m_plugins.end(); ++it)
129 {
130 if (it->valid())
131 {
132 load_mmls(*it);
133 }
134 }
135 }
136
137 void load_shapes_patch(SDL_RWops* p, bool override_replacements);
138
load_shapes_patches(bool is_opengl)139 void Plugins::load_shapes_patches(bool is_opengl)
140 {
141 validate();
142 for (std::vector<Plugin>::iterator it = m_plugins.begin(); it != m_plugins.end(); ++it)
143 {
144 if (it->valid())
145 {
146 ScopedSearchPath ssp(it->directory);
147
148 for (std::vector<ShapesPatch>::iterator shapes_patch = it->shapes_patches.begin(); shapes_patch != it->shapes_patches.end(); ++shapes_patch)
149 {
150 if (is_opengl || !shapes_patch->requires_opengl)
151 {
152 FileSpecifier file;
153 if (file.SetNameWithPath(shapes_patch->path.c_str()))
154 {
155 OpenedFile ofile;
156 if (file.Open(ofile))
157 {
158 load_shapes_patch(ofile.GetRWops(), false);
159 }
160
161 }
162 else
163 {
164 logWarning("%s Plugin: %s not found; ignoring", it->name.c_str(), shapes_patch->path.c_str());
165 }
166 }
167 }
168 }
169 }
170 }
171
find_hud_lua()172 const Plugin* Plugins::find_hud_lua()
173 {
174 validate();
175 std::vector<Plugin>::const_reverse_iterator rend = m_plugins.rend();
176 for (std::vector<Plugin>::const_reverse_iterator rit = m_plugins.rbegin(); rit != rend; ++rit)
177 {
178 if (rit->hud_lua.size() && rit->valid())
179 {
180 return &(*rit);
181 }
182 }
183
184 return 0;
185 }
186
find_solo_lua()187 const Plugin* Plugins::find_solo_lua()
188 {
189 validate();
190 std::vector<Plugin>::const_reverse_iterator rend = m_plugins.rend();
191 for (std::vector<Plugin>::const_reverse_iterator rit = m_plugins.rbegin(); rit != rend; ++rit)
192 {
193 if (rit->solo_lua.size() && rit->valid())
194 {
195 return &(*rit);
196 }
197 }
198
199 return 0;
200 }
201
find_stats_lua()202 const Plugin* Plugins::find_stats_lua()
203 {
204 validate();
205 std::vector<Plugin>::const_reverse_iterator rend = m_plugins.rend();
206 for (std::vector<Plugin>::const_reverse_iterator rit = m_plugins.rbegin(); rit != rend; ++rit)
207 {
208 if (rit->stats_lua.size() && rit->valid())
209 {
210 return &(*rit);
211 }
212 }
213
214 return 0;
215 }
216
find_theme()217 const Plugin* Plugins::find_theme()
218 {
219 validate();
220 std::vector<Plugin>::const_reverse_iterator rend = m_plugins.rend();
221 for (std::vector<Plugin>::const_reverse_iterator rit = m_plugins.rbegin(); rit != rend; ++rit)
222 {
223 if (rit->theme.size() && rit->valid())
224 {
225 return &(*rit);
226 }
227 }
228
229 return 0;
230 }
231
plugin_file_exists(const Plugin & Data,std::string Path)232 static bool plugin_file_exists(const Plugin& Data, std::string Path)
233 {
234 FileSpecifier f = Data.directory + Path;
235 return f.Exists();
236 }
237
ParsePlugin(FileSpecifier & file_name)238 bool PluginLoader::ParsePlugin(FileSpecifier& file_name)
239 {
240 OpenedFile file;
241 if (file_name.Open(file))
242 {
243 int32 data_size;
244 file.GetLength(data_size);
245 std::vector<char> file_data;
246 file_data.resize(data_size);
247
248 if (file.Read(data_size, &file_data[0]))
249 {
250 DirectorySpecifier current_plugin_directory;
251 file_name.ToDirectory(current_plugin_directory);
252
253 char name[256];
254 current_plugin_directory.GetName(name);
255
256 std::istringstream strm(std::string(file_data.begin(), file_data.end()));
257 try {
258 InfoTree root = InfoTree::load_xml(strm).get_child("plugin");
259
260 Plugin Data = Plugin();
261 Data.directory = current_plugin_directory;
262 Data.enabled = true;
263
264 root.read_attr("name", Data.name);
265 root.read_attr("version", Data.version);
266 root.read_attr("description", Data.description);
267 root.read_attr("minimum_version", Data.required_version);
268
269 if (root.read_attr("hud_lua", Data.hud_lua) &&
270 !plugin_file_exists(Data, Data.hud_lua))
271 Data.hud_lua = "";
272
273 if (root.read_attr("solo_lua", Data.solo_lua) &&
274 !plugin_file_exists(Data, Data.solo_lua))
275 Data.solo_lua = "";
276
277 if (root.read_attr("stats_lua", Data.stats_lua) &&
278 !plugin_file_exists(Data, Data.stats_lua))
279 Data.stats_lua = "";
280
281 if (root.read_attr("theme_dir", Data.theme) &&
282 !plugin_file_exists(Data, Data.theme + "/theme2.mml"))
283 Data.theme = "";
284
285 BOOST_FOREACH(InfoTree tree, root.children_named("mml"))
286 {
287 std::string mml_path;
288 if (tree.read_attr("file", mml_path) &&
289 plugin_file_exists(Data, mml_path))
290 Data.mmls.push_back(mml_path);
291 }
292
293 BOOST_FOREACH(InfoTree tree, root.children_named("shapes_patch"))
294 {
295 ShapesPatch patch;
296 tree.read_attr("file", patch.path);
297 tree.read_attr("requires_opengl", patch.requires_opengl);
298 if (plugin_file_exists(Data, patch.path))
299 Data.shapes_patches.push_back(patch);
300 }
301
302 BOOST_FOREACH(InfoTree tree, root.children_named("scenario"))
303 {
304 ScenarioInfo info;
305 tree.read_attr("name", info.name);
306 if (info.name.size() > 31)
307 info.name.erase(31);
308
309 tree.read_attr("id", info.scenario_id);
310 if (info.scenario_id.size() > 23)
311 info.scenario_id.erase(23);
312
313 tree.read_attr("version", info.version);
314 if (info.version.size() > 7)
315 info.version.erase(7);
316
317 if (info.name.size() || info.scenario_id.size())
318 Data.required_scenarios.push_back(info);
319 }
320
321 if (Data.name.length()) {
322 std::sort(Data.mmls.begin(), Data.mmls.end());
323 if (Data.theme.size()) {
324 Data.hud_lua = "";
325 Data.solo_lua = "";
326 Data.shapes_patches.clear();
327 }
328 Plugins::instance()->add(Data);
329 }
330
331 } catch (InfoTree::parse_error e) {
332 logError("There were parsing errors in %s Plugin.xml: %s", name, e.what());
333 } catch (InfoTree::path_error e) {
334 logError("There were parsing errors in %s Plugin.xml: %s", name, e.what());
335 } catch (InfoTree::data_error e) {
336 logError("There were parsing errors in %s Plugin.xml: %s", name, e.what());
337 } catch (InfoTree::unexpected_error e) {
338 logError("There were parsing errors in %s Plugin.xml: %s", name, e.what());
339 }
340 }
341
342 return true;
343 }
344 return false;
345 }
346
ParseDirectory(FileSpecifier & dir)347 bool PluginLoader::ParseDirectory(FileSpecifier& dir)
348 {
349 std::vector<dir_entry> de;
350 if (!dir.ReadDirectory(de))
351 return false;
352
353 for (std::vector<dir_entry>::const_iterator it = de.begin(); it != de.end(); ++it) {
354 FileSpecifier file = dir + it->name;
355 if (it->name == "Plugin.xml")
356 {
357 ParsePlugin(file);
358 }
359 else if (it->is_directory && it->name[0] != '.')
360 {
361 ParseDirectory(file);
362 }
363 #ifdef HAVE_ZZIP
364 else if (algo::ends_with(it->name, ".zip") || algo::ends_with(it->name, ".ZIP"))
365 {
366 // search it for a Plugin.xml file
367 ZZIP_DIR* zzipdir = zzip_dir_open(file.GetPath(), 0);
368 if (zzipdir)
369 {
370 ZZIP_DIRENT dirent;
371 while (zzip_dir_read(zzipdir, &dirent))
372 {
373 if (strcmp(dirent.d_name, "Plugin.xml") == 0 || algo::ends_with(dirent.d_name, "/Plugin.xml"))
374 {
375 std::string archive = file.GetPath();
376 FileSpecifier file_name = FileSpecifier(archive.substr(0, archive.find_last_of('.'))) + dirent.d_name;
377 ParsePlugin(file_name);
378 }
379 }
380 zzip_dir_close(zzipdir);
381 }
382 }
383 #endif
384 }
385
386 return true;
387 }
388
389 extern std::vector<DirectorySpecifier> data_search_path;
390
enumerate()391 void Plugins::enumerate() {
392
393 logContext("parsing plugins");
394 PluginLoader loader;
395
396 for (std::vector<DirectorySpecifier>::const_iterator it = data_search_path.begin(); it != data_search_path.end(); ++it) {
397 DirectorySpecifier path = *it + "Plugins";
398 loader.ParseDirectory(path);
399 }
400 std::sort(m_plugins.begin(), m_plugins.end());
401 clear_game_error();
402 m_validated = false;
403 }
404
405 // enforce all-or-nothing loading of plugins which contain
406 // an "only one-at-a-time" item, like a Lua script or theme
validate()407 void Plugins::validate()
408 {
409 if (m_validated)
410 return;
411 m_validated = true;
412
413 // determine active plugins including solo Lua
414 bool found_solo_lua = false;
415 bool found_hud_lua = false;
416 bool found_stats_lua = false;
417 bool found_theme = false;
418 for (std::vector<Plugin>::reverse_iterator rit = m_plugins.rbegin(); rit != m_plugins.rend(); ++rit)
419 {
420 rit->overridden_solo = false;
421 if (!rit->enabled || !rit->compatible() || !rit->allowed() ||
422 (found_solo_lua && rit->solo_lua.size()) ||
423 (found_hud_lua && rit->hud_lua.size()) ||
424 (found_stats_lua && rit->stats_lua.size()) ||
425 (found_theme && rit->theme.size()))
426 {
427 rit->overridden_solo = true;
428 continue;
429 }
430
431 if (rit->solo_lua.size())
432 found_solo_lua = true;
433 if (rit->hud_lua.size())
434 found_hud_lua = true;
435 if (rit->stats_lua.size())
436 found_stats_lua = true;
437 if (rit->theme.size())
438 found_theme = true;
439 }
440
441 // determine active plugins excluding solo Lua
442 found_hud_lua = false;
443 found_stats_lua = false;
444 found_theme = false;
445 for (std::vector<Plugin>::reverse_iterator rit = m_plugins.rbegin(); rit != m_plugins.rend(); ++rit)
446 {
447 rit->overridden = false;
448 if (!rit->enabled || !rit->compatible() || !rit->allowed() ||
449 (rit->solo_lua.size()) ||
450 (found_hud_lua && rit->hud_lua.size()) ||
451 (found_stats_lua && rit->stats_lua.size()) ||
452 (found_theme && rit->theme.size()))
453 {
454 rit->overridden = true;
455 continue;
456 }
457
458 if (rit->hud_lua.size())
459 found_hud_lua = true;
460 if (rit->stats_lua.size())
461 found_stats_lua = true;
462 if (rit->theme.size())
463 found_theme = true;
464 }
465 }
466