]> git.lizzy.rs Git - dragonfireclient.git/blob - src/mods.cpp
Fix memory leak in run_tests()
[dragonfireclient.git] / src / mods.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program 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
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "mods.h"
21 #include "filesys.h"
22 #include "strfnd.h"
23 #include "log.h"
24 #include "subgame.h"
25 #include "settings.h"
26 #include "strfnd.h"
27 #include <cctype>
28
29 static bool parseDependsLine(std::istream &is,
30                 std::string &dep, std::set<char> &symbols)
31 {
32         std::getline(is, dep);
33         dep = trim(dep);
34         symbols.clear();
35         size_t pos = dep.size();
36         while(pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){
37                 // last character is a symbol, not part of the modname
38                 symbols.insert(dep[pos-1]);
39                 --pos;
40         }
41         dep = trim(dep.substr(0, pos));
42         return dep != "";
43 }
44
45 void parseModContents(ModSpec &spec)
46 {
47         // NOTE: this function works in mutual recursion with getModsInPath
48
49         spec.depends.clear();
50         spec.optdepends.clear();
51         spec.is_modpack = false;
52         spec.modpack_content.clear();
53
54         // Handle modpacks (defined by containing modpack.txt)
55         std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str());
56         if(modpack_is.good()){ //a modpack, recursively get the mods in it
57                 modpack_is.close(); // We don't actually need the file
58                 spec.is_modpack = true;
59                 spec.modpack_content = getModsInPath(spec.path, true);
60
61                 // modpacks have no dependencies; they are defined and
62                 // tracked separately for each mod in the modpack
63         }
64         else{ // not a modpack, parse the dependencies
65                 std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str());
66                 while(is.good()){
67                         std::string dep;
68                         std::set<char> symbols;
69                         if(parseDependsLine(is, dep, symbols)){
70                                 if(symbols.count('?') != 0){
71                                         spec.optdepends.insert(dep);
72                                 }
73                                 else{
74                                         spec.depends.insert(dep);
75                                 }
76                         }
77                 }
78
79                 // FIXME: optdepends.txt is deprecated
80                 // remove this code at some point in the future
81                 std::ifstream is2((spec.path+DIR_DELIM+"optdepends.txt").c_str());
82                 while(is2.good()){
83                         std::string dep;
84                         std::set<char> symbols;
85                         if(parseDependsLine(is2, dep, symbols))
86                                 spec.optdepends.insert(dep);
87                 }
88         }
89 }
90
91 std::map<std::string, ModSpec> getModsInPath(std::string path, bool part_of_modpack)
92 {
93         // NOTE: this function works in mutual recursion with parseModContents
94
95         std::map<std::string, ModSpec> result;
96         std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
97         for(u32 j=0; j<dirlist.size(); j++){
98                 if(!dirlist[j].dir)
99                         continue;
100                 std::string modname = dirlist[j].name;
101                 // Ignore all directories beginning with a ".", especially
102                 // VCS directories like ".git" or ".svn"
103                 if(modname[0] == '.')
104                         continue;
105                 std::string modpath = path + DIR_DELIM + modname;
106
107                 ModSpec spec(modname, modpath);
108                 spec.part_of_modpack = part_of_modpack;
109                 parseModContents(spec);
110                 result.insert(std::make_pair(modname, spec));
111         }
112         return result;
113 }
114
115 ModSpec findCommonMod(const std::string &modname)
116 {
117         // Try to find in {$user,$share}/games/common/$modname
118         std::vector<std::string> find_paths;
119         find_paths.push_back(porting::path_user + DIR_DELIM + "games" +
120                         DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
121         find_paths.push_back(porting::path_share + DIR_DELIM + "games" +
122                         DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
123         for(u32 i=0; i<find_paths.size(); i++){
124                 const std::string &try_path = find_paths[i];
125                 if(fs::PathExists(try_path)){
126                         ModSpec spec(modname, try_path);
127                         parseModContents(spec);
128                         return spec;
129                 }
130         }
131         // Failed to find mod
132         return ModSpec();
133 }
134
135 std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
136 {
137         std::map<std::string, ModSpec> result;
138         for(std::map<std::string,ModSpec>::iterator it = mods.begin();
139                 it != mods.end(); ++it)
140         {
141                 ModSpec mod = (*it).second;
142                 if(mod.is_modpack)
143                 {
144                         std::map<std::string, ModSpec> content = 
145                                 flattenModTree(mod.modpack_content);
146                         result.insert(content.begin(),content.end());
147                         result.insert(std::make_pair(mod.name,mod));
148                 } 
149                 else //not a modpack
150                 {
151                         result.insert(std::make_pair(mod.name,mod));
152                 }
153         }
154         return result;
155 }
156
157 std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
158 {
159         std::vector<ModSpec> result;
160         for(std::map<std::string,ModSpec>::iterator it = mods.begin();
161                 it != mods.end(); ++it)
162         {
163                 ModSpec mod = (*it).second;
164                 if(mod.is_modpack)
165                 {
166                         std::vector<ModSpec> content = flattenMods(mod.modpack_content);
167                         result.reserve(result.size() + content.size());
168                         result.insert(result.end(),content.begin(),content.end());
169                         
170                 } 
171                 else //not a modpack
172                 {
173                         result.push_back(mod);
174                 }
175         }
176         return result;
177 }
178
179 ModConfiguration::ModConfiguration(std::string worldpath)
180 {
181         SubgameSpec gamespec = findWorldSubgame(worldpath);
182
183         // Add common mods
184         std::map<std::string, ModSpec> common_mods;
185         std::vector<std::string> inexistent_common_mods;
186         Settings gameconf;
187         if(getGameConfig(gamespec.path, gameconf)){
188                 if(gameconf.exists("common_mods")){
189                         Strfnd f(gameconf.get("common_mods"));
190                         while(!f.atend()){
191                                 std::string modname = trim(f.next(","));
192                                 if(modname.empty())
193                                         continue;
194                                 ModSpec spec = findCommonMod(modname);
195                                 if(spec.name.empty())
196                                         inexistent_common_mods.push_back(modname);
197                                 else
198                                         common_mods.insert(std::make_pair(modname, spec));
199                         }
200                 }
201         }
202         if(!inexistent_common_mods.empty()){
203                 std::string s = "Required common mods ";
204                 for(u32 i=0; i<inexistent_common_mods.size(); i++){
205                         if(i != 0) s += ", ";
206                         s += std::string("\"") + inexistent_common_mods[i] + "\"";
207                 }
208                 s += " could not be found.";
209                 throw ModError(s);
210         }
211         addMods(flattenMods(common_mods));
212
213         // Add all game mods and all world mods
214         addModsInPath(gamespec.gamemods_path);
215         addModsInPath(worldpath + DIR_DELIM + "worldmods");
216
217         // check world.mt file for mods explicitely declared to be
218         // loaded or not by a load_mod_<modname> = ... line.
219         std::string worldmt = worldpath+DIR_DELIM+"world.mt";
220         Settings worldmt_settings;
221         worldmt_settings.readConfigFile(worldmt.c_str());
222         std::vector<std::string> names = worldmt_settings.getNames();
223         std::set<std::string> exclude_mod_names;
224         for(std::vector<std::string>::iterator it = names.begin(); 
225                 it != names.end(); ++it)
226         {       
227                 std::string name = *it;  
228                 // for backwards compatibility: exclude only mods which are
229                 // explicitely excluded. if mod is not mentioned at all, it is
230                 // enabled. So by default, all installed mods are enabled.
231                 if (name.compare(0,9,"load_mod_") == 0 &&
232                         !worldmt_settings.getBool(name))
233                 {
234                         exclude_mod_names.insert(name.substr(9));
235                 }
236         }
237
238         // Collect all mods in gamespec.addon_mods_paths,
239         // excluding those in the set exclude_mod_names
240         std::vector<ModSpec> addon_mods;
241         for(std::set<std::string>::const_iterator it_path = gamespec.addon_mods_paths.begin();
242                         it_path != gamespec.addon_mods_paths.end(); ++it_path)
243         {
244                 std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(*it_path));
245                 for(std::vector<ModSpec>::iterator it = addon_mods_in_path.begin();
246                         it != addon_mods_in_path.end(); ++it)
247                 {
248                         ModSpec& mod = *it;
249                         if(exclude_mod_names.count(mod.name) == 0)
250                                 addon_mods.push_back(mod);
251                 }
252         }
253
254         addMods(addon_mods);
255
256         // report on name conflicts
257         if(!m_name_conflicts.empty()){
258                 std::string s = "Unresolved name conflicts for mods ";
259                 for(std::set<std::string>::const_iterator it = m_name_conflicts.begin();
260                                 it != m_name_conflicts.end(); ++it)
261                 {
262                         if(it != m_name_conflicts.begin()) s += ", ";
263                         s += std::string("\"") + (*it) + "\"";
264                 }
265                 s += ".";
266                 throw ModError(s);
267         }
268
269         // get the mods in order
270         resolveDependencies();
271 }
272
273 void ModConfiguration::addModsInPath(std::string path)
274 {
275         addMods(flattenMods(getModsInPath(path)));
276 }
277
278 void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
279 {
280         // Maintain a map of all existing m_unsatisfied_mods.
281         // Keys are mod names and values are indices into m_unsatisfied_mods.
282         std::map<std::string, u32> existing_mods;
283         for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){
284                 existing_mods[m_unsatisfied_mods[i].name] = i;
285         }
286
287         // Add new mods
288         for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){
289                 // First iteration:
290                 // Add all the mods that come from modpacks
291                 // Second iteration:
292                 // Add all the mods that didn't come from modpacks
293                 
294                 std::set<std::string> seen_this_iteration;
295
296                 for(std::vector<ModSpec>::const_iterator it = new_mods.begin();
297                                 it != new_mods.end(); ++it){
298                         const ModSpec &mod = *it;
299                         if(mod.part_of_modpack != want_from_modpack)
300                                 continue;
301                         if(existing_mods.count(mod.name) == 0){
302                                 // GOOD CASE: completely new mod.
303                                 m_unsatisfied_mods.push_back(mod);
304                                 existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
305                         }
306                         else if(seen_this_iteration.count(mod.name) == 0){
307                                 // BAD CASE: name conflict in different levels.
308                                 u32 oldindex = existing_mods[mod.name];
309                                 const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
310                                 errorstream<<"WARNING: Mod name conflict detected: \""
311                                         <<mod.name<<"\""<<std::endl
312                                         <<"Will not load: "<<oldmod.path<<std::endl
313                                         <<"Overridden by: "<<mod.path<<std::endl;
314                                 m_unsatisfied_mods[oldindex] = mod;
315
316                                 // If there was a "VERY BAD CASE" name conflict
317                                 // in an earlier level, ignore it.
318                                 m_name_conflicts.erase(mod.name);
319                         }
320                         else{
321                                 // VERY BAD CASE: name conflict in the same level.
322                                 u32 oldindex = existing_mods[mod.name];
323                                 const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
324                                 errorstream<<"WARNING: Mod name conflict detected: \""
325                                         <<mod.name<<"\""<<std::endl
326                                         <<"Will not load: "<<oldmod.path<<std::endl
327                                         <<"Will not load: "<<mod.path<<std::endl;
328                                 m_unsatisfied_mods[oldindex] = mod;
329                                 m_name_conflicts.insert(mod.name);
330                         }
331                         seen_this_iteration.insert(mod.name);
332                 }
333         }
334 }
335
336 void ModConfiguration::resolveDependencies()
337 {
338         // Step 1: Compile a list of the mod names we're working with
339         std::set<std::string> modnames;
340         for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
341                 it != m_unsatisfied_mods.end(); ++it){
342                 modnames.insert((*it).name);
343         }
344
345         // Step 2: get dependencies (including optional dependencies)
346         // of each mod, split mods into satisfied and unsatisfied
347         std::list<ModSpec> satisfied;
348         std::list<ModSpec> unsatisfied;
349         for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
350                         it != m_unsatisfied_mods.end(); ++it){
351                 ModSpec mod = *it;
352                 mod.unsatisfied_depends = mod.depends;
353                 // check which optional dependencies actually exist
354                 for(std::set<std::string>::iterator it_optdep = mod.optdepends.begin();
355                                 it_optdep != mod.optdepends.end(); ++it_optdep){
356                         std::string optdep = *it_optdep;
357                         if(modnames.count(optdep) != 0)
358                                 mod.unsatisfied_depends.insert(optdep);
359                 }
360                 // if a mod has no depends it is initially satisfied
361                 if(mod.unsatisfied_depends.empty())
362                         satisfied.push_back(mod);
363                 else
364                         unsatisfied.push_back(mod);
365         }
366
367         // Step 3: mods without unmet dependencies can be appended to
368         // the sorted list.
369         while(!satisfied.empty()){
370                 ModSpec mod = satisfied.back();
371                 m_sorted_mods.push_back(mod);
372                 satisfied.pop_back();
373                 for(std::list<ModSpec>::iterator it = unsatisfied.begin();
374                                 it != unsatisfied.end(); ){
375                         ModSpec& mod2 = *it;
376                         mod2.unsatisfied_depends.erase(mod.name);
377                         if(mod2.unsatisfied_depends.empty()){
378                                 satisfied.push_back(mod2);
379                                 it = unsatisfied.erase(it);
380                         }
381                         else{
382                                 ++it;
383                         }
384                 }       
385         }
386
387         // Step 4: write back list of unsatisfied mods
388         m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
389 }