mods.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include <cctype>
  17. #include <fstream>
  18. #include <json/json.h>
  19. #include <algorithm>
  20. #include "content/mods.h"
  21. #include "database/database.h"
  22. #include "filesys.h"
  23. #include "log.h"
  24. #include "content/subgames.h"
  25. #include "settings.h"
  26. #include "porting.h"
  27. #include "convert_json.h"
  28. #include "script/common/c_internal.h"
  29. void ModSpec::checkAndLog() const
  30. {
  31. if (!string_allowed(name, MODNAME_ALLOWED_CHARS)) {
  32. throw ModError("Error loading mod \"" + name +
  33. "\": Mod name does not follow naming conventions: "
  34. "Only characters [a-z0-9_] are allowed.");
  35. }
  36. // Log deprecation messages
  37. auto handling_mode = get_deprecated_handling_mode();
  38. if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) {
  39. std::ostringstream os;
  40. os << "Mod " << name << " at " << path << ":" << std::endl;
  41. for (auto msg : deprecation_msgs)
  42. os << "\t" << msg << std::endl;
  43. if (handling_mode == DeprecatedHandlingMode::Error)
  44. throw ModError(os.str());
  45. else
  46. warningstream << os.str();
  47. }
  48. }
  49. bool parseDependsString(std::string &dep, std::unordered_set<char> &symbols)
  50. {
  51. dep = trim(dep);
  52. symbols.clear();
  53. size_t pos = dep.size();
  54. while (pos > 0 &&
  55. !string_allowed(dep.substr(pos - 1, 1), MODNAME_ALLOWED_CHARS)) {
  56. // last character is a symbol, not part of the modname
  57. symbols.insert(dep[pos - 1]);
  58. --pos;
  59. }
  60. dep = trim(dep.substr(0, pos));
  61. return !dep.empty();
  62. }
  63. bool parseModContents(ModSpec &spec)
  64. {
  65. // NOTE: this function works in mutual recursion with getModsInPath
  66. spec.depends.clear();
  67. spec.optdepends.clear();
  68. spec.is_modpack = false;
  69. spec.modpack_content.clear();
  70. // Handle modpacks (defined by containing modpack.txt)
  71. if (fs::IsFile(spec.path + DIR_DELIM + "modpack.txt") ||
  72. fs::IsFile(spec.path + DIR_DELIM + "modpack.conf")) {
  73. spec.is_modpack = true;
  74. spec.modpack_content = getModsInPath(spec.path, spec.virtual_path, true);
  75. return true;
  76. } else if (!fs::IsFile(spec.path + DIR_DELIM + "init.lua")) {
  77. return false;
  78. }
  79. Settings info;
  80. info.readConfigFile((spec.path + DIR_DELIM + "mod.conf").c_str());
  81. if (info.exists("name"))
  82. spec.name = info.get("name");
  83. else
  84. spec.deprecation_msgs.push_back("Mods not having a mod.conf file with the name is deprecated.");
  85. if (info.exists("author"))
  86. spec.author = info.get("author");
  87. if (info.exists("release"))
  88. spec.release = info.getS32("release");
  89. // Attempt to load dependencies from mod.conf
  90. bool mod_conf_has_depends = false;
  91. if (info.exists("depends")) {
  92. mod_conf_has_depends = true;
  93. std::string dep = info.get("depends");
  94. dep.erase(std::remove_if(dep.begin(), dep.end(),
  95. static_cast<int (*)(int)>(&std::isspace)), dep.end());
  96. for (const auto &dependency : str_split(dep, ',')) {
  97. spec.depends.insert(dependency);
  98. }
  99. }
  100. if (info.exists("optional_depends")) {
  101. mod_conf_has_depends = true;
  102. std::string dep = info.get("optional_depends");
  103. dep.erase(std::remove_if(dep.begin(), dep.end(),
  104. static_cast<int (*)(int)>(&std::isspace)), dep.end());
  105. for (const auto &dependency : str_split(dep, ',')) {
  106. spec.optdepends.insert(dependency);
  107. }
  108. }
  109. // Fallback to depends.txt
  110. if (!mod_conf_has_depends) {
  111. std::vector<std::string> dependencies;
  112. std::ifstream is((spec.path + DIR_DELIM + "depends.txt").c_str());
  113. if (is.good())
  114. spec.deprecation_msgs.push_back("depends.txt is deprecated, please use mod.conf instead.");
  115. while (is.good()) {
  116. std::string dep;
  117. std::getline(is, dep);
  118. dependencies.push_back(dep);
  119. }
  120. for (auto &dependency : dependencies) {
  121. std::unordered_set<char> symbols;
  122. if (parseDependsString(dependency, symbols)) {
  123. if (symbols.count('?') != 0) {
  124. spec.optdepends.insert(dependency);
  125. } else {
  126. spec.depends.insert(dependency);
  127. }
  128. }
  129. }
  130. }
  131. if (info.exists("description"))
  132. spec.desc = info.get("description");
  133. else if (fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc))
  134. spec.deprecation_msgs.push_back("description.txt is deprecated, please use mod.conf instead.");
  135. return true;
  136. }
  137. std::map<std::string, ModSpec> getModsInPath(
  138. const std::string &path, const std::string &virtual_path, bool part_of_modpack)
  139. {
  140. // NOTE: this function works in mutual recursion with parseModContents
  141. std::map<std::string, ModSpec> result;
  142. std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
  143. std::string mod_path;
  144. std::string mod_virtual_path;
  145. for (const fs::DirListNode &dln : dirlist) {
  146. if (!dln.dir)
  147. continue;
  148. const std::string &modname = dln.name;
  149. // Ignore all directories beginning with a ".", especially
  150. // VCS directories like ".git" or ".svn"
  151. if (modname[0] == '.')
  152. continue;
  153. mod_path.clear();
  154. mod_path.append(path).append(DIR_DELIM).append(modname);
  155. mod_virtual_path.clear();
  156. // Intentionally uses / to keep paths same on different platforms
  157. mod_virtual_path.append(virtual_path).append("/").append(modname);
  158. ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path);
  159. parseModContents(spec);
  160. result.insert(std::make_pair(modname, spec));
  161. }
  162. return result;
  163. }
  164. std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods)
  165. {
  166. std::vector<ModSpec> result;
  167. for (const auto &it : mods) {
  168. const ModSpec &mod = it.second;
  169. if (mod.is_modpack) {
  170. std::vector<ModSpec> content = flattenMods(mod.modpack_content);
  171. result.reserve(result.size() + content.size());
  172. result.insert(result.end(), content.begin(), content.end());
  173. } else // not a modpack
  174. {
  175. result.push_back(mod);
  176. }
  177. }
  178. return result;
  179. }
  180. ModStorage::ModStorage(const std::string &mod_name, ModStorageDatabase *database):
  181. m_mod_name(mod_name), m_database(database)
  182. {
  183. }
  184. void ModStorage::clear()
  185. {
  186. m_database->removeModEntries(m_mod_name);
  187. }
  188. bool ModStorage::contains(const std::string &name) const
  189. {
  190. return m_database->hasModEntry(m_mod_name, name);
  191. }
  192. bool ModStorage::setString(const std::string &name, std::string_view var)
  193. {
  194. if (var.empty())
  195. return m_database->removeModEntry(m_mod_name, name);
  196. else
  197. return m_database->setModEntry(m_mod_name, name, var);
  198. }
  199. const StringMap &ModStorage::getStrings(StringMap *place) const
  200. {
  201. place->clear();
  202. m_database->getModEntries(m_mod_name, place);
  203. return *place;
  204. }
  205. const std::vector<std::string> &ModStorage::getKeys(std::vector<std::string> *place) const
  206. {
  207. place->clear();
  208. m_database->getModKeys(m_mod_name, place);
  209. return *place;
  210. }
  211. const std::string *ModStorage::getStringRaw(const std::string &name, std::string *place) const
  212. {
  213. return m_database->getModEntry(m_mod_name, name, place) ? place : nullptr;
  214. }