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