subgames.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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 <common/c_internal.h>
  17. #include "content/subgames.h"
  18. #include "porting.h"
  19. #include "filesys.h"
  20. #include "settings.h"
  21. #include "log.h"
  22. #include "util/strfnd.h"
  23. #include "defaultsettings.h" // for set_default_settings
  24. #include "map_settings_manager.h"
  25. #include "util/string.h"
  26. #ifndef SERVER
  27. #include "client/texturepaths.h"
  28. #endif
  29. // The maximum number of identical world names allowed
  30. #define MAX_WORLD_NAMES 100
  31. namespace
  32. {
  33. bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
  34. {
  35. std::string conf_path = game_path + DIR_DELIM + "minetest.conf";
  36. return conf.readConfigFile(conf_path.c_str());
  37. }
  38. }
  39. void SubgameSpec::checkAndLog() const
  40. {
  41. // Log deprecation messages
  42. auto handling_mode = get_deprecated_handling_mode();
  43. if (!deprecation_msgs.empty() && handling_mode != DeprecatedHandlingMode::Ignore) {
  44. std::ostringstream os;
  45. os << "Game " << title << " at " << path << ":" << std::endl;
  46. for (auto msg : deprecation_msgs)
  47. os << "\t" << msg << std::endl;
  48. if (handling_mode == DeprecatedHandlingMode::Error)
  49. throw ModError(os.str());
  50. else
  51. warningstream << os.str();
  52. }
  53. }
  54. struct GameFindPath
  55. {
  56. std::string path;
  57. bool user_specific;
  58. GameFindPath(const std::string &path, bool user_specific) :
  59. path(path), user_specific(user_specific)
  60. {
  61. }
  62. };
  63. std::string getSubgamePathEnv()
  64. {
  65. static bool has_warned = false;
  66. char *subgame_path = getenv("MINETEST_SUBGAME_PATH");
  67. if (subgame_path && !has_warned) {
  68. warningstream << "MINETEST_SUBGAME_PATH is deprecated, use MINETEST_GAME_PATH instead."
  69. << std::endl;
  70. has_warned = true;
  71. }
  72. char *game_path = getenv("MINETEST_GAME_PATH");
  73. if (game_path)
  74. return std::string(game_path);
  75. else if (subgame_path)
  76. return std::string(subgame_path);
  77. return "";
  78. }
  79. SubgameSpec findSubgame(const std::string &id)
  80. {
  81. if (id.empty())
  82. return SubgameSpec();
  83. std::string share = porting::path_share;
  84. std::string user = porting::path_user;
  85. // Get games install locations
  86. Strfnd search_paths(getSubgamePathEnv());
  87. // Get all possible paths fo game
  88. std::vector<GameFindPath> find_paths;
  89. while (!search_paths.at_end()) {
  90. std::string path = search_paths.next(PATH_DELIM);
  91. path.append(DIR_DELIM).append(id);
  92. find_paths.emplace_back(path, false);
  93. path.append("_game");
  94. find_paths.emplace_back(path, false);
  95. }
  96. std::string game_base = DIR_DELIM;
  97. game_base = game_base.append("games").append(DIR_DELIM).append(id);
  98. std::string game_suffixed = game_base + "_game";
  99. find_paths.emplace_back(user + game_suffixed, true);
  100. find_paths.emplace_back(user + game_base, true);
  101. find_paths.emplace_back(share + game_suffixed, false);
  102. find_paths.emplace_back(share + game_base, false);
  103. // Find game directory
  104. std::string game_path;
  105. bool user_game = true; // Game is in user's directory
  106. for (const GameFindPath &find_path : find_paths) {
  107. const std::string &try_path = find_path.path;
  108. if (fs::PathExists(try_path)) {
  109. game_path = try_path;
  110. user_game = find_path.user_specific;
  111. break;
  112. }
  113. }
  114. if (game_path.empty())
  115. return SubgameSpec();
  116. std::string gamemod_path = game_path + DIR_DELIM + "mods";
  117. // Find mod directories
  118. std::unordered_map<std::string, std::string> mods_paths;
  119. mods_paths["mods"] = user + DIR_DELIM + "mods";
  120. if (!user_game && user != share)
  121. mods_paths["share"] = share + DIR_DELIM + "mods";
  122. for (const std::string &mod_path : getEnvModPaths()) {
  123. mods_paths[fs::AbsolutePath(mod_path)] = mod_path;
  124. }
  125. // Get meta
  126. std::string conf_path = game_path + DIR_DELIM + "game.conf";
  127. Settings conf;
  128. conf.readConfigFile(conf_path.c_str());
  129. std::string game_title;
  130. if (conf.exists("title"))
  131. game_title = conf.get("title");
  132. else if (conf.exists("name"))
  133. game_title = conf.get("name");
  134. else
  135. game_title = id;
  136. std::string game_author;
  137. if (conf.exists("author"))
  138. game_author = conf.get("author");
  139. int game_release = 0;
  140. if (conf.exists("release"))
  141. game_release = conf.getS32("release");
  142. std::string menuicon_path;
  143. #ifndef SERVER
  144. menuicon_path = getImagePath(
  145. game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
  146. #endif
  147. SubgameSpec spec(id, game_path, gamemod_path, mods_paths, game_title,
  148. menuicon_path, game_author, game_release);
  149. if (conf.exists("name") && !conf.exists("title"))
  150. spec.deprecation_msgs.push_back("\"name\" setting in game.conf is deprecated, please use \"title\" instead");
  151. return spec;
  152. }
  153. SubgameSpec findWorldSubgame(const std::string &world_path)
  154. {
  155. std::string world_gameid = getWorldGameId(world_path, true);
  156. // See if world contains an embedded game; if so, use it.
  157. std::string world_gamepath = world_path + DIR_DELIM + "game";
  158. if (fs::PathExists(world_gamepath)) {
  159. SubgameSpec gamespec;
  160. gamespec.id = world_gameid;
  161. gamespec.path = world_gamepath;
  162. gamespec.gamemods_path = world_gamepath + DIR_DELIM + "mods";
  163. Settings conf;
  164. std::string conf_path = world_gamepath + DIR_DELIM + "game.conf";
  165. conf.readConfigFile(conf_path.c_str());
  166. if (conf.exists("title"))
  167. gamespec.title = conf.get("title");
  168. else if (conf.exists("name"))
  169. gamespec.title = conf.get("name");
  170. else
  171. gamespec.title = world_gameid;
  172. return gamespec;
  173. }
  174. return findSubgame(world_gameid);
  175. }
  176. std::set<std::string> getAvailableGameIds()
  177. {
  178. std::set<std::string> gameids;
  179. std::set<std::string> gamespaths;
  180. gamespaths.insert(porting::path_share + DIR_DELIM + "games");
  181. gamespaths.insert(porting::path_user + DIR_DELIM + "games");
  182. Strfnd search_paths(getSubgamePathEnv());
  183. while (!search_paths.at_end())
  184. gamespaths.insert(search_paths.next(PATH_DELIM));
  185. for (const std::string &gamespath : gamespaths) {
  186. std::vector<fs::DirListNode> dirlist = fs::GetDirListing(gamespath);
  187. for (const fs::DirListNode &dln : dirlist) {
  188. if (!dln.dir)
  189. continue;
  190. // If configuration file is not found or broken, ignore game
  191. Settings conf;
  192. std::string conf_path = gamespath + DIR_DELIM + dln.name +
  193. DIR_DELIM + "game.conf";
  194. if (!conf.readConfigFile(conf_path.c_str()))
  195. continue;
  196. // Add it to result
  197. const char *ends[] = {"_game", NULL};
  198. auto shorter = removeStringEnd(dln.name, ends);
  199. if (!shorter.empty())
  200. gameids.emplace(shorter);
  201. else
  202. gameids.insert(dln.name);
  203. }
  204. }
  205. return gameids;
  206. }
  207. std::vector<SubgameSpec> getAvailableGames()
  208. {
  209. std::vector<SubgameSpec> specs;
  210. std::set<std::string> gameids = getAvailableGameIds();
  211. specs.reserve(gameids.size());
  212. for (const auto &gameid : gameids)
  213. specs.push_back(findSubgame(gameid));
  214. return specs;
  215. }
  216. #define LEGACY_GAMEID "minetest"
  217. bool getWorldExists(const std::string &world_path)
  218. {
  219. return (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt") ||
  220. fs::PathExists(world_path + DIR_DELIM + "world.mt"));
  221. }
  222. //! Try to get the displayed name of a world
  223. std::string getWorldName(const std::string &world_path, const std::string &default_name)
  224. {
  225. std::string conf_path = world_path + DIR_DELIM + "world.mt";
  226. Settings conf;
  227. bool succeeded = conf.readConfigFile(conf_path.c_str());
  228. if (!succeeded) {
  229. return default_name;
  230. }
  231. if (!conf.exists("world_name"))
  232. return default_name;
  233. return conf.get("world_name");
  234. }
  235. std::string getWorldGameId(const std::string &world_path, bool can_be_legacy)
  236. {
  237. std::string conf_path = world_path + DIR_DELIM + "world.mt";
  238. Settings conf;
  239. bool succeeded = conf.readConfigFile(conf_path.c_str());
  240. if (!succeeded) {
  241. if (can_be_legacy) {
  242. // If map_meta.txt exists, it is probably an old minetest world
  243. if (fs::PathExists(world_path + DIR_DELIM + "map_meta.txt"))
  244. return LEGACY_GAMEID;
  245. }
  246. return "";
  247. }
  248. if (!conf.exists("gameid"))
  249. return "";
  250. // The "mesetint" gameid has been discarded
  251. if (conf.get("gameid") == "mesetint")
  252. return "minetest";
  253. return conf.get("gameid");
  254. }
  255. std::string getWorldPathEnv()
  256. {
  257. char *world_path = getenv("MINETEST_WORLD_PATH");
  258. return world_path ? std::string(world_path) : "";
  259. }
  260. std::vector<WorldSpec> getAvailableWorlds()
  261. {
  262. std::vector<WorldSpec> worlds;
  263. std::set<std::string> worldspaths;
  264. Strfnd search_paths(getWorldPathEnv());
  265. while (!search_paths.at_end())
  266. worldspaths.insert(search_paths.next(PATH_DELIM));
  267. worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
  268. infostream << "Searching worlds..." << std::endl;
  269. for (const std::string &worldspath : worldspaths) {
  270. infostream << " In " << worldspath << ": ";
  271. std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
  272. for (const fs::DirListNode &dln : dirvector) {
  273. if (!dln.dir)
  274. continue;
  275. std::string fullpath = worldspath + DIR_DELIM + dln.name;
  276. std::string name = getWorldName(fullpath, dln.name);
  277. // Just allow filling in the gameid always for now
  278. bool can_be_legacy = true;
  279. std::string gameid = getWorldGameId(fullpath, can_be_legacy);
  280. WorldSpec spec(fullpath, name, gameid);
  281. if (!spec.isValid()) {
  282. infostream << "(invalid: " << name << ") ";
  283. } else {
  284. infostream << name << " ";
  285. worlds.push_back(spec);
  286. }
  287. }
  288. infostream << std::endl;
  289. }
  290. // Check old world location
  291. do {
  292. std::string fullpath = porting::path_user + DIR_DELIM + "world";
  293. if (!fs::PathExists(fullpath))
  294. break;
  295. std::string name = "Old World";
  296. std::string gameid = getWorldGameId(fullpath, true);
  297. WorldSpec spec(fullpath, name, gameid);
  298. infostream << "Old world found." << std::endl;
  299. worlds.push_back(spec);
  300. } while (false);
  301. infostream << worlds.size() << " found." << std::endl;
  302. return worlds;
  303. }
  304. void loadGameConfAndInitWorld(const std::string &path, const std::string &name,
  305. const SubgameSpec &gamespec, bool create_world)
  306. {
  307. std::string final_path = path;
  308. // If we're creating a new world, ensure that the path isn't already taken
  309. if (create_world) {
  310. int counter = 1;
  311. while (fs::PathExists(final_path) && counter < MAX_WORLD_NAMES) {
  312. final_path = path + "_" + std::to_string(counter);
  313. counter++;
  314. }
  315. if (fs::PathExists(final_path)) {
  316. throw BaseException("Too many similar filenames");
  317. }
  318. }
  319. Settings *game_settings = Settings::getLayer(SL_GAME);
  320. const bool new_game_settings = (game_settings == nullptr);
  321. if (new_game_settings) {
  322. // Called by main-menu without a Server instance running
  323. // -> create and free manually
  324. game_settings = Settings::createLayer(SL_GAME);
  325. }
  326. getGameMinetestConfig(gamespec.path, *game_settings);
  327. game_settings->removeSecureSettings();
  328. infostream << "Initializing world at " << final_path << std::endl;
  329. fs::CreateAllDirs(final_path);
  330. // Create world.mt if does not already exist
  331. std::string worldmt_path = final_path + DIR_DELIM "world.mt";
  332. if (!fs::PathExists(worldmt_path)) {
  333. Settings gameconf;
  334. std::string gameconf_path = gamespec.path + DIR_DELIM "game.conf";
  335. gameconf.readConfigFile(gameconf_path.c_str());
  336. Settings conf; // for world.mt
  337. conf.set("world_name", name);
  338. conf.set("gameid", gamespec.id);
  339. std::string backend = "sqlite3";
  340. if (gameconf.exists("map_persistent") && !gameconf.getBool("map_persistent")) {
  341. backend = "dummy";
  342. }
  343. conf.set("backend", backend);
  344. conf.set("player_backend", "sqlite3");
  345. conf.set("auth_backend", "sqlite3");
  346. conf.set("mod_storage_backend", "sqlite3");
  347. conf.setBool("creative_mode", g_settings->getBool("creative_mode"));
  348. conf.setBool("enable_damage", g_settings->getBool("enable_damage"));
  349. if (MAP_BLOCKSIZE != 16)
  350. conf.set("blocksize", std::to_string(MAP_BLOCKSIZE));
  351. if (!conf.updateConfigFile(worldmt_path.c_str())) {
  352. throw BaseException("Failed to update the config file");
  353. }
  354. }
  355. // Create map_meta.txt if does not already exist
  356. std::string map_meta_path = final_path + DIR_DELIM + "map_meta.txt";
  357. if (!fs::PathExists(map_meta_path)) {
  358. MapSettingsManager mgr(map_meta_path);
  359. mgr.setMapSetting("seed", g_settings->get("fixed_map_seed"));
  360. mgr.makeMapgenParams();
  361. mgr.saveMapMeta();
  362. }
  363. // The Settings object is no longer needed for created worlds
  364. if (new_game_settings)
  365. delete game_settings;
  366. }
  367. std::vector<std::string> getEnvModPaths()
  368. {
  369. const char *c_mod_path = getenv("MINETEST_MOD_PATH");
  370. std::vector<std::string> paths;
  371. Strfnd search_paths(c_mod_path ? c_mod_path : "");
  372. while (!search_paths.at_end())
  373. paths.push_back(search_paths.next(PATH_DELIM));
  374. return paths;
  375. }