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