|
@@ -69,7 +69,7 @@ bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols)
|
|
|
return !dep.empty();
|
|
|
}
|
|
|
|
|
|
-void parseModContents(ModSpec &spec)
|
|
|
+bool parseModContents(ModSpec &spec)
|
|
|
{
|
|
|
// NOTE: this function works in mutual recursion with getModsInPath
|
|
|
|
|
@@ -79,91 +79,89 @@ void parseModContents(ModSpec &spec)
|
|
|
spec.modpack_content.clear();
|
|
|
|
|
|
// Handle modpacks (defined by containing modpack.txt)
|
|
|
- std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
|
|
|
- std::ifstream modpack2_is((spec.path + DIR_DELIM + "modpack.conf").c_str());
|
|
|
- if (modpack_is.good() || modpack2_is.good()) {
|
|
|
- if (modpack_is.good())
|
|
|
- modpack_is.close();
|
|
|
-
|
|
|
- if (modpack2_is.good())
|
|
|
- modpack2_is.close();
|
|
|
-
|
|
|
+ if (fs::IsFile(spec.path + DIR_DELIM + "modpack.txt") ||
|
|
|
+ fs::IsFile(spec.path + DIR_DELIM + "modpack.conf")) {
|
|
|
spec.is_modpack = true;
|
|
|
spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, true);
|
|
|
+ return true;
|
|
|
+ } else if (!fs::IsFile(spec.path + DIR_DELIM + "init.lua")) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- } else {
|
|
|
- Settings info;
|
|
|
- info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
|
|
|
|
|
|
- if (info.exists("name"))
|
|
|
- spec.name = info.get("name");
|
|
|
- else
|
|
|
- spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated.");
|
|
|
-
|
|
|
- if (info.exists("author"))
|
|
|
- spec.author = info.get("author");
|
|
|
-
|
|
|
- if (info.exists("release"))
|
|
|
- spec.release = info.getS32("release");
|
|
|
-
|
|
|
- // Attempt to load dependencies from mod.conf
|
|
|
- bool mod_conf_has_depends = false;
|
|
|
- if (info.exists("depends")) {
|
|
|
- mod_conf_has_depends = true;
|
|
|
- std::string dep = info.get("depends");
|
|
|
- // clang-format off
|
|
|
- dep.erase(std::remove_if(dep.begin(), dep.end(),
|
|
|
- static_cast<int (*)(int)>(&std::isspace)), dep.end());
|
|
|
- // clang-format on
|
|
|
- for (const auto &dependency : str_split(dep, ',')) {
|
|
|
- spec.depends.insert(dependency);
|
|
|
- }
|
|
|
+ Settings info;
|
|
|
+ info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
|
|
|
+
|
|
|
+ if (info.exists("name"))
|
|
|
+ spec.name = info.get("name");
|
|
|
+ else
|
|
|
+ spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated.");
|
|
|
+
|
|
|
+ if (info.exists("author"))
|
|
|
+ spec.author = info.get("author");
|
|
|
+
|
|
|
+ if (info.exists("release"))
|
|
|
+ spec.release = info.getS32("release");
|
|
|
+
|
|
|
+ // Attempt to load dependencies from mod.conf
|
|
|
+ bool mod_conf_has_depends = false;
|
|
|
+ if (info.exists("depends")) {
|
|
|
+ mod_conf_has_depends = true;
|
|
|
+ std::string dep = info.get("depends");
|
|
|
+ // clang-format off
|
|
|
+ dep.erase(std::remove_if(dep.begin(), dep.end(),
|
|
|
+ static_cast<int (*)(int)>(&std::isspace)), dep.end());
|
|
|
+ // clang-format on
|
|
|
+ for (const auto &dependency : str_split(dep, ',')) {
|
|
|
+ spec.depends.insert(dependency);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (info.exists("optional_depends")) {
|
|
|
- mod_conf_has_depends = true;
|
|
|
- std::string dep = info.get("optional_depends");
|
|
|
- // clang-format off
|
|
|
- dep.erase(std::remove_if(dep.begin(), dep.end(),
|
|
|
- static_cast<int (*)(int)>(&std::isspace)), dep.end());
|
|
|
- // clang-format on
|
|
|
- for (const auto &dependency : str_split(dep, ',')) {
|
|
|
- spec.optdepends.insert(dependency);
|
|
|
- }
|
|
|
+ if (info.exists("optional_depends")) {
|
|
|
+ mod_conf_has_depends = true;
|
|
|
+ std::string dep = info.get("optional_depends");
|
|
|
+ // clang-format off
|
|
|
+ dep.erase(std::remove_if(dep.begin(), dep.end(),
|
|
|
+ static_cast<int (*)(int)>(&std::isspace)), dep.end());
|
|
|
+ // clang-format on
|
|
|
+ for (const auto &dependency : str_split(dep, ',')) {
|
|
|
+ spec.optdepends.insert(dependency);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Fallback to depends.txt
|
|
|
- if (!mod_conf_has_depends) {
|
|
|
- std::vector<std::string> dependencies;
|
|
|
+ // Fallback to depends.txt
|
|
|
+ if (!mod_conf_has_depends) {
|
|
|
+ std::vector<std::string> dependencies;
|
|
|
|
|
|
- std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
|
|
|
+ std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
|
|
|
|
|
|
- if (is.good())
|
|
|
- spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead.");
|
|
|
+ if (is.good())
|
|
|
+ spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead.");
|
|
|
|
|
|
- while (is.good()) {
|
|
|
- std::string dep;
|
|
|
- std::getline(is, dep);
|
|
|
- dependencies.push_back(dep);
|
|
|
- }
|
|
|
+ while (is.good()) {
|
|
|
+ std::string dep;
|
|
|
+ std::getline(is, dep);
|
|
|
+ dependencies.push_back(dep);
|
|
|
+ }
|
|
|
|
|
|
- for (auto &dependency : dependencies) {
|
|
|
- std::unordered_set<char> symbols;
|
|
|
- if (parseDependsString(dependency, symbols)) {
|
|
|
- if (symbols.count('?') != 0) {
|
|
|
- spec.optdepends.insert(dependency);
|
|
|
- } else {
|
|
|
- spec.depends.insert(dependency);
|
|
|
- }
|
|
|
+ for (auto &dependency : dependencies) {
|
|
|
+ std::unordered_set<char> symbols;
|
|
|
+ if (parseDependsString(dependency, symbols)) {
|
|
|
+ if (symbols.count('?') != 0) {
|
|
|
+ spec.optdepends.insert(dependency);
|
|
|
+ } else {
|
|
|
+ spec.depends.insert(dependency);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if (info.exists("description"))
|
|
|
- spec.desc = info.get("description");
|
|
|
- else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc))
|
|
|
- spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead.");
|
|
|
}
|
|
|
+
|
|
|
+ if (info.exists("description"))
|
|
|
+ spec.desc = info.get("description");
|
|
|
+ else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc))
|
|
|
+ spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead.");
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
std::map<std::string, ModSpec> getModsInPath(
|
|
@@ -218,240 +216,6 @@ std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods)
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
-ModConfiguration::ModConfiguration(const std::string &worldpath)
|
|
|
-{
|
|
|
-}
|
|
|
-
|
|
|
-void ModConfiguration::printUnsatisfiedModsError() const
|
|
|
-{
|
|
|
- for (const ModSpec &mod : m_unsatisfied_mods) {
|
|
|
- errorstream << "mod \"" << mod.name
|
|
|
- << "\" has unsatisfied dependencies: ";
|
|
|
- for (const std::string &unsatisfied_depend : mod.unsatisfied_depends)
|
|
|
- errorstream << " \"" << unsatisfied_depend << "\"";
|
|
|
- errorstream << std::endl;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void ModConfiguration::addModsInPath(const std::string &path, const std::string &virtual_path)
|
|
|
-{
|
|
|
- addMods(flattenMods(getModsInPath(path, virtual_path)));
|
|
|
-}
|
|
|
-
|
|
|
-void ModConfiguration::addMods(const std::vector<ModSpec> &new_mods)
|
|
|
-{
|
|
|
- // Maintain a map of all existing m_unsatisfied_mods.
|
|
|
- // Keys are mod names and values are indices into m_unsatisfied_mods.
|
|
|
- std::map<std::string, u32> existing_mods;
|
|
|
- for (u32 i = 0; i < m_unsatisfied_mods.size(); ++i) {
|
|
|
- existing_mods[m_unsatisfied_mods[i].name] = i;
|
|
|
- }
|
|
|
-
|
|
|
- // Add new mods
|
|
|
- for (int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack) {
|
|
|
- // First iteration:
|
|
|
- // Add all the mods that come from modpacks
|
|
|
- // Second iteration:
|
|
|
- // Add all the mods that didn't come from modpacks
|
|
|
-
|
|
|
- std::set<std::string> seen_this_iteration;
|
|
|
-
|
|
|
- for (const ModSpec &mod : new_mods) {
|
|
|
- if (mod.part_of_modpack != (bool)want_from_modpack)
|
|
|
- continue;
|
|
|
-
|
|
|
- if (existing_mods.count(mod.name) == 0) {
|
|
|
- // GOOD CASE: completely new mod.
|
|
|
- m_unsatisfied_mods.push_back(mod);
|
|
|
- existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
|
|
|
- } else if (seen_this_iteration.count(mod.name) == 0) {
|
|
|
- // BAD CASE: name conflict in different levels.
|
|
|
- u32 oldindex = existing_mods[mod.name];
|
|
|
- const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
|
|
|
- warningstream << "Mod name conflict detected: \""
|
|
|
- << mod.name << "\"" << std::endl
|
|
|
- << "Will not load: " << oldmod.path
|
|
|
- << std::endl
|
|
|
- << "Overridden by: " << mod.path
|
|
|
- << std::endl;
|
|
|
- m_unsatisfied_mods[oldindex] = mod;
|
|
|
-
|
|
|
- // If there was a "VERY BAD CASE" name conflict
|
|
|
- // in an earlier level, ignore it.
|
|
|
- m_name_conflicts.erase(mod.name);
|
|
|
- } else {
|
|
|
- // VERY BAD CASE: name conflict in the same level.
|
|
|
- u32 oldindex = existing_mods[mod.name];
|
|
|
- const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
|
|
|
- warningstream << "Mod name conflict detected: \""
|
|
|
- << mod.name << "\"" << std::endl
|
|
|
- << "Will not load: " << oldmod.path
|
|
|
- << std::endl
|
|
|
- << "Will not load: " << mod.path
|
|
|
- << std::endl;
|
|
|
- m_unsatisfied_mods[oldindex] = mod;
|
|
|
- m_name_conflicts.insert(mod.name);
|
|
|
- }
|
|
|
-
|
|
|
- seen_this_iteration.insert(mod.name);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void ModConfiguration::addModsFromConfig(
|
|
|
- const std::string &settings_path,
|
|
|
- const std::unordered_map<std::string, std::string> &modPaths)
|
|
|
-{
|
|
|
- Settings conf;
|
|
|
- std::unordered_map<std::string, std::string> load_mod_names;
|
|
|
-
|
|
|
- conf.readConfigFile(settings_path.c_str());
|
|
|
- std::vector<std::string> names = conf.getNames();
|
|
|
- for (const std::string &name : names) {
|
|
|
- const auto &value = conf.get(name);
|
|
|
- if (name.compare(0, 9, "load_mod_") == 0 && value != "false" &&
|
|
|
- value != "nil")
|
|
|
- load_mod_names[name.substr(9)] = value;
|
|
|
- }
|
|
|
-
|
|
|
- std::vector<ModSpec> addon_mods;
|
|
|
- std::unordered_map<std::string, std::vector<std::string>> candidates;
|
|
|
-
|
|
|
- for (const auto &modPath : modPaths) {
|
|
|
- std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(modPath.second, modPath.first));
|
|
|
- for (std::vector<ModSpec>::const_iterator it = addon_mods_in_path.begin();
|
|
|
- it != addon_mods_in_path.end(); ++it) {
|
|
|
- const ModSpec &mod = *it;
|
|
|
- const auto &pair = load_mod_names.find(mod.name);
|
|
|
- if (pair != load_mod_names.end()) {
|
|
|
- if (is_yes(pair->second) || pair->second == mod.virtual_path) {
|
|
|
- addon_mods.push_back(mod);
|
|
|
- } else {
|
|
|
- candidates[pair->first].emplace_back(mod.virtual_path);
|
|
|
- }
|
|
|
- } else {
|
|
|
- conf.setBool("load_mod_" + mod.name, false);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- conf.updateConfigFile(settings_path.c_str());
|
|
|
-
|
|
|
- addMods(addon_mods);
|
|
|
- checkConflictsAndDeps();
|
|
|
-
|
|
|
- // complain about mods declared to be loaded, but not found
|
|
|
- for (const ModSpec &addon_mod : addon_mods)
|
|
|
- load_mod_names.erase(addon_mod.name);
|
|
|
-
|
|
|
- std::vector<ModSpec> unsatisfiedMods = getUnsatisfiedMods();
|
|
|
-
|
|
|
- for (const ModSpec &unsatisfiedMod : unsatisfiedMods)
|
|
|
- load_mod_names.erase(unsatisfiedMod.name);
|
|
|
-
|
|
|
- if (!load_mod_names.empty()) {
|
|
|
- errorstream << "The following mods could not be found:";
|
|
|
- for (const auto &pair : load_mod_names)
|
|
|
- errorstream << " \"" << pair.first << "\"";
|
|
|
- errorstream << std::endl;
|
|
|
-
|
|
|
- for (const auto &pair : load_mod_names) {
|
|
|
- const auto &candidate = candidates.find(pair.first);
|
|
|
- if (candidate != candidates.end()) {
|
|
|
- errorstream << "Unable to load " << pair.first << " as the specified path "
|
|
|
- << pair.second << " could not be found. "
|
|
|
- << "However, it is available in the following locations:"
|
|
|
- << std::endl;
|
|
|
- for (const auto &path : candidate->second) {
|
|
|
- errorstream << " - " << path << std::endl;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void ModConfiguration::checkConflictsAndDeps()
|
|
|
-{
|
|
|
- // report on name conflicts
|
|
|
- if (!m_name_conflicts.empty()) {
|
|
|
- std::string s = "Unresolved name conflicts for mods ";
|
|
|
- for (std::unordered_set<std::string>::const_iterator it =
|
|
|
- m_name_conflicts.begin();
|
|
|
- it != m_name_conflicts.end(); ++it) {
|
|
|
- if (it != m_name_conflicts.begin())
|
|
|
- s += ", ";
|
|
|
- s += std::string("\"") + (*it) + "\"";
|
|
|
- }
|
|
|
- s += ".";
|
|
|
- throw ModError(s);
|
|
|
- }
|
|
|
-
|
|
|
- // get the mods in order
|
|
|
- resolveDependencies();
|
|
|
-}
|
|
|
-
|
|
|
-void ModConfiguration::resolveDependencies()
|
|
|
-{
|
|
|
- // Step 1: Compile a list of the mod names we're working with
|
|
|
- std::set<std::string> modnames;
|
|
|
- for (const ModSpec &mod : m_unsatisfied_mods) {
|
|
|
- modnames.insert(mod.name);
|
|
|
- }
|
|
|
-
|
|
|
- // Step 2: get dependencies (including optional dependencies)
|
|
|
- // of each mod, split mods into satisfied and unsatisfied
|
|
|
- std::list<ModSpec> satisfied;
|
|
|
- std::list<ModSpec> unsatisfied;
|
|
|
- for (ModSpec mod : m_unsatisfied_mods) {
|
|
|
- mod.unsatisfied_depends = mod.depends;
|
|
|
- // check which optional dependencies actually exist
|
|
|
- for (const std::string &optdep : mod.optdepends) {
|
|
|
- if (modnames.count(optdep) != 0)
|
|
|
- mod.unsatisfied_depends.insert(optdep);
|
|
|
- }
|
|
|
- // if a mod has no depends it is initially satisfied
|
|
|
- if (mod.unsatisfied_depends.empty())
|
|
|
- satisfied.push_back(mod);
|
|
|
- else
|
|
|
- unsatisfied.push_back(mod);
|
|
|
- }
|
|
|
-
|
|
|
- // Step 3: mods without unmet dependencies can be appended to
|
|
|
- // the sorted list.
|
|
|
- while (!satisfied.empty()) {
|
|
|
- ModSpec mod = satisfied.back();
|
|
|
- m_sorted_mods.push_back(mod);
|
|
|
- satisfied.pop_back();
|
|
|
- for (auto it = unsatisfied.begin(); it != unsatisfied.end();) {
|
|
|
- ModSpec &mod2 = *it;
|
|
|
- mod2.unsatisfied_depends.erase(mod.name);
|
|
|
- if (mod2.unsatisfied_depends.empty()) {
|
|
|
- satisfied.push_back(mod2);
|
|
|
- it = unsatisfied.erase(it);
|
|
|
- } else {
|
|
|
- ++it;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Step 4: write back list of unsatisfied mods
|
|
|
- m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
|
|
|
-}
|
|
|
-
|
|
|
-#ifndef SERVER
|
|
|
-ClientModConfiguration::ClientModConfiguration(const std::string &path) :
|
|
|
- ModConfiguration(path)
|
|
|
-{
|
|
|
- std::unordered_map<std::string, std::string> paths;
|
|
|
- std::string path_user = porting::path_user + DIR_DELIM + "clientmods";
|
|
|
- if (path != path_user) {
|
|
|
- paths["share"] = path;
|
|
|
- }
|
|
|
- paths["mods"] = path_user;
|
|
|
-
|
|
|
- std::string settings_path = path_user + DIR_DELIM + "mods.conf";
|
|
|
- addModsFromConfig(settings_path, paths);
|
|
|
-}
|
|
|
-#endif
|
|
|
|
|
|
ModMetadata::ModMetadata(const std::string &mod_name, ModMetadataDatabase *database):
|
|
|
m_mod_name(mod_name), m_database(database)
|