1 /*
2    Copyright (C) 2013 - 2018 by Andrius Silinskas <silinskas.andrius@gmail.com>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 #include "game_config_manager.hpp"
15 
16 #include "about.hpp"
17 #include "addon/manager.hpp"
18 #include "ai/configuration.hpp"
19 #include "cursor.hpp"
20 #include "events.hpp"
21 #include "formatter.hpp"
22 #include "game_config.hpp"
23 #include "gettext.hpp"
24 #include "game_classification.hpp"
25 #include "gui/dialogs/loading_screen.hpp"
26 #include "gui/dialogs/wml_error.hpp"
27 #include "hotkey/hotkey_item.hpp"
28 #include "hotkey/hotkey_command.hpp"
29 #include "language.hpp"
30 #include "log.hpp"
31 #include "preferences/general.hpp"
32 #include "scripting/game_lua_kernel.hpp"
33 #include "terrain/builder.hpp"
34 #include "terrain/type_data.hpp"
35 #include "units/types.hpp"
36 #include "game_version.hpp"
37 #include "theme.hpp"
38 #include "picture.hpp"
39 
40 static lg::log_domain log_config("config");
41 #define ERR_CONFIG LOG_STREAM(err, log_config)
42 #define WRN_CONFIG LOG_STREAM(warn, log_config)
43 #define LOG_CONFIG LOG_STREAM(info, log_config)
44 
45 static game_config_manager * singleton;
46 
game_config_manager(const commandline_options & cmdline_opts,const bool jump_to_editor)47 game_config_manager::game_config_manager(
48 		const commandline_options& cmdline_opts,
49 		const bool jump_to_editor) :
50 	cmdline_opts_(cmdline_opts),
51 	jump_to_editor_(jump_to_editor),
52 	game_config_(),
53 	old_defines_map_(),
54 	paths_manager_(),
55 	cache_(game_config::config_cache::instance())
56 {
57 	assert(!singleton);
58 	singleton = this;
59 
60 	if(cmdline_opts_.nocache) {
61 		cache_.set_use_cache(false);
62 	}
63 	if(cmdline_opts_.validcache) {
64 		cache_.set_force_valid_cache(true);
65 	}
66 }
67 
~game_config_manager()68 game_config_manager::~game_config_manager()
69 {
70 	assert(singleton);
71 	singleton = nullptr;
72 }
73 
get()74 game_config_manager * game_config_manager::get() {
75 	return singleton;
76 }
77 
init_game_config(FORCE_RELOAD_CONFIG force_reload)78 bool game_config_manager::init_game_config(FORCE_RELOAD_CONFIG force_reload)
79 {
80 	// Add preproc defines according to the command line arguments.
81 	game_config::scoped_preproc_define multiplayer("MULTIPLAYER",
82 		cmdline_opts_.multiplayer);
83 	game_config::scoped_preproc_define test("TEST", bool(cmdline_opts_.test));
84 	game_config::scoped_preproc_define mptest("MP_TEST", cmdline_opts_.mptest);
85 	game_config::scoped_preproc_define editor("EDITOR", jump_to_editor_);
86 	game_config::scoped_preproc_define title_screen("TITLE_SCREEN",
87 		!cmdline_opts_.multiplayer && !cmdline_opts_.test && !jump_to_editor_);
88 
89 	game_config::reset_color_info();
90 	load_game_config_with_loadscreen(force_reload);
91 
92 	game_config::load_config(game_config_.child("game_config"));
93 
94 	hotkey::deactivate_all_scopes();
95 	hotkey::set_scope_active(hotkey::SCOPE_MAIN_MENU);
96 
97 	// Load the standard hotkeys, then apply any player customizations.
98 	hotkey::load_hotkeys(game_config(), true);
99 	preferences::load_hotkeys();
100 
101 	::init_textdomains(game_config());
102 	about::set_about(game_config());
103 	ai::configuration::init(game_config());
104 
105 	return true;
106 }
107 
108 namespace {
109 /// returns true if every define in special is also defined in general
map_includes(const preproc_map & general,const preproc_map & special)110 bool map_includes(const preproc_map& general, const preproc_map& special)
111 {
112 	for (const preproc_map::value_type& pair : special)
113 	{
114 		preproc_map::const_iterator it = general.find(pair.first);
115 		if (it == general.end() || it->second != pair.second) {
116 			return false;
117 		}
118 	}
119 	return true;
120 }
121 } // end anonymous namespace
122 
load_game_config_with_loadscreen(FORCE_RELOAD_CONFIG force_reload,game_classification const * classification)123 void game_config_manager::load_game_config_with_loadscreen(FORCE_RELOAD_CONFIG force_reload,
124 	game_classification const* classification)
125 {
126 	game_config::scoped_preproc_define debug_mode("DEBUG_MODE",
127 		game_config::debug || game_config::mp_debug);
128 
129 	// Game_config already holds requested config in memory.
130 	if (!game_config_.empty()) {
131 		if ((force_reload == NO_FORCE_RELOAD) && old_defines_map_ == cache_.get_preproc_map()) {
132 			return;
133 		}
134 		if ((force_reload == NO_INCLUDE_RELOAD) && map_includes(old_defines_map_, cache_.get_preproc_map())) {
135 			return;
136 		}
137 	}
138 
139 	gui2::dialogs::loading_screen::display([this, force_reload, classification]() {
140 		load_game_config(force_reload, classification);
141 	});
142 }
143 
load_game_config(FORCE_RELOAD_CONFIG force_reload,game_classification const * classification)144 void game_config_manager::load_game_config(FORCE_RELOAD_CONFIG force_reload,
145 	game_classification const* classification)
146 {
147 		// Make sure that 'debug mode' symbol is set
148 	// if command line parameter is selected
149 	// also if we're in multiplayer and actual debug mode is disabled.
150 
151 	// The loadscreen will erase the titlescreen.
152 	// NOTE: even without loadscreen, needed after MP lobby.
153 	try {
154 		// Read all game configs.
155 		// First we load all core configs, the mainline one and the ones from the addons.
156 		// Validate the cores and discard the invalid.
157 		// Then find the path to the selected core.
158 		// Load the selected core.
159 		// Handle terrains so that they are last loaded from the core.
160 		// Load every compatible addon.
161 		gui2::dialogs::loading_screen::progress(loading_stage::verify_cache);
162 		filesystem::data_tree_checksum();
163 		gui2::dialogs::loading_screen::progress(loading_stage::create_cache);
164 
165 		// Start transaction so macros are shared.
166 		game_config::config_cache_transaction main_transaction;
167 
168 		config cores_cfg;
169 		// Load mainline cores definition file.
170 		cache_.get_config(game_config::path + "/data/cores.cfg", cores_cfg);
171 
172 		// Append the $user_campaign_dir/*/cores.cfg files to the cores.
173 		std::vector<std::string> user_dirs;
174 		{
175 			const std::string user_campaign_dir = filesystem::get_addons_dir();
176 			std::vector<std::string> user_files;
177 			filesystem::get_files_in_dir(user_campaign_dir, &user_files, &user_dirs,
178 					filesystem::ENTIRE_FILE_PATH);
179 		}
180 		for (const std::string& umc : user_dirs) {
181 			const std::string cores_file = umc + "/cores.cfg";
182 			if (filesystem::file_exists(cores_file)) {
183 				config cores;
184 				cache_.get_config(cores_file, cores);
185 				cores_cfg.append(cores);
186 			}
187 		}
188 
189 		// Validate every core
190 		config valid_cores;
191 		bool current_core_valid = false;
192 		std::string wml_tree_root;
193 		for (const config& core : cores_cfg.child_range("core")) {
194 
195 			const std::string& id = core["id"];
196 			if (id.empty()) {
197 				events::call_in_main_thread([&]() {
198 					gui2::dialogs::wml_error::display(
199 						_("Error validating data core."),
200 						_("Found a core without id attribute.")
201 						+ '\n' +  _("Skipping the core."));
202 				});
203 				continue;
204 			}
205 			if (*&valid_cores.find_child("core", "id", id)) {
206 				events::call_in_main_thread([&]() {
207 					gui2::dialogs::wml_error::display(
208 						_("Error validating data core."),
209 						_("Core ID: ") + id
210 						+ '\n' + _("The ID is already in use.")
211 						+ '\n' + _("Skipping the core."));
212 				});
213 				continue;
214 			}
215 
216 			const std::string& path = core["path"];
217 			if (!filesystem::file_exists(filesystem::get_wml_location(path))) {
218 				events::call_in_main_thread([&]() {
219 					gui2::dialogs::wml_error::display(
220 						_("Error validating data core."),
221 						_("Core ID: ") + id
222 						+ '\n' + _("Core Path: ") + path
223 						+ '\n' + _("File not found.")
224 						+ '\n' + _("Skipping the core."));
225 				});
226 				continue;
227 			}
228 
229 			if (id == "default" && !current_core_valid) {
230 				wml_tree_root = path;
231 			}
232 			if (id == preferences::core_id()) {
233 				current_core_valid = true;
234 				wml_tree_root = path;
235 			}
236 
237 			valid_cores.add_child("core", core);  // append(core);
238 		}
239 
240 		if (!current_core_valid) {
241 			events::call_in_main_thread([&]() {
242 				gui2::dialogs::wml_error::display(
243 					_("Error loading core data."),
244 					_("Core ID: ") + preferences::core_id()
245 					+ '\n' + _("Error loading the core with named id.")
246 					+ '\n' + _("Falling back to the default core."));
247 			});
248 			preferences::set_core_id("default");
249 		}
250 
251 		// check if we have a valid default core which should always be the case.
252 		if (wml_tree_root.empty()) {
253 			events::call_in_main_thread([&]() {
254 				gui2::dialogs::wml_error::display(
255 					_("Error loading core data."),
256 					_("Can't locate the default core.")
257 					+ '\n' + _("The game will now exit."));
258 			});
259 			throw;
260 		}
261 
262 		// Load the selected core
263 		cache_.get_config(filesystem::get_wml_location(wml_tree_root), game_config_);
264 		game_config_.append(valid_cores);
265 
266 		main_transaction.lock();
267 
268 		// Put the gfx rules aside so that we can prepend the add-on
269 		// rules to them.
270 		config core_terrain_rules;
271 		core_terrain_rules.splice_children(game_config_, "terrain_graphics");
272 
273 		if (!game_config::no_addons && !cmdline_opts_.noaddons)
274 			load_addons_cfg();
275 
276 		// If multiplayer campaign is being loaded, [scenario] tags should
277 		// become [multiplayer] tags and campaign's id should be added to them
278 		// to allow to recognize which scenarios belongs to a loaded campaign.
279 		if (classification != nullptr) {
280 			if (const config& campaign = game_config().find_child("campaign", "id", classification->campaign))
281 			{
282 				const bool require_campaign = campaign["require_campaign"].to_bool(true);
283 				for (config& scenario : game_config_.child_range("scenario"))
284 				{
285 					scenario["require_scenario"] = require_campaign;
286 				}
287 			}
288 		}
289 
290 		// Extract the Lua scripts at toplevel.
291 		game_lua_kernel::extract_preload_scripts(game_config_);
292 		game_config_.clear_children("lua");
293 
294 		// Put the gfx rules back to game config.
295 		game_config_.splice_children(core_terrain_rules, "terrain_graphics");
296 
297 		set_multiplayer_hashes();
298 		set_unit_data();
299 		game_config::add_color_info(game_config_);
300 
301 		terrain_builder::set_terrain_rules_cfg(game_config());
302 		tdata_ = std::make_shared<terrain_type_data>(game_config_);
303 		::init_strings(game_config());
304 		theme::set_known_themes(&game_config());
305 	} catch(const game::error& e) {
306 		ERR_CONFIG << "Error loading game configuration files\n" << e.message << '\n';
307 
308 		// Try reloading without add-ons
309 		if (!game_config::no_addons) {
310 			game_config::no_addons = true;
311 			events::call_in_main_thread([&]() {
312 				gui2::dialogs::wml_error::display(
313 					_("Error loading custom game configuration files. The game will try without loading add-ons."),
314 					e.message);
315 			});
316 			load_game_config(force_reload, classification);
317 		} else if (preferences::core_id() != "default") {
318 			events::call_in_main_thread([&]() {
319 				gui2::dialogs::wml_error::display(
320 					_("Error loading custom game configuration files. The game will fallback to the default core files."),
321 					e.message);
322 			});
323 			preferences::set_core_id("default");
324 			game_config::no_addons = false;
325 			load_game_config(force_reload, classification);
326 		} else {
327 			events::call_in_main_thread([&]() {
328 				gui2::dialogs::wml_error::display(
329 					_("Error loading default core game configuration files. The game will now exit."),
330 					e.message);
331 			});
332 			throw;
333 		}
334 	}
335 
336 	old_defines_map_ = cache_.get_preproc_map();
337 
338 	// Set new binary paths.
339 	paths_manager_.set_paths(game_config());
340 }
341 
load_addons_cfg()342 void game_config_manager::load_addons_cfg()
343 {
344 	const std::string user_campaign_dir = filesystem::get_addons_dir();
345 
346 	std::vector<std::string> error_log;
347 	std::vector<std::string> error_addons;
348 	std::vector<std::string> user_dirs;
349 	std::vector<std::string> user_files;
350 
351 	filesystem::get_files_in_dir(user_campaign_dir, &user_files, &user_dirs,
352 		filesystem::ENTIRE_FILE_PATH);
353 
354 	// Warn player about addons using the no-longer-supported single-file format.
355 	for(const std::string& file : user_files) {
356 		const int size_minus_extension = file.size() - 4;
357 
358 		if(file.substr(size_minus_extension, file.size()) == ".cfg") {
359 			ERR_CONFIG << "error reading usermade add-on '" << file << "'\n";
360 
361 			error_addons.push_back(file);
362 
363 			const int userdata_loc = file.find("data/add-ons") + 5;
364 			const std::string log_msg = formatter()
365 				<< "The format '~"
366 				<< file.substr(userdata_loc)
367 				<< "' (for single-file add-ons) is not supported anymore, use '~"
368 				<< file.substr(userdata_loc, size_minus_extension - userdata_loc)
369 				<< "/_main.cfg' instead.";
370 
371 			error_log.push_back(log_msg);
372 		}
373 	}
374 
375 	// Rerun the directory scan using filename only, to get the addon_ids more easily.
376 	user_files.clear();
377 	user_dirs.clear();
378 
379 	filesystem::get_files_in_dir(user_campaign_dir, nullptr, &user_dirs,
380 		filesystem::FILE_NAME_ONLY);
381 
382 	// Load the addons.
383 	for(const std::string& addon_id : user_dirs) {
384 		log_scope2(log_config, "Loading add-on '" + addon_id + "'");
385 		const std::string addon_dir = user_campaign_dir + "/" + addon_id;
386 
387 		const std::string main_cfg = addon_dir + "/_main.cfg";
388 		const std::string info_cfg = addon_dir + "/_info.cfg";
389 
390 		if(!filesystem::file_exists(main_cfg)) {
391 			continue;
392 		}
393 
394 		// Try to find this addon's metadata. Author publishing info (_server.pbl) is given
395 		// precedence over addon sever-generated info (_info.cfg). If neither are found, it
396 		// probably means the addon was installed manually and certain defaults will be used.
397 		config metadata;
398 
399 		if(have_addon_pbl_info(addon_id)) {
400 			// Publishing info needs to be read from disk.
401 			try {
402 				metadata = get_addon_pbl_info(addon_id);
403 			} catch(const invalid_pbl_exception& e) {
404 				const std::string log_msg = formatter()
405 				<< "The provided addon has an invalid pbl file"
406 				<< " for addon "
407 				<< addon_id;
408 
409 				error_addons.push_back(e.message);
410 				error_log.push_back(log_msg);
411 			}
412 		} else if(filesystem::file_exists(info_cfg)) {
413 			// Addon server-generated info can be fetched from cache.
414 			config temp;
415 			cache_.get_config(info_cfg, temp);
416 
417 			metadata = temp.child_or_empty("info");
418 		}
419 
420 		std::string using_core = metadata["core"];
421 		if(using_core.empty()) {
422 			using_core = "default";
423 		}
424 
425 		// Skip add-ons not matching our current core. Cores themselves should be selectable
426 		// at all times, so they aren't considered here.
427 		if(!metadata.empty() && metadata["type"] != "core" && using_core != preferences::core_id()) {
428 			continue;
429 		}
430 
431 		std::string addon_title = metadata["title"].str();
432 		if(addon_title.empty()) {
433 			addon_title = addon_id;
434 		}
435 
436 		version_info addon_version(metadata["version"]);
437 
438 		try {
439 			// Load this addon from the cache to a config.
440 			config umc_cfg;
441 			cache_.get_config(main_cfg, umc_cfg);
442 
443 			static const std::set<std::string> tags_with_addon_id {
444 				"era",
445 				"modification",
446 				"resource",
447 				"multiplayer",
448 				"scenario",
449 				"campaign"
450 			};
451 
452 			// Annotate appropriate addon types with addon_id info.
453 			for(auto child : umc_cfg.all_children_range()) {
454 				if(tags_with_addon_id.count(child.key) > 0) {
455 					auto& cfg = child.cfg;
456 					cfg["addon_id"] = addon_id;
457 					cfg["addon_title"] = addon_title;
458 					// Note that this may reformat the string in a canonical form.
459 					cfg["addon_version"] = addon_version.str();
460 				}
461 			}
462 
463 			game_config_.append(std::move(umc_cfg));
464 		} catch(const config::error& err) {
465 			ERR_CONFIG << "error reading usermade add-on '" << main_cfg << "'" << std::endl;
466 			ERR_CONFIG << err.message << '\n';
467 			error_addons.push_back(main_cfg);
468 			error_log.push_back(err.message);
469 		} catch(const preproc_config::error& err) {
470 			ERR_CONFIG << "error reading usermade add-on '" << main_cfg << "'" << std::endl;
471 			ERR_CONFIG << err.message << '\n';
472 			error_addons.push_back(main_cfg);
473 			error_log.push_back(err.message);
474 		} catch(const filesystem::io_exception&) {
475 			ERR_CONFIG << "error reading usermade add-on '" << main_cfg << "'" << std::endl;
476 			error_addons.push_back(main_cfg);
477 		}
478 	}
479 
480 	if(!error_addons.empty()) {
481 		const size_t n = error_addons.size();
482 		const std::string& msg1 =
483 			_n("The following add-on had errors and could not be loaded:",
484 			   "The following add-ons had errors and could not be loaded:",
485 			   n);
486 		const std::string& msg2 =
487 			_n("Please report this to the author or maintainer of this add-on.",
488 			   "Please report this to the respective authors or maintainers of these add-ons.",
489 			   n);
490 
491 		const std::string& report = utils::join(error_log, "\n\n");
492 		events::call_in_main_thread([&]() {
493 			gui2::dialogs::wml_error::display(msg1, msg2, error_addons, report);
494 		});
495 	}
496 }
497 
set_multiplayer_hashes()498 void game_config_manager::set_multiplayer_hashes()
499 {
500 	config& hashes = game_config_.add_child("multiplayer_hashes");
501 	for (const config &ch : game_config_.child_range("multiplayer")) {
502 		hashes[ch["id"].str()] = ch.hash();
503 	}
504 }
505 
set_unit_data()506 void game_config_manager::set_unit_data()
507 {
508 	game_config_.merge_children("units");
509 	gui2::dialogs::loading_screen::progress(loading_stage::load_unit_types);
510 	if(config &units = game_config_.child("units")) {
511 		unit_types.set_config(units);
512 	}
513 }
514 
reload_changed_game_config()515 void game_config_manager::reload_changed_game_config()
516 {
517 	// Rebuild addon version info cache.
518 	refresh_addon_version_info_cache();
519 
520 	// Force a reload of configuration information.
521 	cache_.recheck_filetree_checksum();
522 	old_defines_map_.clear();
523 	filesystem::clear_binary_paths_cache();
524 	init_game_config(FORCE_RELOAD);
525 
526 	image::flush_cache();
527 }
528 
load_game_config_for_editor()529 void game_config_manager::load_game_config_for_editor()
530 {
531 	game_config::scoped_preproc_define editor("EDITOR");
532 	load_game_config_with_loadscreen(NO_FORCE_RELOAD);
533 }
534 
load_game_config_for_game(const game_classification & classification)535 void game_config_manager::load_game_config_for_game(
536 	const game_classification& classification)
537 {
538 	game_config::scoped_preproc_define difficulty(classification.difficulty,
539 		!classification.difficulty.empty());
540 	game_config::scoped_preproc_define campaign(classification.campaign_define,
541 		!classification.campaign_define.empty());
542 	game_config::scoped_preproc_define scenario(classification.scenario_define,
543 		!classification.scenario_define.empty());
544 	game_config::scoped_preproc_define era(classification.era_define,
545 		!classification.era_define.empty());
546 	game_config::scoped_preproc_define multiplayer("MULTIPLAYER",
547 		classification.campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER);
548 	game_config::scoped_preproc_define mptest("MP_TEST", cmdline_opts_.mptest &&
549 		classification.campaign_type == game_classification::CAMPAIGN_TYPE::MULTIPLAYER);
550 
551 	//
552 	// NOTE: these deques aren't used here, but the objects within are utilized as RAII helpers.
553 	//
554 
555 	typedef std::unique_ptr<game_config::scoped_preproc_define> define;
556 
557 	std::deque<define> extra_defines;
558 	for(const std::string& extra_define : classification.campaign_xtra_defines) {
559 		extra_defines.emplace_back(new game_config::scoped_preproc_define(extra_define));
560 	}
561 
562 	std::deque<define> modification_defines;
563 	for(const std::string& mod_define : classification.mod_defines) {
564 		modification_defines.emplace_back(new game_config::scoped_preproc_define(mod_define, !mod_define.empty()));
565 	}
566 
567 	try {
568 		load_game_config_with_loadscreen(NO_FORCE_RELOAD, &classification);
569 	} catch(const game::error&) {
570 		cache_.clear_defines();
571 
572 		std::deque<define> previous_defines;
573 		for(const preproc_map::value_type& preproc : old_defines_map_) {
574 			previous_defines.emplace_back(new game_config::scoped_preproc_define(preproc.first));
575 		}
576 
577 		load_game_config_with_loadscreen(NO_FORCE_RELOAD);
578 
579 		throw;
580 	}
581 
582 	// This needs to be done in the main thread since this function (load_game_config_for_game)
583 	// might be called from a loading screen worker thread (and currently is, in fact). If the
584 	// image cache is purged from the worker thread, there's a possibility for a data race where
585 	// the main thread accesses the image cache and the worker thread simultaneously clears it.
586 	events::call_in_main_thread([]() { image::flush_cache(); });
587 }
588 
load_game_config_for_create(bool is_mp,bool is_test)589 void game_config_manager::load_game_config_for_create(bool is_mp, bool is_test)
590 {
591 	game_config::scoped_preproc_define multiplayer("MULTIPLAYER", is_mp);
592 	game_config::scoped_preproc_define test("TEST", is_test);
593 	game_config::scoped_preproc_define mptest("MP_TEST", cmdline_opts_.mptest && is_mp);
594 ///During an mp game the default difficuly define is also defined so better already load it now if we alreeady must reload config cache.
595 	game_config::scoped_preproc_define normal(DEFAULT_DIFFICULTY, !map_includes(old_defines_map_, cache_.get_preproc_map()));
596 
597 	typedef std::unique_ptr<game_config::scoped_preproc_define> define;
598 	try{
599 		load_game_config_with_loadscreen(NO_INCLUDE_RELOAD);
600 	}
601 	catch(const game::error&) {
602 		cache_.clear_defines();
603 
604 		std::deque<define> previous_defines;
605 		for (const preproc_map::value_type& preproc : old_defines_map_) {
606 			previous_defines.emplace_back(new game_config::scoped_preproc_define(preproc.first));
607 		}
608 
609 		load_game_config_with_loadscreen(NO_FORCE_RELOAD);
610 
611 		throw;
612 	}
613 }
614