The Battle for Wesnoth  1.19.0+dev
wesnoth.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "addon/manager.hpp"
17 #include "build_info.hpp"
18 #include "commandline_argv.hpp"
19 #include "commandline_options.hpp" // for commandline_options, etc
20 #include "config.hpp" // for config, config::error, etc
21 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
22 #include "filesystem.hpp" // for filesystem::file_exists, filesystem::io_exception, etc
23 #include "floating_label.hpp"
24 #include "font/error.hpp" // for error
25 #include "font/font_config.hpp" // for load_font_config, etc
26 #include "formula/formula.hpp" // for formula_error
27 #include "game_config.hpp" // for path, debug, debug_lua, etc
28 #include "game_config_manager.hpp" // for game_config_manager, etc
29 #include "game_end_exceptions.hpp"
30 #include "game_launcher.hpp" // for game_launcher, etc
31 #include "gettext.hpp"
32 #include "gui/core/event/handler.hpp" // for tmanager
34 #include "gui/dialogs/message.hpp" // for show_error_message
36 #include "gui/dialogs/title_screen.hpp" // for title_screen, etc
37 #include "gui/gui.hpp" // for init
38 #include "log.hpp" // for LOG_STREAM, general, logger, etc
42 #include "sdl/exception.hpp" // for exception
43 #include "serialization/binary_or_text.hpp" // for config_writer
44 #include "serialization/parser.hpp" // for read
45 #include "serialization/preprocessor.hpp" // for preproc_define, etc
46 #include "serialization/schema_validator.hpp" // for strict_validation_enabled and schema_validator
47 #include "sound.hpp" // for commit_music_changes, etc
48 #include "formula/string_utils.hpp" // VGETTEXT
49 #include <functional>
50 #include "game_version.hpp" // for version_info
51 #include "video.hpp" // for video::error and video::quit
52 #include "wesconfig.h" // for PACKAGE
53 #include "widgets/button.hpp" // for button
54 #include "wml_exception.hpp" // for wml_exception
55 
56 #ifdef _WIN32
57 #include "log_windows.hpp"
58 
59 #include <float.h>
60 #endif // _WIN32
61 
62 #ifndef _MSC_VER
63 #include <fenv.h>
64 #endif // _MSC_VER
65 
66 #include <SDL2/SDL.h> // for SDL_Init, SDL_INIT_TIMER
67 
68 #include <boost/program_options/errors.hpp> // for error
69 #include <boost/algorithm/string/predicate.hpp> // for checking cmdline options
70 #include <optional>
71 
72 #include <algorithm> // for transform
73 #include <cerrno> // for ENOMEM
74 #include <clocale> // for setlocale, LC_ALL, etc
75 #include <cstdio> // for remove, fprintf, stderr
76 #include <cstdlib> // for srand, exit
77 #include <ctime> // for time, ctime, std::time_t
78 #include <exception> // for exception
79 #include <vector>
80 #include <iostream>
81 
82 //#define NO_CATCH_AT_GAME_END
83 
84 #ifdef _WIN32
85 
86 #ifdef INADDR_ANY
87 #undef INADDR_ANY
88 #endif
89 
90 #ifdef INADDR_BROADCAST
91 #undef INADDR_BROADCAST
92 #endif
93 
94 #ifdef INADDR_NONE
95 #undef INADDR_NONE
96 #endif
97 
98 #include <windows.h>
99 
100 #endif // _WIN32
101 
102 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
103 #include "gui/widgets/debug.hpp"
104 #endif
105 
106 static lg::log_domain log_config("config");
107 #define LOG_CONFIG LOG_STREAM(info, log_config)
108 
109 #define LOG_GENERAL LOG_STREAM(info, lg::general())
110 
111 static lg::log_domain log_preprocessor("preprocessor");
112 #define LOG_PREPROC LOG_STREAM(info, log_preprocessor)
113 
114 // this is needed to allow identical functionality with clean refactoring
115 // play_game only returns on an error, all returns within play_game can
116 // be replaced with this
117 static void safe_exit(int res)
118 {
119  LOG_GENERAL << "exiting with code " << res;
120  exit(res);
121 }
122 
123 static void handle_preprocess_command(const commandline_options& cmdline_opts)
124 {
125  preproc_map input_macros;
126 
127  if(cmdline_opts.preprocess_input_macros) {
128  std::string file = *cmdline_opts.preprocess_input_macros;
129  if(filesystem::file_exists(file) == false) {
130  PLAIN_LOG << "please specify an existing file. File " << file << " doesn't exist.";
131  return;
132  }
133 
134  PLAIN_LOG << SDL_GetTicks() << " Reading cached defines from: " << file;
135 
136  config cfg;
137 
138  try {
140  read(cfg, *stream);
141  } catch(const config::error& e) {
142  PLAIN_LOG << "Caught a config error while parsing file '" << file << "':\n" << e.message;
143  }
144 
145  int read = 0;
146 
147  // use static preproc_define::read_pair(config) to make a object
148  for(const config::any_child value : cfg.all_children_range()) {
149  const preproc_map::value_type def = preproc_define::read_pair(value.cfg);
150  input_macros[def.first] = def.second;
151  ++read;
152  }
153 
154  PLAIN_LOG << SDL_GetTicks() << " Read " << read << " defines.";
155  }
156 
157  const std::string resourceToProcess(*cmdline_opts.preprocess_path);
158  const std::string targetDir(*cmdline_opts.preprocess_target);
159 
160  uint32_t startTime = SDL_GetTicks();
161 
162  // If the users add the SKIP_CORE define we won't preprocess data/core
163  bool skipCore = false;
164  bool skipTerrainGFX = false;
165 
166  // The 'core_defines_map' is the one got from data/core macros
167  preproc_map defines_map(input_macros);
168 
169  if(cmdline_opts.preprocess_defines) {
170  // add the specified defines
171  for(const std::string& define : *cmdline_opts.preprocess_defines) {
172  if(define.empty()) {
173  PLAIN_LOG << "empty define supplied";
174  continue;
175  }
176 
177  LOG_PREPROC << "adding define: " << define;
178  defines_map.emplace(define, preproc_define(define));
179 
180  if(define == "SKIP_CORE") {
181  PLAIN_LOG << "'SKIP_CORE' defined.";
182  skipCore = true;
183  } else if(define == "NO_TERRAIN_GFX") {
184  PLAIN_LOG << "'NO_TERRAIN_GFX' defined.";
185  skipTerrainGFX = true;
186  }
187  }
188  }
189 
190  // add the WESNOTH_VERSION define
191  defines_map["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
192 
193  PLAIN_LOG << "added " << defines_map.size() << " defines.";
194 
195  // preprocess core macros first if we don't skip the core
196  if(skipCore == false) {
197  PLAIN_LOG << "preprocessing common macros from 'data/core' ...";
198 
199  // process each folder explicitly to gain speed
200  preprocess_resource(game_config::path + "/data/core/macros", &defines_map);
201 
202  if(skipTerrainGFX == false) {
203  preprocess_resource(game_config::path + "/data/core/terrain-graphics", &defines_map);
204  }
205 
206  PLAIN_LOG << "acquired " << (defines_map.size() - input_macros.size()) << " 'data/core' defines.";
207  } else {
208  PLAIN_LOG << "skipped 'data/core'";
209  }
210 
211  // preprocess resource
212  PLAIN_LOG << "preprocessing specified resource: " << resourceToProcess << " ...";
213 
214  preprocess_resource(resourceToProcess, &defines_map, true, true, targetDir);
215  PLAIN_LOG << "acquired " << (defines_map.size() - input_macros.size()) << " total defines.";
216 
217  if(cmdline_opts.preprocess_output_macros) {
218  std::string outputFileName = "_MACROS_.cfg";
219  if(!cmdline_opts.preprocess_output_macros->empty()) {
220  outputFileName = *cmdline_opts.preprocess_output_macros;
221  }
222 
223  std::string outputPath = targetDir + "/" + outputFileName;
224 
225  PLAIN_LOG << "writing '" << outputPath << "' with " << defines_map.size() << " defines.";
226 
228  if(!out->fail()) {
229  config_writer writer(*out, false);
230 
231  for(auto& define_pair : defines_map) {
232  define_pair.second.write(writer, define_pair.first);
233  }
234  } else {
235  PLAIN_LOG << "couldn't open the file.";
236  }
237  }
238 
239  PLAIN_LOG << "preprocessing finished. Took " << SDL_GetTicks() - startTime << " ticks.";
240 }
241 
242 static int handle_validate_command(const std::string& file, abstract_validator& validator, const std::vector<std::string>& defines) {
243  preproc_map defines_map;
244  // add the WESNOTH_VERSION define
245  defines_map["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
246  defines_map["SCHEMA_VALIDATION"] = preproc_define();
247  for(const std::string& define : defines) {
248  if(define.empty()) {
249  PLAIN_LOG << "empty define supplied";
250  continue;
251  }
252 
253  LOG_PREPROC << "adding define: " << define;
254  defines_map.emplace(define, preproc_define(define));
255  }
256  PLAIN_LOG << "Validating " << file << " against schema " << validator.name_;
258  filesystem::scoped_istream stream = preprocess_file(file, &defines_map);
259  config result;
260  read(result, *stream, &validator);
261  if(lg::broke_strict()) {
262  std::cout << "validation failed\n";
263  } else {
264  std::cout << "validation succeeded\n";
265  }
266  return lg::broke_strict();
267 }
268 
269 /** Process commandline-arguments */
270 static int process_command_args(const commandline_options& cmdline_opts)
271 {
272  // Options that don't change behavior based on any others should be checked alphabetically below.
273 
274  if(cmdline_opts.log) {
275  for(const auto& log_pair : *cmdline_opts.log) {
276  const std::string log_domain = log_pair.second;
277  const lg::severity severity = log_pair.first;
278  if(!lg::set_log_domain_severity(log_domain, severity)) {
279  PLAIN_LOG << "unknown log domain: " << log_domain;
280  return 2;
281  }
282  }
283  }
284 
285  if(cmdline_opts.usercache_dir) {
287  }
288 
289  if(cmdline_opts.usercache_path) {
290  std::cout << filesystem::get_cache_dir();
291  return 0;
292  }
293 
294  if(cmdline_opts.userconfig_dir) {
296  }
297 
298  if(cmdline_opts.userconfig_path) {
299  std::cout << filesystem::get_user_config_dir();
300  return 0;
301  }
302 
303  if(cmdline_opts.userdata_dir) {
305  }
306 
307  if(cmdline_opts.userdata_path) {
308  std::cout << filesystem::get_user_data_dir();
309  return 0;
310  }
311 
312  if(cmdline_opts.data_dir) {
313  const std::string datadir = *cmdline_opts.data_dir;
314  PLAIN_LOG << "Starting with directory: '" << datadir << "'";
315 #ifdef _WIN32
316  // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
317  if(datadir.c_str()[1] == ':') {
318 #else
319  if(datadir[0] == '/') {
320 #endif
321  game_config::path = datadir;
322  } else {
323  game_config::path = filesystem::get_cwd() + '/' + datadir;
324  }
325 
326  PLAIN_LOG << "Now have with directory: '" << game_config::path << "'";
328  if(!cmdline_opts.nobanner) {
329  PLAIN_LOG << "Overriding data directory with '" << game_config::path << "'";
330  }
331 
333  PLAIN_LOG << "Could not find directory '" << game_config::path << "'";
334  throw config::error("directory not found");
335  }
336 
337  // don't update font as we already updating it in game ctor
338  // font_manager_.update_font_path();
339  }
340 
341  if(cmdline_opts.data_path) {
342  std::cout << game_config::path;
343  return 0;
344  }
345 
346  if(cmdline_opts.debug_lua) {
347  game_config::debug_lua = true;
348  }
349 
350  if(cmdline_opts.allow_insecure) {
352  }
353 
354  if(cmdline_opts.strict_lua) {
356  }
357 
358  if(cmdline_opts.help) {
359  std::cout << cmdline_opts;
360  return 0;
361  }
362 
363  if(cmdline_opts.logdomains) {
364  std::cout << lg::list_log_domains(*cmdline_opts.logdomains);
365  return 0;
366  }
367 
368  if(cmdline_opts.log_precise_timestamps) {
370  }
371 
372  if(cmdline_opts.rng_seed) {
373  srand(*cmdline_opts.rng_seed);
374  }
375 
376  if(cmdline_opts.render_image) {
377  SDL_setenv("SDL_VIDEODRIVER", "dummy", 1);
378  }
379 
380  if(cmdline_opts.strict_validation) {
382  }
383 
384  if(cmdline_opts.version) {
385  std::cout << "Battle for Wesnoth" << " " << game_config::wesnoth_version.str() << "\n\n";
386  std::cout << "Library versions:\n" << game_config::library_versions_report() << '\n';
387  std::cout << "Optional features:\n" << game_config::optional_features_report();
388 
389  return 0;
390  }
391 
392  if(cmdline_opts.simple_version) {
393  std::cout << game_config::wesnoth_version.str() << "\n";
394 
395  return 0;
396  }
397 
398  if(cmdline_opts.report) {
399  std::cout << "\n========= BUILD INFORMATION =========\n\n" << game_config::full_build_report();
400  return 0;
401  }
402 
403  if(cmdline_opts.validate_schema) {
405  validator.set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
406  return handle_validate_command(*cmdline_opts.validate_schema, validator, {});
407  }
408 
409  if(cmdline_opts.do_diff) {
410  config left, right;
411  std::ifstream in_left(cmdline_opts.diff_left);
412  std::ifstream in_right(cmdline_opts.diff_right);
413  read(left, in_left);
414  read(right, in_right);
415  std::ostream* os = &std::cout;
416  if(cmdline_opts.output_file) {
417  os = new std::ofstream(*cmdline_opts.output_file);
418  }
420  out.write(right.get_diff(left));
421  if(os != &std::cout) delete os;
422  return 0;
423  }
424 
425  if(cmdline_opts.do_patch) {
426  config base, diff;
427  std::ifstream in_base(cmdline_opts.diff_left);
428  std::ifstream in_diff(cmdline_opts.diff_right);
429  read(base, in_base);
430  read(diff, in_diff);
431  base.apply_diff(diff);
432  std::ostream* os = &std::cout;
433  if(cmdline_opts.output_file) {
434  os = new std::ofstream(*cmdline_opts.output_file);
435  }
437  out.write(base);
438  if(os != &std::cout) delete os;
439  return 0;
440  }
441 
442  // Options changing their behavior dependent on some others should be checked below.
443 
444  if(cmdline_opts.preprocess) {
445  handle_preprocess_command(cmdline_opts);
446  return 0;
447  }
448 
449  if(cmdline_opts.validate_wml) {
450  std::string schema_path;
451  if(cmdline_opts.validate_with) {
452  schema_path = *cmdline_opts.validate_with;
453  if(!filesystem::file_exists(schema_path)) {
454  auto check = filesystem::get_wml_location(schema_path);
455  if(!filesystem::file_exists(check)) {
456  PLAIN_LOG << "Could not find schema file: " << schema_path;
457  } else {
458  schema_path = check;
459  }
460  } else {
461  schema_path = filesystem::normalize_path(schema_path);
462  }
463  } else {
464  schema_path = filesystem::get_wml_location("schema/game_config.cfg");
465  }
466  schema_validation::schema_validator validator(schema_path);
467  validator.set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
468  return handle_validate_command(*cmdline_opts.validate_wml, validator,
469  cmdline_opts.preprocess_defines.value_or<decltype(cmdline_opts.preprocess_defines)::value_type>({}));
470  }
471 
472  if(cmdline_opts.preprocess_defines || cmdline_opts.preprocess_input_macros || cmdline_opts.preprocess_path) {
473  // It would be good if this was supported for running tests too, possibly for other uses.
474  // For the moment show an error message instead of leaving the user wondering why it doesn't work.
475  PLAIN_LOG << "That --preprocess-* option is only supported when using --preprocess or --validate-wml.";
476  // Return an error status other than -1, because in our caller -1 means no error
477  return -2;
478  }
479 
480  // Not the most intuitive solution, but I wanted to leave current semantics for now
481  return -1;
482 }
483 
484 /**
485  * I would prefer to setup locale first so that early error
486  * messages can get localized, but we need the game_launcher
487  * initialized to have filesystem::get_intl_dir() to work. Note: setlocale()
488  * does not take GUI language setting into account.
489  */
490 static void init_locale()
491 {
492 #if defined _WIN32 || defined __APPLE__
493  setlocale(LC_ALL, "English");
494 #else
495  std::setlocale(LC_ALL, "C");
496 #endif
497 
498  const std::string& intl_dir = filesystem::get_intl_dir();
499 
500  translation::bind_textdomain(PACKAGE, intl_dir.c_str(), "UTF-8");
501  translation::bind_textdomain(PACKAGE "-lib", intl_dir.c_str(), "UTF-8");
503 }
504 
505 /**
506  * Print an alert and instructions to stderr about early initialization errors.
507  *
508  * This is provided as an aid for users dealing with potential data dir
509  * configuration issues. The first code to read core WML *has* the
510  * responsibility to call this function in the event of a problem, to inform
511  * the user of the most likely possible cause and suggest a course of action
512  * to solve the issue.
513  */
515 {
516  // NOTE: wrap output to 80 columns.
517  PLAIN_LOG << '\n'
518  << "An error at this point during initialization usually indicates that the data\n"
519  << "directory above was not correctly set or detected. Try passing the correct path\n"
520  << "in the command line with the --data-dir switch or as the only argument.";
521 }
522 
523 /**
524  * Handles the lua script command line arguments if present.
525  * This function will only run once.
526  */
528 {
529  static bool first_time = true;
530 
531  if(!first_time) {
532  return;
533  }
534 
535  first_time = false;
536 
537  if(!game->init_lua_script()) {
538  // PLAIN_LOG << "error when loading lua scripts at startup";
539  // PLAIN_LOG << "could not load lua script: " << *cmdline_opts.script_file;
540  }
541 }
542 
543 #ifdef _MSC_VER
544 static void check_fpu()
545 {
546  uint32_t f_control;
547 
548  if(_controlfp_s(&f_control, 0, 0) == 0) {
549  uint32_t unused;
550  uint32_t rounding_mode = f_control & _MCW_RC;
551 
552  if(rounding_mode != _RC_NEAR) {
553  PLAIN_LOG << "Floating point rounding mode is currently '"
554  << ((rounding_mode == _RC_CHOP)
555  ? "chop"
556  : (rounding_mode == _RC_UP)
557  ? "up"
558  : (rounding_mode == _RC_DOWN)
559  ? "down"
560  : (rounding_mode == _RC_NEAR) ? "near" : "unknown")
561  << "' setting to 'near'";
562 
563  if(_controlfp_s(&unused, _RC_NEAR, _MCW_RC)) {
564  PLAIN_LOG << "failed to set floating point rounding type to 'near'";
565  }
566  }
567 
568 #ifndef _M_AMD64
569  uint32_t precision_mode = f_control & _MCW_PC;
570  if(precision_mode != _PC_53) {
571  PLAIN_LOG << "Floating point precision mode is currently '"
572  << ((precision_mode == _PC_53)
573  ? "double"
574  : (precision_mode == _PC_24)
575  ? "single"
576  : (precision_mode == _PC_64) ? "double extended" : "unknown")
577  << "' setting to 'double'";
578 
579  if(_controlfp_s(&unused, _PC_53, _MCW_PC)) {
580  PLAIN_LOG << "failed to set floating point precision type to 'double'";
581  }
582  }
583 #endif
584 
585  } else {
586  PLAIN_LOG << "_controlfp_s failed.";
587  }
588 }
589 #else
590 static void check_fpu()
591 {
592  switch(fegetround()) {
593  case FE_TONEAREST:
594  break;
595  case FE_DOWNWARD:
596  STREAMING_LOG << "Floating point precision mode is currently 'downward'";
597  goto reset_fpu;
598  case FE_TOWARDZERO:
599  STREAMING_LOG << "Floating point precision mode is currently 'toward-zero'";
600  goto reset_fpu;
601  case FE_UPWARD:
602  STREAMING_LOG << "Floating point precision mode is currently 'upward'";
603  goto reset_fpu;
604  default:
605  STREAMING_LOG << "Floating point precision mode is currently 'unknown'";
606  goto reset_fpu;
607  reset_fpu:
608  STREAMING_LOG << " - setting to 'nearest'\n";
609  fesetround(FE_TONEAREST);
610  break;
611  }
612 }
613 #endif
614 
615 /**
616  * Setups the game environment and enters
617  * the titlescreen or game loops.
618  */
619 static int do_gameloop(const std::vector<std::string>& args)
620 {
621  srand(std::time(nullptr));
622 
623  commandline_options cmdline_opts = commandline_options(args);
624 
625  int finished = process_command_args(cmdline_opts);
626  if(finished != -1) {
627 #ifdef _WIN32
628  if(lg::using_own_console()) {
629  std::cerr << "Press enter to continue..." << std::endl;
630  std::cin.get();
631  }
632 #endif
633 
634  return finished;
635  }
636 
637  const auto game = std::make_unique<game_launcher>(cmdline_opts);
638  const int start_ticks = SDL_GetTicks();
639 
640  init_locale();
641 
642  bool res;
643 
644  // Do initialize fonts before reading the game config, to have game
645  // config error messages displayed. fonts will be re-initialized later
646  // when the language is read from the game config.
647  res = font::load_font_config();
648  if(res == false) {
649  PLAIN_LOG << "could not initialize fonts";
650  // The most common symptom of a bogus data dir path -- warn the user.
652  return 1;
653  }
654 
655  res = game->init_language();
656  if(res == false) {
657  PLAIN_LOG << "could not initialize the language";
658  return 1;
659  }
660 
661  res = game->init_video();
662  if(res == false) {
663  PLAIN_LOG << "could not initialize display";
664  return 1;
665  }
666 
667  check_fpu();
668  const cursor::manager cursor_manager;
670 
671 #if(defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
672  SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
673 #endif
674 
675  gui2::init();
676  const gui2::event::manager gui_event_manager;
677 
678  // if the log directory is not writable, then this is the error condition so show the error message.
679  // if the log directory is writable, then there's no issue.
680  // if the optional isn't set, then logging to file has been disabled, so there's no issue.
681  if(!lg::log_dir_writable().value_or(true)) {
682  utils::string_map symbols;
683  symbols["logdir"] = filesystem::get_logs_dir();
684  std::string msg = VGETTEXT("Unable to create log files in directory $logdir. This is often caused by incorrect folder permissions, anti-virus software restricting folder access, or using OneDrive to manage your My Documents folder.", symbols);
686  }
687 
688  game_config_manager config_manager(cmdline_opts);
689 
693  }
694 
695  gui2::dialogs::loading_screen::display([&res, &config_manager, &cmdline_opts]() {
698 
699  if(res == false) {
700  PLAIN_LOG << "could not initialize game config";
701  return;
702  }
703 
705 
706  res = font::load_font_config();
707  if(res == false) {
708  PLAIN_LOG << "could not re-initialize fonts for the current language";
709  return;
710  }
711 
712  if(!game_config::no_addons && !cmdline_opts.noaddons) {
714 
716  }
717  });
718 
719  if(res == false) {
720  return 1;
721  }
722 
723  LOG_CONFIG << "time elapsed: " << (SDL_GetTicks() - start_ticks) << " ms";
724 
725  plugins_manager plugins_man(new application_lua_kernel);
726 
727  const plugins_context::reg_vec callbacks {
728  {"play_multiplayer", std::bind(&game_launcher::play_multiplayer, game.get(), game_launcher::mp_mode::CONNECT)},
729  };
730 
731  const plugins_context::areg_vec accessors {
732  {"command_line", std::bind(&commandline_options::to_config, &cmdline_opts)},
733  };
734 
735  plugins_context plugins("titlescreen", callbacks, accessors);
736 
737  plugins.set_callback("exit", [](const config& cfg) { safe_exit(cfg["code"].to_int(0)); }, false);
738 
739  while(true) {
740  if(!game->has_load_data()) {
741  auto cfg = config_manager.game_config().optional_child("titlescreen_music");
742  if(cfg) {
743  for(const config& i : cfg->child_range("music")) {
745  }
746 
747  config title_music_config;
748  title_music_config["name"] = game_config::title_music;
749  title_music_config["append"] = true;
750  title_music_config["immediate"] = true;
751  sound::play_music_config(title_music_config);
752  } else {
755  }
756  }
757 
758  handle_lua_script_args(&*game, cmdline_opts);
759 
760  plugins.play_slice();
761  plugins.play_slice();
762 
763  if(!cmdline_opts.unit_test.empty()) {
764  return static_cast<int>(game->unit_test());
765  }
766 
767  if(game->play_test() == false) {
768  return 0;
769  }
770 
771  if(game->play_screenshot_mode() == false) {
772  return 0;
773  }
774 
775  if(game->play_render_image_mode() == false) {
776  return 0;
777  }
778 
779  // Start directly a campaign
780  if(game->goto_campaign() == false) {
781  if(game->jump_to_campaign_id().empty())
782  continue; // Go to main menu
783  else
784  return 1; // we got an error starting the campaign from command line
785  }
786 
787  // Start directly a multiplayer
788  // Eventually with a specified server
789  if(game->goto_multiplayer() == false) {
790  continue; // Go to main menu
791  }
792 
793  // Start directly a commandline multiplayer game
794  if(game->play_multiplayer_commandline() == false) {
795  return 0;
796  }
797 
798  if(game->goto_editor() == false) {
799  return 0;
800  }
801 
802  const font::floating_label_context label_manager;
803 
805 
806  // If loading a game, skip the titlescreen entirely
807  if(game->has_load_data() && game->load_game()) {
809  continue;
810  }
811 
812  int retval;
813  { // scope to not keep the title screen alive all game
815 
816  // Allows re-layout on resize
818  dlg.show();
819  }
820  retval = dlg.get_retval();
821  }
822 
823  switch(retval) {
825  LOG_GENERAL << "quitting game...";
826  return 0;
829  game->play_multiplayer(game_launcher::mp_mode::CONNECT);
830  break;
833  game->play_multiplayer(game_launcher::mp_mode::HOST);
834  break;
837  game->play_multiplayer(game_launcher::mp_mode::LOCAL);
838  break;
840  gui2::dialogs::loading_screen::display([&config_manager]() {
841  config_manager.reload_changed_game_config();
842  });
843  break;
845  game->start_editor();
846  break;
849  break;
851  break;
852  }
853  }
854 }
855 
856 /**
857  * Try to autodetect the location of the game data dir. Note that
858  * the root of the source tree currently doubles as the data dir.
859  */
860 static std::string autodetect_game_data_dir(std::string exe_dir)
861 {
862  std::string auto_dir;
863 
864  // scons leaves the resulting binaries at the root of the source
865  // tree by default.
866  if(filesystem::file_exists(exe_dir + "/data/_main.cfg")) {
867  auto_dir = std::move(exe_dir);
868  }
869  // cmake encourages creating a subdir at the root of the source
870  // tree for the build, and the resulting binaries are found in it.
871  else if(filesystem::file_exists(exe_dir + "/../data/_main.cfg")) {
872  auto_dir = filesystem::normalize_path(exe_dir + "/..");
873  }
874  // Allow using the current working directory as the game data dir
875  else if(filesystem::file_exists(filesystem::get_cwd() + "/data/_main.cfg")) {
876  auto_dir = filesystem::get_cwd();
877  }
878 #ifdef _WIN32
879  // In Windows builds made using Visual Studio and its CMake
880  // integration, the EXE is placed a few levels below the game data
881  // dir (e.g. .\out\build\x64-Debug).
882  else if(filesystem::file_exists(exe_dir + "/../../build") && filesystem::file_exists(exe_dir + "/../../../out")
883  && filesystem::file_exists(exe_dir + "/../../../data/_main.cfg")) {
884  auto_dir = filesystem::normalize_path(exe_dir + "/../../..");
885  }
886 #endif
887 
888  return auto_dir;
889 }
890 
891 #ifdef _WIN32
892 #define error_exit(res) \
893  do { \
894  if(lg::using_own_console()) { \
895  std::cerr << "Press enter to continue..." << std::endl; \
896  std::cin.get(); \
897  } \
898  return res; \
899  } while(false)
900 #else
901 #define error_exit(res) return res
902 #endif
903 
904 #ifdef __APPLE__
905 extern "C" int wesnoth_main(int argc, char** argv);
906 int wesnoth_main(int argc, char** argv)
907 #else
908 int main(int argc, char** argv)
909 #endif
910 {
911  auto args = read_argv(argc, argv);
912  assert(!args.empty());
913 
914 #ifdef _WIN32
915  _putenv("PANGOCAIRO_BACKEND=fontconfig");
916  _putenv("FONTCONFIG_PATH=fonts");
917 #endif
918 
919  // write_to_log_file means that writing to the log file will be done, if true.
920  // if false, output will be written to the terminal
921  // on windows, if wesnoth was not started from a console, then it will allocate one
922  bool write_to_log_file = !getenv("WESNOTH_NO_LOG_FILE");
923  [[maybe_unused]]
924  bool no_con = false;
925 
926  // --nobanner needs to be detected before the main command-line parsing happens
927  // --log-to needs to be detected so the logging output location is set before any actual logging happens
928  bool nobanner = false;
929  for(const auto& arg : args) {
930  if(arg == "--nobanner") {
931  nobanner = true;
932  break;
933  }
934  }
935 
936  // Some switches force a Windows console to be attached to the process even
937  // if Wesnoth is an IMAGE_SUBSYSTEM_WINDOWS_GUI executable because they
938  // turn it into a CLI application. Also, --no-log-to-file in particular attaches
939  // a console to a regular GUI game session.
940  //
941  // It's up to commandline_options later to handle these switches (except
942  // --no-log-to-file) later and emit any applicable console output, but right here
943  // we need a rudimentary check for the switches in question to set up the
944  // console before proceeding any further.
945  for(const auto& arg : args) {
946  // Switches that don't take arguments
947  static const std::set<std::string> terminal_switches = {
948  "--config-path", "--data-path", "-h", "--help", "--logdomains", "--nogui", "-R", "--report",
949  "--simple-version", "--userconfig-path", "--userdata-path", "-v", "--version"
950  };
951 
952  // Switches that take arguments, the switch may have the argument past
953  // the first = character, or in a subsequent argv entry which we don't
954  // care about -- we just want to see if the switch is there.
955  static const std::set<std::string> terminal_arg_switches = {
956  "-D", "--diff", "-p", "--preprocess", "-P", "--patch", "--render-image", "--screenshot",
957  "-u", "--unit", "-V", "--validate", "--validate-schema"
958  };
959 
960  auto switch_matches_arg = [&arg](const std::string& sw) {
961  const auto pos = arg.find('=');
962  return pos == std::string::npos ? arg == sw : arg.substr(0, pos) == sw;
963  };
964 
965  if(terminal_switches.find(arg) != terminal_switches.end() ||
966  std::find_if(terminal_arg_switches.begin(), terminal_arg_switches.end(), switch_matches_arg) != terminal_arg_switches.end()) {
967  write_to_log_file = false;
968  }
969 
970  if(arg == "--no-log-to-file") {
971  write_to_log_file = false;
972  } else if(arg == "--log-to-file") {
973  write_to_log_file = true;
974  }
975 
976  if(arg == "--wnoconsole") {
977  no_con = true;
978  }
979  }
980 
981  // setup logging to file
982  // else handle redirecting the output and potentially attaching a console on windows
983  if(write_to_log_file) {
985  } else {
986 #ifdef _WIN32
987  if(!no_con) {
989  }
990 #endif
991  }
992 
993  SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
994  // Is there a reason not to just use SDL_INIT_EVERYTHING?
995  if(SDL_Init(SDL_INIT_TIMER) < 0) {
996  PLAIN_LOG << "Couldn't initialize SDL: " << SDL_GetError();
997  return (1);
998  }
999  atexit(SDL_Quit);
1000 
1001  // Mac's touchpad generates touch events too.
1002  // Ignore them until Macs have a touchscreen: https://forums.libsdl.org/viewtopic.php?p=45758
1003 #if defined(__APPLE__) && !defined(__IPHONEOS__)
1004  SDL_EventState(SDL_FINGERMOTION, SDL_DISABLE);
1005  SDL_EventState(SDL_FINGERDOWN, SDL_DISABLE);
1006  SDL_EventState(SDL_FINGERUP, SDL_DISABLE);
1007 #endif
1008 
1009  // declare this here so that it will always be at the front of the event queue.
1010  events::event_context global_context;
1011 
1012  SDL_StartTextInput();
1013 
1014  try {
1015  if(!nobanner) {
1016  PLAIN_LOG << "Battle for Wesnoth v" << game_config::revision << " " << game_config::build_arch();
1017  const std::time_t t = std::time(nullptr);
1018  PLAIN_LOG << "Started on " << ctime(&t);
1019  }
1020 
1021  if(std::string exe_dir = filesystem::get_exe_dir(); !exe_dir.empty()) {
1022  if(std::string auto_dir = autodetect_game_data_dir(std::move(exe_dir)); !auto_dir.empty()) {
1023  if(!nobanner) {
1024  PLAIN_LOG << "Automatically found a possible data directory at: " << auto_dir;
1025  }
1026  game_config::path = std::move(auto_dir);
1027  } else if(game_config::path.empty()) {
1028  bool data_dir_specified = false;
1029  for(int i=0;i<argc;i++) {
1030  if(std::string(argv[i]) == "--data-dir" || boost::algorithm::starts_with(argv[i], "--data-dir=")) {
1031  data_dir_specified = true;
1032  break;
1033  }
1034  }
1035  if (!data_dir_specified) {
1036  PLAIN_LOG << "Cannot find a data directory. Specify one with --data-dir";
1037  return 1;
1038  }
1039  }
1040  }
1041 
1042  const int res = do_gameloop(args);
1043  safe_exit(res);
1044  } catch(const boost::program_options::error& e) {
1045  PLAIN_LOG << "Error in command line: " << e.what();
1046  error_exit(1);
1047  } catch(const video::error& e) {
1048  PLAIN_LOG << "Video system error: " << e.what();
1049  error_exit(1);
1050  } catch(const font::error& e) {
1051  PLAIN_LOG << "Could not initialize fonts.\n\n" << e.what() << "\n\nExiting.";
1052  error_exit(1);
1053  } catch(const config::error& e) {
1054  PLAIN_LOG << e.message;
1055  error_exit(1);
1056  } catch(const gui::button::error&) {
1057  PLAIN_LOG << "Could not create button: Image could not be found";
1058  error_exit(1);
1059  } catch(const video::quit&) {
1060  // just means the game should quit
1061  } catch(const return_to_play_side_exception&) {
1062  PLAIN_LOG << "caught return_to_play_side_exception, please report this bug (quitting)";
1063  } catch(const quit_game_exception&) {
1064  PLAIN_LOG << "caught quit_game_exception (quitting)";
1065  } catch(const wml_exception& e) {
1066  PLAIN_LOG << "WML exception:\nUser message: " << e.user_message << "\nDev message: " << e.dev_message;
1067  error_exit(1);
1068  } catch(const wfl::formula_error& e) {
1069  PLAIN_LOG << e.what() << "\n\nGame will be aborted.";
1070  error_exit(1);
1071  } catch(const sdl::exception& e) {
1072  PLAIN_LOG << e.what();
1073  error_exit(1);
1074  } catch(const game::error& e) {
1075  PLAIN_LOG << "Game error: " << e.what();
1076  error_exit(1);
1077  } catch(const std::bad_alloc&) {
1078  PLAIN_LOG << "Ran out of memory. Aborted.";
1079  error_exit(ENOMEM);
1080 #if !defined(NO_CATCH_AT_GAME_END)
1081  } catch(const std::exception& e) {
1082  // Try to catch unexpected exceptions.
1083  PLAIN_LOG << "Caught general '" << typeid(e).name() << "' exception:\n" << e.what();
1084  error_exit(1);
1085  } catch(const std::string& e) {
1086  PLAIN_LOG << "Caught a string thrown as an exception:\n" << e;
1087  error_exit(1);
1088  } catch(const char* e) {
1089  PLAIN_LOG << "Caught a string thrown as an exception:\n" << e;
1090  error_exit(1);
1091  } catch(...) {
1092  // Ensure that even when we terminate with `throw 42`, the exception
1093  // is caught and all destructors are actually called. (Apparently,
1094  // some compilers will simply terminate without calling destructors if
1095  // the exception isn't caught.)
1096  PLAIN_LOG << "Caught general exception " << utils::get_unknown_exception_type() << ". Terminating.";
1097  error_exit(1);
1098 #endif
1099  }
1100 
1101  return 0;
1102 } // end main
int wesnoth_main(int argc, char **argv)
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Definition: manager.cpp:388
double t
Definition: astarsearch.cpp:63
Used in parsing config file.
Definition: validator.hpp:38
const std::string name_
Definition: validator.hpp:101
std::optional< std::string > preprocess_input_macros
Non-empty if –preprocess-input-macros was given on the command line.
bool simple_version
True if –simple-version was given on the command line.
bool report
True if –report was given on the command line.
std::optional< std::string > validate_wml
Non-empty if –validate was given on the command line.
bool strict_lua
True if –strict-lua was given in the commandline.
std::optional< std::string > preprocess_path
Path to parse that was given to the –preprocess option.
std::optional< std::string > userdata_dir
Non-empty if –userdata-dir was given on the command line.
std::optional< std::string > render_image
Image path to render.
std::optional< std::string > preprocess_output_macros
Non-empty if –preprocess-output-macros was given on the command line.
std::optional< std::string > logdomains
Non-empty if –logdomains was given on the command line.
std::optional< unsigned int > rng_seed
RNG seed specified by –rng-seed option.
bool userconfig_path
True if –userconfig-path was given on the command line.
bool nobanner
True if –nobanner was given on the command line.
bool preprocess
True if –preprocess was given on the command line.
std::string diff_left
Files for diffing or patching.
bool data_path
True if –data-path was given on the command line.
bool version
True if –version was given on the command line.
bool allow_insecure
True if –allow-insecure was given in the commandline.
std::optional< std::string > output_file
Output filename for WML diff or preprocessing.
bool noaddons
True if –noaddons was given on the command line.
std::optional< std::string > preprocess_target
Target (output) path that was given to the –preprocess option.
bool debug_lua
True if –debug-lua was given in the commandline.
std::vector< std::string > unit_test
Non-empty if –unit was given on the command line.
bool userdata_path
True if –userdata-path was given on the command line.
bool log_precise_timestamps
True if –log-precise was given on the command line.
std::optional< std::string > data_dir
Non-empty if –data-dir was given on the command line.
std::optional< std::vector< std::string > > preprocess_defines
Defines that were given to the –preprocess option.
std::optional< std::vector< std::pair< lg::severity, std::string > > > log
Contains parsed arguments of –log-* (e.g.
std::optional< std::string > usercache_dir
Non-empty if –usercache-dir was given on the command line.
std::optional< std::string > userconfig_dir
Non-empty if –userconfig-dir was given on the command line.
bool strict_validation
True if –strict-validation was given on the command line.
bool help
True if –help was given on the command line.
std::optional< std::string > validate_with
Non-empty if –use-schema was given on the command line.
bool usercache_path
True if –usercache-path was given on the command line.
std::optional< std::string > validate_schema
Non-empty if –validate-schema was given on the command line.
Class for writing a config out to a file in pieces.
void write(const config &cfg)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:887
child_itors child_range(config_key_type key)
Definition: config.cpp:273
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1029
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object.
Definition: config.cpp:913
@ NO_FORCE_RELOAD
Don't reload if the previous defines equal the new defines.
bool init_game_config(FORCE_RELOAD_CONFIG force_reload)
const game_config_view & game_config() const
optional_const_config optional_child(config_key_type key) const
bool play_multiplayer(mp_mode mode)
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
static void display(std::function< void()> f)
@ ok_button
Shows an ok button.
Definition: message.hpp:73
bool show(const unsigned auto_close_time=0)
Shows the window.
int get_retval() const
Returns the cached window exit code.
This class implements the title screen.
void play_slice()
Definition: context.cpp:96
std::vector< Reg > reg_vec
Definition: context.hpp:40
std::vector< aReg > areg_vec
Definition: context.hpp:41
void set_callback(const std::string &name, callback_function)
Definition: context.cpp:51
Exception used to escape form the ai or ui code to playsingle_controller::play_side.
Realization of serialization/validator.hpp abstract validator.
std::string str() const
Serializes the version number into string form.
Type that can be thrown as an exception to quit to desktop.
Definition: video.hpp:317
std::vector< std::string > read_argv([[maybe_unused]] int argc, [[maybe_unused]] char **argv)
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:968
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:93
Standard logging facilities (interface).
#define STREAMING_LOG
Definition: log.hpp:296
#define PLAIN_LOG
Definition: log.hpp:295
@ WAIT
Definition: cursor.hpp:28
@ NORMAL
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:177
std::string get_cache_dir()
Definition: filesystem.cpp:926
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_config_dir()
Definition: filesystem.cpp:907
std::string get_user_data_dir()
Definition: filesystem.cpp:916
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:319
std::string get_exe_dir()
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn't pres...
void set_user_config_dir(const std::string &newconfigdir)
Definition: filesystem.cpp:880
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
void set_cache_dir(const std::string &newcachedir)
Definition: filesystem.cpp:893
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:50
std::string get_logs_dir()
Definition: filesystem.cpp:921
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:51
std::string get_intl_dir()
std::string get_cwd()
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
void set_user_data_dir(std::string newprefdir)
Definition: filesystem.cpp:846
bool load_font_config()
Definition: font_config.cpp:79
std::string path
Definition: filesystem.cpp:84
std::string full_build_report()
Produce a bug report-style info dump.
Definition: build_info.cpp:665
std::string library_versions_report()
Produce a plain-text report of library versions suitable for stdout/stderr.
Definition: build_info.cpp:655
const version_info wesnoth_version(VERSION)
bool allow_insecure
Definition: game_config.cpp:74
std::string title_music
std::string build_arch()
Obtain the processor architecture for this build.
Definition: build_info.cpp:316
std::string optional_features_report()
Produce a plain-text report of features suitable for stdout/stderr.
Definition: build_info.cpp:660
const std::string revision
void set_debug(bool new_debug)
Definition: game_config.cpp:93
bool check_migration
Definition: filesystem.cpp:92
void init()
Initializes the GUI subsystems.
Definition: gui.cpp:36
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:150
retval
Default window/dialog return values.
Definition: retval.hpp:30
bool using_own_console()
Returns true if a console was allocated by the Wesnoth process.
severity
Definition: log.hpp:82
std::string list_log_domains(const std::string &filter)
Definition: log.cpp:375
bool broke_strict()
Definition: log.cpp:395
void set_log_to_file()
Do the initial redirection to a log file if the logs directory is writable.
Definition: log.cpp:229
void do_console_redirect()
Allocates a console if needed and redirects output to CONOUT.
void precise_timestamps(bool pt)
Definition: log.cpp:300
std::optional< bool > log_dir_writable()
Returns the result set by check_log_dir_writable().
Definition: log.cpp:275
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:342
void set_strict_severity(severity severity)
Definition: log.cpp:385
void empty_playlist()
Definition: sound.cpp:610
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition: sound.cpp:711
void stop_music()
Definition: sound.cpp:555
void bind_textdomain(const char *domain, const char *directory, const char *)
Definition: gettext.cpp:479
void set_default_textdomain(const char *domain)
Definition: gettext.cpp:487
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::map< std::string, t_string > string_map
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
void preprocess_resource(const std::string &res_name, preproc_map *defines_map, bool write_cfg, bool write_plain_cfg, const std::string &parent_directory)
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
std::map< std::string, struct preproc_define > preproc_map
One of the realizations of serialization/validator.hpp abstract validator.
Contains a basic exception class for SDL operations.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
structure which will hide all current floating labels, and cause floating labels instantiated after i...
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
static preproc_map::value_type read_pair(const config &)
An error specifically indicating video subsystem problems.
Definition: video.hpp:310
Helper class, don't construct this directly.
static map_location::DIRECTION sw
bool strict_validation_enabled
Definition: validator.cpp:21
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define PACKAGE
Definition: wesconfig.h:23
#define LOG_CONFIG
Definition: wesnoth.cpp:107
static lg::log_domain log_preprocessor("preprocessor")
static void safe_exit(int res)
Definition: wesnoth.cpp:117
static std::string autodetect_game_data_dir(std::string exe_dir)
Try to autodetect the location of the game data dir.
Definition: wesnoth.cpp:860
static int handle_validate_command(const std::string &file, abstract_validator &validator, const std::vector< std::string > &defines)
Definition: wesnoth.cpp:242
int main(int argc, char **argv)
Definition: wesnoth.cpp:908
static void handle_preprocess_command(const commandline_options &cmdline_opts)
Definition: wesnoth.cpp:123
static int do_gameloop(const std::vector< std::string > &args)
Setups the game environment and enters the titlescreen or game loops.
Definition: wesnoth.cpp:619
static void check_fpu()
Definition: wesnoth.cpp:590
#define LOG_PREPROC
Definition: wesnoth.cpp:112
static void init_locale()
I would prefer to setup locale first so that early error messages can get localized,...
Definition: wesnoth.cpp:490
static void handle_lua_script_args(game_launcher *game, commandline_options &)
Handles the lua script command line arguments if present.
Definition: wesnoth.cpp:527
static int process_command_args(const commandline_options &cmdline_opts)
Process commandline-arguments.
Definition: wesnoth.cpp:270
#define error_exit(res)
Definition: wesnoth.cpp:901
static void warn_early_init_failure()
Print an alert and instructions to stderr about early initialization errors.
Definition: wesnoth.cpp:514
#define LOG_GENERAL
Definition: wesnoth.cpp:109
static lg::log_domain log_config("config")
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e