/* Minetest-c55 Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "mods.h" #include "filesys.h" #include "strfnd.h" #include "log.h" #include "subgame.h" #include "settings.h" std::map<std::string, ModSpec> getModsInPath(std::string path) { std::map<std::string, ModSpec> result; std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path); for(u32 j=0; j<dirlist.size(); j++){ if(!dirlist[j].dir) continue; std::string modname = dirlist[j].name; // Ignore all directories beginning with a ".", especially // VCS directories like ".git" or ".svn" if(modname[0] == '.') continue; std::string modpath = path + DIR_DELIM + modname; // Handle modpacks (defined by containing modpack.txt) std::ifstream modpack_is((modpath+DIR_DELIM+"modpack.txt").c_str(), std::ios_base::binary); if(modpack_is.good()) //a modpack, recursively get the mods in it { modpack_is.close(); // We don't actually need the file ModSpec spec(modname,modpath); spec.modpack_content = getModsInPath(modpath); spec.is_modpack = true; result.insert(std::make_pair(modname,spec)); } else // not a modpack, add the modspec { std::set<std::string> depends; std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(), std::ios_base::binary); while(is.good()) { std::string dep; std::getline(is, dep); dep = trim(dep); if(dep != "") depends.insert(dep); } ModSpec spec(modname, modpath, depends); result.insert(std::make_pair(modname,spec)); } } return result; } std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods) { std::map<std::string, ModSpec> result; for(std::map<std::string,ModSpec>::iterator it = mods.begin(); it != mods.end(); ++it) { ModSpec mod = (*it).second; if(mod.is_modpack) { std::map<std::string, ModSpec> content = flattenModTree(mod.modpack_content); result.insert(content.begin(),content.end()); result.insert(std::make_pair(mod.name,mod)); } else //not a modpack { result.insert(std::make_pair(mod.name,mod)); } } return result; } std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods) { std::vector<ModSpec> result; for(std::map<std::string,ModSpec>::iterator it = mods.begin(); it != mods.end(); ++it) { ModSpec mod = (*it).second; if(mod.is_modpack) { std::vector<ModSpec> content = flattenMods(mod.modpack_content); result.reserve(result.size() + content.size()); result.insert(result.end(),content.begin(),content.end()); } else //not a modpack { // infostream << "inserting mod " << mod.name << std::endl; result.push_back(mod); } } return result; } std::vector<ModSpec> filterMods(std::vector<ModSpec> mods, std::set<std::string> exclude_mod_names) { std::vector<ModSpec> result; for(std::vector<ModSpec>::iterator it = mods.begin(); it != mods.end(); ++it) { ModSpec& mod = *it; if(exclude_mod_names.count(mod.name) == 0) result.push_back(mod); } return result; } void ModConfiguration::addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods) { addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods)); } void ModConfiguration::addMods(std::vector<ModSpec> new_mods) { // Step 1: remove mods in sorted_mods from unmet dependencies // of new_mods. new mods without unmet dependencies are // temporarily stored in satisfied_mods std::vector<ModSpec> satisfied_mods; for(std::vector<ModSpec>::iterator it = m_sorted_mods.begin(); it != m_sorted_mods.end(); ++it) { ModSpec mod = *it; for(std::vector<ModSpec>::iterator it_new = new_mods.begin(); it_new != new_mods.end(); ++it_new) { ModSpec& mod_new = *it_new; //infostream << "erasing dependency " << mod.name << " from " << mod_new.name << std::endl; mod_new.unsatisfied_depends.erase(mod.name); } } // split new mods into satisfied and unsatisfied for(std::vector<ModSpec>::iterator it = new_mods.begin(); it != new_mods.end(); ++it) { ModSpec mod_new = *it; if(mod_new.unsatisfied_depends.empty()) satisfied_mods.push_back(mod_new); else m_unsatisfied_mods.push_back(mod_new); } // Step 2: mods without unmet dependencies can be appended to // the sorted list. while(!satisfied_mods.empty()) { ModSpec mod = satisfied_mods.back(); m_sorted_mods.push_back(mod); satisfied_mods.pop_back(); for(std::list<ModSpec>::iterator it = m_unsatisfied_mods.begin(); it != m_unsatisfied_mods.end(); ) { ModSpec& mod2 = *it; mod2.unsatisfied_depends.erase(mod.name); if(mod2.unsatisfied_depends.empty()) { satisfied_mods.push_back(mod2); it = m_unsatisfied_mods.erase(it); } else ++it; } } } ModConfiguration::ModConfiguration(std::string worldpath) { // Add all world mods and all game mods addModsInPath(worldpath + DIR_DELIM + "worldmods"); SubgameSpec gamespec = findWorldSubgame(worldpath); addModsInPath(gamespec.gamemods_path); // check world.mt file for mods explicitely declared to be // loaded or not by a load_mod_<modname> = ... line. std::string worldmt = worldpath+DIR_DELIM+"world.mt"; Settings worldmt_settings; worldmt_settings.readConfigFile(worldmt.c_str()); std::vector<std::string> names = worldmt_settings.getNames(); std::set<std::string> exclude_mod_names; for(std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) { std::string name = *it; // for backwards compatibility: exclude only mods which are // explicitely excluded. if mod is not mentioned at all, it is // enabled. So by default, all installed mods are enabled. if (name.compare(0,9,"load_mod_") == 0 && !worldmt_settings.getBool(name)) { exclude_mod_names.insert(name.substr(9)); } } for(std::set<std::string>::const_iterator i = gamespec.addon_mods_paths.begin(); i != gamespec.addon_mods_paths.end(); ++i) addModsInPathFiltered((*i),exclude_mod_names); }