3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
29 static bool parseDependsLine(std::istream &is,
30 std::string &dep, std::set<char> &symbols)
32 std::getline(is, dep);
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]);
41 dep = trim(dep.substr(0, pos));
45 void parseModContents(ModSpec &spec)
47 // NOTE: this function works in mutual recursion with getModsInPath
50 spec.optdepends.clear();
51 spec.is_modpack = false;
52 spec.modpack_content.clear();
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);
61 // modpacks have no dependencies; they are defined and
62 // tracked separately for each mod in the modpack
64 else{ // not a modpack, parse the dependencies
65 std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str());
68 std::set<char> symbols;
69 if(parseDependsLine(is, dep, symbols)){
70 if(symbols.count('?') != 0){
71 spec.optdepends.insert(dep);
74 spec.depends.insert(dep);
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());
84 std::set<char> symbols;
85 if(parseDependsLine(is2, dep, symbols))
86 spec.optdepends.insert(dep);
91 std::map<std::string, ModSpec> getModsInPath(std::string path, bool part_of_modpack)
93 // NOTE: this function works in mutual recursion with parseModContents
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++){
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] == '.')
105 std::string modpath = path + DIR_DELIM + modname;
107 ModSpec spec(modname, modpath);
108 spec.part_of_modpack = part_of_modpack;
109 parseModContents(spec);
110 result.insert(std::make_pair(modname, spec));
115 ModSpec findCommonMod(const std::string &modname)
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);
131 // Failed to find mod
135 std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
137 std::map<std::string, ModSpec> result;
138 for(std::map<std::string,ModSpec>::iterator it = mods.begin();
139 it != mods.end(); ++it)
141 ModSpec mod = (*it).second;
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));
151 result.insert(std::make_pair(mod.name,mod));
157 std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
159 std::vector<ModSpec> result;
160 for(std::map<std::string,ModSpec>::iterator it = mods.begin();
161 it != mods.end(); ++it)
163 ModSpec mod = (*it).second;
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());
173 result.push_back(mod);
179 ModConfiguration::ModConfiguration(std::string worldpath)
181 SubgameSpec gamespec = findWorldSubgame(worldpath);
184 std::map<std::string, ModSpec> common_mods;
185 std::vector<std::string> inexistent_common_mods;
187 if(getGameConfig(gamespec.path, gameconf)){
188 if(gameconf.exists("common_mods")){
189 Strfnd f(gameconf.get("common_mods"));
191 std::string modname = trim(f.next(","));
194 ModSpec spec = findCommonMod(modname);
195 if(spec.name.empty())
196 inexistent_common_mods.push_back(modname);
198 common_mods.insert(std::make_pair(modname, spec));
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] + "\"";
208 s += " could not be found.";
211 addMods(flattenMods(common_mods));
213 // Add all game mods and all world mods
214 addModsInPath(gamespec.gamemods_path);
215 addModsInPath(worldpath + DIR_DELIM + "worldmods");
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> include_mod_names;
224 for(std::vector<std::string>::iterator it = names.begin();
225 it != names.end(); ++it)
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))
234 include_mod_names.insert(name.substr(9));
238 // Collect all mods that are also in include_mod_names
239 std::vector<ModSpec> addon_mods;
240 for(std::set<std::string>::const_iterator it_path = gamespec.addon_mods_paths.begin();
241 it_path != gamespec.addon_mods_paths.end(); ++it_path)
243 std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(*it_path));
244 for(std::vector<ModSpec>::iterator it = addon_mods_in_path.begin();
245 it != addon_mods_in_path.end(); ++it)
248 if(include_mod_names.count(mod.name) != 0)
249 addon_mods.push_back(mod);
251 worldmt_settings.setBool("load_mod_" + mod.name, false);
254 worldmt_settings.updateConfigFile(worldmt.c_str());
258 // report on name conflicts
259 if(!m_name_conflicts.empty()){
260 std::string s = "Unresolved name conflicts for mods ";
261 for(std::set<std::string>::const_iterator it = m_name_conflicts.begin();
262 it != m_name_conflicts.end(); ++it)
264 if(it != m_name_conflicts.begin()) s += ", ";
265 s += std::string("\"") + (*it) + "\"";
271 // get the mods in order
272 resolveDependencies();
275 void ModConfiguration::addModsInPath(std::string path)
277 addMods(flattenMods(getModsInPath(path)));
280 void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
282 // Maintain a map of all existing m_unsatisfied_mods.
283 // Keys are mod names and values are indices into m_unsatisfied_mods.
284 std::map<std::string, u32> existing_mods;
285 for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){
286 existing_mods[m_unsatisfied_mods[i].name] = i;
290 for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){
292 // Add all the mods that come from modpacks
294 // Add all the mods that didn't come from modpacks
296 std::set<std::string> seen_this_iteration;
298 for(std::vector<ModSpec>::const_iterator it = new_mods.begin();
299 it != new_mods.end(); ++it){
300 const ModSpec &mod = *it;
301 if(mod.part_of_modpack != want_from_modpack)
303 if(existing_mods.count(mod.name) == 0){
304 // GOOD CASE: completely new mod.
305 m_unsatisfied_mods.push_back(mod);
306 existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
308 else if(seen_this_iteration.count(mod.name) == 0){
309 // BAD CASE: name conflict in different levels.
310 u32 oldindex = existing_mods[mod.name];
311 const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
312 actionstream<<"WARNING: Mod name conflict detected: \""
313 <<mod.name<<"\""<<std::endl
314 <<"Will not load: "<<oldmod.path<<std::endl
315 <<"Overridden by: "<<mod.path<<std::endl;
316 m_unsatisfied_mods[oldindex] = mod;
318 // If there was a "VERY BAD CASE" name conflict
319 // in an earlier level, ignore it.
320 m_name_conflicts.erase(mod.name);
323 // VERY BAD CASE: name conflict in the same level.
324 u32 oldindex = existing_mods[mod.name];
325 const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
326 errorstream<<"WARNING: Mod name conflict detected: \""
327 <<mod.name<<"\""<<std::endl
328 <<"Will not load: "<<oldmod.path<<std::endl
329 <<"Will not load: "<<mod.path<<std::endl;
330 m_unsatisfied_mods[oldindex] = mod;
331 m_name_conflicts.insert(mod.name);
333 seen_this_iteration.insert(mod.name);
338 void ModConfiguration::resolveDependencies()
340 // Step 1: Compile a list of the mod names we're working with
341 std::set<std::string> modnames;
342 for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
343 it != m_unsatisfied_mods.end(); ++it){
344 modnames.insert((*it).name);
347 // Step 2: get dependencies (including optional dependencies)
348 // of each mod, split mods into satisfied and unsatisfied
349 std::list<ModSpec> satisfied;
350 std::list<ModSpec> unsatisfied;
351 for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
352 it != m_unsatisfied_mods.end(); ++it){
354 mod.unsatisfied_depends = mod.depends;
355 // check which optional dependencies actually exist
356 for(std::set<std::string>::iterator it_optdep = mod.optdepends.begin();
357 it_optdep != mod.optdepends.end(); ++it_optdep){
358 std::string optdep = *it_optdep;
359 if(modnames.count(optdep) != 0)
360 mod.unsatisfied_depends.insert(optdep);
362 // if a mod has no depends it is initially satisfied
363 if(mod.unsatisfied_depends.empty())
364 satisfied.push_back(mod);
366 unsatisfied.push_back(mod);
369 // Step 3: mods without unmet dependencies can be appended to
371 while(!satisfied.empty()){
372 ModSpec mod = satisfied.back();
373 m_sorted_mods.push_back(mod);
374 satisfied.pop_back();
375 for(std::list<ModSpec>::iterator it = unsatisfied.begin();
376 it != unsatisfied.end(); ){
378 mod2.unsatisfied_depends.erase(mod.name);
379 if(mod2.unsatisfied_depends.empty()){
380 satisfied.push_back(mod2);
381 it = unsatisfied.erase(it);
389 // Step 4: write back list of unsatisfied mods
390 m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());