The Battle for Wesnoth  1.19.5+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
43 #include "sdl/exception.hpp" // for exception
44 #include "serialization/binary_or_text.hpp" // for config_writer
45 #include "serialization/parser.hpp" // for read
46 #include "serialization/preprocessor.hpp" // for preproc_define, etc
47 #include "serialization/schema_validator.hpp" // for strict_validation_enabled and schema_validator
48 #include "sound.hpp" // for commit_music_changes, etc
49 #include "utils/optimer.hpp"
50 #include "formula/string_utils.hpp" // VGETTEXT
51 #include <functional>
52 #include "game_version.hpp" // for version_info
53 #include "video.hpp" // for video::error and video::quit
54 #include "wesconfig.h" // for PACKAGE
55 #include "widgets/button.hpp" // for button
56 #include "wml_exception.hpp" // for wml_exception
57 
59 #ifdef _WIN32
60 #include "log_windows.hpp"
61 
62 #include <float.h>
63 #endif // _WIN32
64 
65 #ifndef _MSC_VER
66 #include <fenv.h>
67 #endif // _MSC_VER
68 
69 #include <SDL2/SDL.h> // for SDL_Init, SDL_INIT_TIMER
70 
71 #include <boost/program_options/errors.hpp> // for error
72 #include <boost/algorithm/string/predicate.hpp> // for checking cmdline options
73 #include "utils/optional_fwd.hpp"
74 
75 #include <algorithm> // for transform
76 #include <cerrno> // for ENOMEM
77 #include <clocale> // for setlocale, LC_ALL, etc
78 #include <cstdio> // for remove, fprintf, stderr
79 #include <cstdlib> // for srand, exit
80 #include <ctime> // for time, ctime, std::time_t
81 #include <exception> // for exception
82 #include <vector>
83 #include <iostream>
84 
85 //#define NO_CATCH_AT_GAME_END
86 
87 #ifdef _WIN32
88 
89 #ifdef INADDR_ANY
90 #undef INADDR_ANY
91 #endif
92 
93 #ifdef INADDR_BROADCAST
94 #undef INADDR_BROADCAST
95 #endif
96 
97 #ifdef INADDR_NONE
98 #undef INADDR_NONE
99 #endif
100 
101 #include <windows.h>
102 
103 #endif // _WIN32
104 
105 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
106 #include "gui/widgets/debug.hpp"
107 #endif
108 
109 static lg::log_domain log_config("config");
110 #define LOG_CONFIG LOG_STREAM(info, log_config)
111 
112 #define LOG_GENERAL LOG_STREAM(info, lg::general())
113 
114 static lg::log_domain log_preprocessor("preprocessor");
115 #define LOG_PREPROC LOG_STREAM(info, log_preprocessor)
116 
117 // this is needed to allow identical functionality with clean refactoring
118 // play_game only returns on an error, all returns within play_game can
119 // be replaced with this
120 static void safe_exit(int res)
121 {
122  LOG_GENERAL << "exiting with code " << res;
123  exit(res);
124 }
125 
126 static void handle_preprocess_command(const commandline_options& cmdline_opts)
127 {
128  preproc_map input_macros;
129 
130  if(cmdline_opts.preprocess_input_macros) {
131  std::string file = *cmdline_opts.preprocess_input_macros;
132  if(filesystem::file_exists(file) == false) {
133  PLAIN_LOG << "please specify an existing file. File " << file << " doesn't exist.";
134  return;
135  }
136 
137  PLAIN_LOG << "Reading cached defines from: " << file;
138 
139  config cfg;
140 
141  try {
143  read(cfg, *stream);
144  } catch(const config::error& e) {
145  PLAIN_LOG << "Caught a config error while parsing file '" << file << "':\n" << e.message;
146  }
147 
148  int read = 0;
149 
150  // use static preproc_define::read_pair(config) to make a object
151  for(const auto [_, cfg] : cfg.all_children_view()) {
152  const preproc_map::value_type def = preproc_define::read_pair(cfg);
153  input_macros[def.first] = def.second;
154  ++read;
155  }
156 
157  PLAIN_LOG << "Read " << read << " defines.";
158  }
159 
160  const std::string resourceToProcess(*cmdline_opts.preprocess_path);
161  const std::string targetDir(*cmdline_opts.preprocess_target);
162 
163  const utils::ms_optimer timer(
164  [](const auto& timer) { PLAIN_LOG << "preprocessing finished. Took " << timer << " ticks."; });
165 
166  // If the users add the SKIP_CORE define we won't preprocess data/core
167  bool skipCore = false;
168  bool skipTerrainGFX = false;
169 
170  // The 'core_defines_map' is the one got from data/core macros
171  preproc_map defines_map(input_macros);
172 
173  if(cmdline_opts.preprocess_defines) {
174  // add the specified defines
175  for(const std::string& define : *cmdline_opts.preprocess_defines) {
176  if(define.empty()) {
177  PLAIN_LOG << "empty define supplied";
178  continue;
179  }
180 
181  LOG_PREPROC << "adding define: " << define;
182  defines_map.emplace(define, preproc_define(define));
183 
184  if(define == "SKIP_CORE") {
185  PLAIN_LOG << "'SKIP_CORE' defined.";
186  skipCore = true;
187  } else if(define == "NO_TERRAIN_GFX") {
188  PLAIN_LOG << "'NO_TERRAIN_GFX' defined.";
189  skipTerrainGFX = true;
190  }
191  }
192  }
193 
194  // add the WESNOTH_VERSION define
195  defines_map["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
196 
197  PLAIN_LOG << "added " << defines_map.size() << " defines.";
198 
199  // preprocess core macros first if we don't skip the core
200  if(skipCore == false) {
201  PLAIN_LOG << "preprocessing common macros from 'data/core' ...";
202 
203  // process each folder explicitly to gain speed
204  preprocess_resource(game_config::path + "/data/core/macros", &defines_map);
205 
206  if(skipTerrainGFX == false) {
207  preprocess_resource(game_config::path + "/data/core/terrain-graphics", &defines_map);
208  }
209 
210  PLAIN_LOG << "acquired " << (defines_map.size() - input_macros.size()) << " 'data/core' defines.";
211  } else {
212  PLAIN_LOG << "skipped 'data/core'";
213  }
214 
215  // preprocess resource
216  PLAIN_LOG << "preprocessing specified resource: " << resourceToProcess << " ...";
217 
218  preprocess_resource(resourceToProcess, &defines_map, true, true, targetDir);
219  PLAIN_LOG << "acquired " << (defines_map.size() - input_macros.size()) << " total defines.";
220 
221  if(cmdline_opts.preprocess_output_macros) {
222  std::string outputFileName = "_MACROS_.cfg";
223  if(!cmdline_opts.preprocess_output_macros->empty()) {
224  outputFileName = *cmdline_opts.preprocess_output_macros;
225  }
226 
227  std::string outputPath = targetDir + "/" + outputFileName;
228 
229  PLAIN_LOG << "writing '" << outputPath << "' with " << defines_map.size() << " defines.";
230 
232  if(!out->fail()) {
233  config_writer writer(*out, false);
234 
235  for(auto& define_pair : defines_map) {
236  define_pair.second.write(writer, define_pair.first);
237  }
238  } else {
239  PLAIN_LOG << "couldn't open the file.";
240  }
241  }
242 }
243 
244 static int handle_validate_command(const std::string& file, abstract_validator& validator, const std::vector<std::string>& defines) {
245  preproc_map defines_map;
246  // add the WESNOTH_VERSION define
247  defines_map["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
248  defines_map["SCHEMA_VALIDATION"] = preproc_define();
249  for(const std::string& define : defines) {
250  if(define.empty()) {
251  PLAIN_LOG << "empty define supplied";
252  continue;
253  }
254 
255  LOG_PREPROC << "adding define: " << define;
256  defines_map.emplace(define, preproc_define(define));
257  }
258  PLAIN_LOG << "Validating " << file << " against schema " << validator.name_;
260  filesystem::scoped_istream stream = preprocess_file(file, &defines_map);
261  config result;
262  read(result, *stream, &validator);
263  if(lg::broke_strict()) {
264  std::cout << "validation failed\n";
265  } else {
266  std::cout << "validation succeeded\n";
267  }
268  return lg::broke_strict();
269 }
270 
271 /** Process commandline-arguments */
272 static int process_command_args(commandline_options& cmdline_opts)
273 {
274  // Options that don't change behavior based on any others should be checked alphabetically below.
275 
276  if(cmdline_opts.no_log_sanitize) {
277  lg::set_log_sanitize(false);
278  }
279 
280  if(cmdline_opts.usercache_dir) {
282  }
283 
284  if(cmdline_opts.userdata_dir) {
286  }
287 
288  // earliest possible point to ensure the userdata directory is known
290  filesystem::set_user_data_dir(std::string());
291  }
292 
293  // userdata is initialized, so initialize logging to file if enabled
294  // If true, output will be redirected to file, else output be written to console.
295  // On Windows, if Wesnoth was not started from a console, one will be allocated.
296  if(cmdline_opts.log_to_file
297  || (!cmdline_opts.no_log_to_file
298  && !getenv("WESNOTH_NO_LOG_FILE")
299  // command line options that imply not redirecting output to a log file
300  && !cmdline_opts.data_path
301  && !cmdline_opts.userdata_path
302  && !cmdline_opts.usercache_path
303  && !cmdline_opts.version
304  && !cmdline_opts.simple_version
305  && !cmdline_opts.logdomains
306  && !cmdline_opts.help
307  && !cmdline_opts.report
308  && !cmdline_opts.do_diff
309  && !cmdline_opts.do_patch
310  && !cmdline_opts.preprocess
311  && !cmdline_opts.render_image
312  && !cmdline_opts.screenshot
313  && !cmdline_opts.nogui
314  && !cmdline_opts.headless_unit_test
315  && !cmdline_opts.validate_schema
316  && !cmdline_opts.validate_wml
317  )
318  )
319  {
321  }
322 #ifdef _WIN32
323  // This forces a Windows console to be attached to the process even
324  // if Wesnoth is an IMAGE_SUBSYSTEM_WINDOWS_GUI executable because it
325  // turns Wesnoth into a CLI application. (unless --wnoconsole is given)
326  else if(!cmdline_opts.no_console) {
328  }
329 #endif
330 
331  if(cmdline_opts.log) {
332  for(const auto& log_pair : *cmdline_opts.log) {
333  const std::string log_domain = log_pair.second;
334  const lg::severity severity = log_pair.first;
335  if(!lg::set_log_domain_severity(log_domain, severity)) {
336  PLAIN_LOG << "unknown log domain: " << log_domain;
337  return 2;
338  }
339  }
340  }
341 
342  if(!cmdline_opts.nobanner) {
343  PLAIN_LOG << "Battle for Wesnoth v" << game_config::revision << " " << game_config::build_arch();
344  const std::time_t t = std::time(nullptr);
345  PLAIN_LOG << "Started on " << ctime(&t);
346  }
347 
348  if(cmdline_opts.usercache_path) {
349  std::cout << filesystem::get_cache_dir();
350  return 0;
351  }
352 
353  if(cmdline_opts.userdata_path) {
354  std::cout << filesystem::get_user_data_dir();
355  return 0;
356  }
357 
358  if(cmdline_opts.data_dir) {
359  game_config::path = filesystem::normalize_path(*cmdline_opts.data_dir, true, true);
360  if(!cmdline_opts.nobanner) {
361  PLAIN_LOG << "Overriding data directory with '" << game_config::path << "'";
362  }
363  } else {
364  // if a pre-defined path does not exist this will empty it
366  if(game_config::path.empty()) {
367  if(std::string exe_dir = filesystem::get_exe_dir(); !exe_dir.empty()) {
368  if(std::string auto_dir = filesystem::autodetect_game_data_dir(std::move(exe_dir)); !auto_dir.empty()) {
369  if(!cmdline_opts.nobanner) {
370  PLAIN_LOG << "Automatically found a possible data directory at: " << auto_dir;
371  }
372  game_config::path = filesystem::normalize_path(auto_dir, true, true);
373  }
374  } else {
375  PLAIN_LOG << "Cannot find game data directory. Specify one with --data-dir";
376  return 1;
377  }
378  }
379  }
380 
382  PLAIN_LOG << "Could not find game data directory '" << game_config::path << "'";
383  return 1;
384  }
385 
386  if(cmdline_opts.data_path) {
387  std::cout << game_config::path;
388  return 0;
389  }
390 
391  if(cmdline_opts.debug_lua) {
392  game_config::debug_lua = true;
393  }
394 
395  if(cmdline_opts.allow_insecure) {
397  }
398 
399  if(cmdline_opts.strict_lua) {
401  }
402 
403  if(cmdline_opts.help) {
404  std::cout << cmdline_opts;
405  return 0;
406  }
407 
408  if(cmdline_opts.logdomains) {
409  std::cout << lg::list_log_domains(*cmdline_opts.logdomains);
410  return 0;
411  }
412 
413  if(cmdline_opts.log_precise_timestamps) {
415  }
416 
417  if(cmdline_opts.rng_seed) {
418  srand(*cmdline_opts.rng_seed);
419  }
420 
421  if(cmdline_opts.render_image) {
422  SDL_setenv("SDL_VIDEODRIVER", "dummy", 1);
423  }
424 
425  if(cmdline_opts.strict_validation) {
427  }
428 
429  if(cmdline_opts.version) {
430  std::cout << "Battle for Wesnoth" << " " << game_config::wesnoth_version.str() << "\n\n";
431  std::cout << "Library versions:\n" << game_config::library_versions_report() << '\n';
432  std::cout << "Optional features:\n" << game_config::optional_features_report();
433 
434  return 0;
435  }
436 
437  if(cmdline_opts.simple_version) {
438  std::cout << game_config::wesnoth_version.str() << "\n";
439 
440  return 0;
441  }
442 
443  if(cmdline_opts.report) {
444  std::cout << "\n========= BUILD INFORMATION =========\n\n" << game_config::full_build_report();
445  return 0;
446  }
447 
448  if(cmdline_opts.validate_schema) {
450  validator.set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
451  return handle_validate_command(*cmdline_opts.validate_schema, validator, {});
452  }
453 
454  if(cmdline_opts.do_diff) {
455  config left, right;
456  std::ifstream in_left(cmdline_opts.diff_left);
457  std::ifstream in_right(cmdline_opts.diff_right);
458  read(left, in_left);
459  read(right, in_right);
460  std::ostream* os = &std::cout;
461  if(cmdline_opts.output_file) {
462  os = new std::ofstream(*cmdline_opts.output_file);
463  }
465  out.write(right.get_diff(left));
466  if(os != &std::cout) delete os;
467  return 0;
468  }
469 
470  if(cmdline_opts.do_patch) {
471  config base, diff;
472  std::ifstream in_base(cmdline_opts.diff_left);
473  std::ifstream in_diff(cmdline_opts.diff_right);
474  read(base, in_base);
475  read(diff, in_diff);
476  base.apply_diff(diff);
477  std::ostream* os = &std::cout;
478  if(cmdline_opts.output_file) {
479  os = new std::ofstream(*cmdline_opts.output_file);
480  }
482  out.write(base);
483  if(os != &std::cout) delete os;
484  return 0;
485  }
486 
487  if(cmdline_opts.generate_spritesheet) {
488  PLAIN_LOG << "sheet path " << *cmdline_opts.generate_spritesheet;
490  return 0;
491  }
492 
493  // Options changing their behavior dependent on some others should be checked below.
494 
495  if(cmdline_opts.preprocess) {
496  handle_preprocess_command(cmdline_opts);
497  return 0;
498  }
499 
500  if(cmdline_opts.validate_wml) {
501  std::string schema_path;
502  if(cmdline_opts.validate_with) {
503  schema_path = *cmdline_opts.validate_with;
504  if(!filesystem::file_exists(schema_path)) {
505  if(auto check = filesystem::get_wml_location(schema_path)) {
506  schema_path = check.value();
507  } else {
508  PLAIN_LOG << "Could not find schema file: " << schema_path;
509  }
510  } else {
511  schema_path = filesystem::normalize_path(schema_path);
512  }
513  } else {
514  schema_path = filesystem::get_wml_location("schema/game_config.cfg").value();
515  }
517  validator.set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
518  return handle_validate_command(*cmdline_opts.validate_wml, validator,
519  cmdline_opts.preprocess_defines.value_or<decltype(cmdline_opts.preprocess_defines)::value_type>({}));
520  }
521 
522  if(cmdline_opts.preprocess_defines || cmdline_opts.preprocess_input_macros || cmdline_opts.preprocess_path) {
523  // It would be good if this was supported for running tests too, possibly for other uses.
524  // For the moment show an error message instead of leaving the user wondering why it doesn't work.
525  PLAIN_LOG << "That --preprocess-* option is only supported when using --preprocess or --validate.";
526  return 2;
527  }
528 
529  // Not the most intuitive solution, but I wanted to leave current semantics for now
530  return -1;
531 }
532 
533 /**
534  * I would prefer to setup locale first so that early error
535  * messages can get localized, but we need the game_launcher
536  * initialized to have filesystem::get_intl_dir() to work. Note: setlocale()
537  * does not take GUI language setting into account.
538  */
539 static void init_locale()
540 {
541 #if defined _WIN32 || defined __APPLE__
542  setlocale(LC_ALL, "English");
543 #else
544  std::setlocale(LC_ALL, "C");
545 #endif
546 
547  const std::string& intl_dir = filesystem::get_intl_dir();
548 
549  translation::bind_textdomain(PACKAGE, intl_dir.c_str(), "UTF-8");
550  translation::bind_textdomain(PACKAGE "-lib", intl_dir.c_str(), "UTF-8");
552 }
553 
554 /**
555  * Print an alert and instructions to stderr about early initialization errors.
556  *
557  * This is provided as an aid for users dealing with potential data dir
558  * configuration issues. The first code to read core WML *has* the
559  * responsibility to call this function in the event of a problem, to inform
560  * the user of the most likely possible cause and suggest a course of action
561  * to solve the issue.
562  */
564 {
565  // NOTE: wrap output to 80 columns.
566  PLAIN_LOG << '\n'
567  << "An error at this point during initialization usually indicates that the data\n"
568  << "directory above was not correctly set or detected. Try passing the correct path\n"
569  << "in the command line with the --data-dir switch or as the only argument.";
570 }
571 
572 /**
573  * Handles the lua script command line arguments if present.
574  * This function will only run once.
575  */
577 {
578  static bool first_time = true;
579 
580  if(!first_time) {
581  return;
582  }
583 
584  first_time = false;
585 
586  if(!game->init_lua_script()) {
587  // PLAIN_LOG << "error when loading lua scripts at startup";
588  // PLAIN_LOG << "could not load lua script: " << *cmdline_opts.script_file;
589  }
590 }
591 
592 #ifdef _MSC_VER
593 static void check_fpu()
594 {
595  uint32_t f_control;
596 
597  if(_controlfp_s(&f_control, 0, 0) == 0) {
598  uint32_t unused;
599  uint32_t rounding_mode = f_control & _MCW_RC;
600 
601  if(rounding_mode != _RC_NEAR) {
602  PLAIN_LOG << "Floating point rounding mode is currently '"
603  << ((rounding_mode == _RC_CHOP)
604  ? "chop"
605  : (rounding_mode == _RC_UP)
606  ? "up"
607  : (rounding_mode == _RC_DOWN)
608  ? "down"
609  : (rounding_mode == _RC_NEAR) ? "near" : "unknown")
610  << "' setting to 'near'";
611 
612  if(_controlfp_s(&unused, _RC_NEAR, _MCW_RC)) {
613  PLAIN_LOG << "failed to set floating point rounding type to 'near'";
614  }
615  }
616 
617 #ifndef _M_AMD64
618  uint32_t precision_mode = f_control & _MCW_PC;
619  if(precision_mode != _PC_53) {
620  PLAIN_LOG << "Floating point precision mode is currently '"
621  << ((precision_mode == _PC_53)
622  ? "double"
623  : (precision_mode == _PC_24)
624  ? "single"
625  : (precision_mode == _PC_64) ? "double extended" : "unknown")
626  << "' setting to 'double'";
627 
628  if(_controlfp_s(&unused, _PC_53, _MCW_PC)) {
629  PLAIN_LOG << "failed to set floating point precision type to 'double'";
630  }
631  }
632 #endif
633 
634  } else {
635  PLAIN_LOG << "_controlfp_s failed.";
636  }
637 }
638 #else
639 static void check_fpu()
640 {
641  switch(fegetround()) {
642  case FE_TONEAREST:
643  break;
644  case FE_DOWNWARD:
645  STREAMING_LOG << "Floating point precision mode is currently 'downward'";
646  goto reset_fpu;
647  case FE_TOWARDZERO:
648  STREAMING_LOG << "Floating point precision mode is currently 'toward-zero'";
649  goto reset_fpu;
650  case FE_UPWARD:
651  STREAMING_LOG << "Floating point precision mode is currently 'upward'";
652  goto reset_fpu;
653  default:
654  STREAMING_LOG << "Floating point precision mode is currently 'unknown'";
655  goto reset_fpu;
656  reset_fpu:
657  STREAMING_LOG << " - setting to 'nearest'\n";
658  fesetround(FE_TONEAREST);
659  break;
660  }
661 }
662 #endif
663 
664 /**
665  * Setups the game environment and enters
666  * the titlescreen or game loops.
667  */
668 static int do_gameloop(commandline_options& cmdline_opts)
669 {
670  srand(std::time(nullptr));
671 
672  const auto game = std::make_unique<game_launcher>(cmdline_opts);
673 
674  init_locale();
675 
676  bool res;
677 
678  // Do initialize fonts before reading the game config, to have game
679  // config error messages displayed. fonts will be re-initialized later
680  // when the language is read from the game config.
681  res = font::load_font_config();
682  if(res == false) {
683  PLAIN_LOG << "could not initialize fonts";
684  // The most common symptom of a bogus data dir path -- warn the user.
686  return 1;
687  }
688 
689  res = game->init_language();
690  if(res == false) {
691  PLAIN_LOG << "could not initialize the language";
692  return 1;
693  }
694 
695  res = game->init_video();
696  if(res == false) {
697  PLAIN_LOG << "could not initialize display";
698  return 1;
699  }
700 
701  check_fpu();
702  const cursor::manager cursor_manager;
704 
705 #if(defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
706  SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
707 #endif
708 
709  gui2::init();
710  gui2::switch_theme(prefs::get().gui2_theme());
711  const gui2::event::manager gui_event_manager;
712 
713  // if the log directory is not writable, then this is the error condition so show the error message.
714  // if the log directory is writable, then there's no issue.
715  // if the optional isn't set, then logging to file has been disabled, so there's no issue.
716  if(!lg::log_dir_writable().value_or(true)) {
717  utils::string_map symbols;
718  symbols["logdir"] = filesystem::get_logs_dir();
719  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);
721  }
722 
723  game_config_manager config_manager(cmdline_opts);
724 
728  }
729 
730  gui2::dialogs::loading_screen::display([&res, &config_manager, &cmdline_opts]() {
733 
734  if(res == false) {
735  PLAIN_LOG << "could not initialize game config";
736  return;
737  }
738 
740 
741  res = font::load_font_config();
742  if(res == false) {
743  PLAIN_LOG << "could not re-initialize fonts for the current language";
744  return;
745  }
746 
747  if(!game_config::no_addons && !cmdline_opts.noaddons) {
749 
751  }
752  });
753 
754  if(res == false) {
755  return 1;
756  }
757 
758  plugins_manager plugins_man(new application_lua_kernel);
759 
760  const plugins_context::reg_vec callbacks {
761  {"play_multiplayer", std::bind(&game_launcher::play_multiplayer, game.get(), game_launcher::mp_mode::CONNECT)},
762  };
763 
764  const plugins_context::areg_vec accessors {
765  {"command_line", std::bind(&commandline_options::to_config, &cmdline_opts)},
766  };
767 
768  plugins_context plugins("titlescreen", callbacks, accessors);
769 
770  plugins.set_callback("exit", [](const config& cfg) { safe_exit(cfg["code"].to_int(0)); }, false);
771 
772  while(true) {
773  if(!game->has_load_data()) {
774  auto cfg = config_manager.game_config().optional_child("titlescreen_music");
775  if(cfg) {
776  for(const config& i : cfg->child_range("music")) {
778  }
779 
780  config title_music_config;
781  title_music_config["name"] = game_config::title_music;
782  title_music_config["append"] = true;
783  title_music_config["immediate"] = true;
784  sound::play_music_config(title_music_config);
785  } else {
788  }
789  }
790 
791  handle_lua_script_args(&*game, cmdline_opts);
792 
793  plugins.play_slice();
794  plugins.play_slice();
795 
796  if(!cmdline_opts.unit_test.empty()) {
797  return static_cast<int>(game->unit_test());
798  }
799 
800  if(game->play_test() == false) {
801  return 0;
802  }
803 
804  if(game->play_screenshot_mode() == false) {
805  return 0;
806  }
807 
808  if(game->play_render_image_mode() == false) {
809  return 0;
810  }
811 
812  // Start directly a campaign
813  if(game->goto_campaign() == false) {
814  if(game->jump_to_campaign_id().empty())
815  continue; // Go to main menu
816  else
817  return 1; // we got an error starting the campaign from command line
818  }
819 
820  // Start directly a multiplayer
821  // Eventually with a specified server
822  if(game->goto_multiplayer() == false) {
823  continue; // Go to main menu
824  }
825 
826  // Start directly a commandline multiplayer game
827  if(game->play_multiplayer_commandline() == false) {
828  return 0;
829  }
830 
831  if(game->goto_editor() == false) {
832  return 0;
833  }
834 
835  const font::floating_label_context label_manager;
836 
838 
839  // If loading a game, skip the titlescreen entirely
840  if(game->has_load_data() && game->load_game()) {
842  continue;
843  }
844 
845  int retval;
846  { // scope to not keep the title screen alive all game
848 
849  // Allows re-layout on resize.
850  // Since RELOAD_UI is not checked here, it causes
851  // the dialog to be closed and reshown with changes.
853  dlg.show();
854  }
855  retval = dlg.get_retval();
856  }
857 
858  switch(retval) {
860  LOG_GENERAL << "quitting game...";
861  return 0;
864  game->play_multiplayer(game_launcher::mp_mode::CONNECT);
865  break;
868  game->play_multiplayer(game_launcher::mp_mode::HOST);
869  break;
872  game->play_multiplayer(game_launcher::mp_mode::LOCAL);
873  break;
875  gui2::dialogs::loading_screen::display([&config_manager]() {
876  config_manager.reload_changed_game_config();
877  gui2::init();
878  gui2::switch_theme(prefs::get().gui2_theme());
879  });
880  break;
882  game->start_editor();
883  break;
886  break;
888  break;
890  gui2::switch_theme(prefs::get().gui2_theme());
891  break;
892  }
893  }
894 }
895 
896 #ifdef _WIN32
897 #define error_exit(res) \
898  do { \
899  if(lg::using_own_console()) { \
900  std::cerr << "Press enter to continue..." << std::endl; \
901  std::cin.get(); \
902  } \
903  return res; \
904  } while(false)
905 #else
906 #define error_exit(res) return res
907 #endif
908 
909 #ifdef __APPLE__
910 extern "C" int wesnoth_main(int argc, char** argv);
911 int wesnoth_main(int argc, char** argv)
912 #else
913 int main(int argc, char** argv)
914 #endif
915 {
916  auto args = read_argv(argc, argv);
917  assert(!args.empty());
918 
919 #ifdef _WIN32
920  _putenv("PANGOCAIRO_BACKEND=fontconfig");
921  _putenv("FONTCONFIG_PATH=fonts");
922 #endif
923 
924  try {
925  commandline_options cmdline_opts = commandline_options(args);
926  int finished = process_command_args(cmdline_opts);
927 
928  if(finished != -1) {
929 #ifdef _WIN32
930  if(lg::using_own_console()) {
931  std::cerr << "Press enter to continue..." << std::endl;
932  std::cin.get();
933  }
934 #endif
935  safe_exit(finished);
936  }
937 
938  SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
939  // Is there a reason not to just use SDL_INIT_EVERYTHING?
940  if(SDL_Init(SDL_INIT_TIMER) < 0) {
941  PLAIN_LOG << "Couldn't initialize SDL: " << SDL_GetError();
942  return (1);
943  }
944  atexit(SDL_Quit);
945 
946  // Mac's touchpad generates touch events too.
947  // Ignore them until Macs have a touchscreen: https://forums.libsdl.org/viewtopic.php?p=45758
948 #if defined(__APPLE__) && !defined(__IPHONEOS__)
949  SDL_EventState(SDL_FINGERMOTION, SDL_DISABLE);
950  SDL_EventState(SDL_FINGERDOWN, SDL_DISABLE);
951  SDL_EventState(SDL_FINGERUP, SDL_DISABLE);
952 #endif
953 
954  // declare this here so that it will always be at the front of the event queue.
955  events::event_context global_context;
956 
957  SDL_StartTextInput();
958 
959  const int res = do_gameloop(cmdline_opts);
960  safe_exit(res);
961  } catch(const boost::program_options::error& e) {
962  // logging hasn't been initialized by this point
963  std::cerr << "Error in command line: " << e.what() << std::endl;
964  std::string error = "Error parsing command line arguments: ";
965  error += e.what();
966  SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", error.c_str(), nullptr);
967  error_exit(2);
968  } catch(const video::error& e) {
969  PLAIN_LOG << "Video system error: " << e.what();
970  error_exit(1);
971  } catch(const font::error& e) {
972  PLAIN_LOG << "Could not initialize fonts.\n\n" << e.what() << "\n\nExiting.";
973  error_exit(1);
974  } catch(const config::error& e) {
975  PLAIN_LOG << e.message;
976  error_exit(1);
977  } catch(const video::quit&) {
978  // just means the game should quit
979  } catch(const return_to_play_side_exception&) {
980  PLAIN_LOG << "caught return_to_play_side_exception, please report this bug (quitting)";
981  } catch(const quit_game_exception&) {
982  PLAIN_LOG << "caught quit_game_exception (quitting)";
983  } catch(const wml_exception& e) {
984  PLAIN_LOG << "WML exception:\nUser message: " << e.user_message << "\nDev message: " << e.dev_message;
985  error_exit(1);
986  } catch(const wfl::formula_error& e) {
987  PLAIN_LOG << e.what() << "\n\nGame will be aborted.";
988  error_exit(1);
989  } catch(const sdl::exception& e) {
990  PLAIN_LOG << e.what();
991  error_exit(1);
992  } catch(const game::error& e) {
993  PLAIN_LOG << "Game error: " << e.what();
994  error_exit(1);
995  } catch(const std::bad_alloc&) {
996  PLAIN_LOG << "Ran out of memory. Aborted.";
997  error_exit(ENOMEM);
998 #if !defined(NO_CATCH_AT_GAME_END)
999  } catch(const std::exception& e) {
1000  // Try to catch unexpected exceptions.
1001  PLAIN_LOG << "Caught general '" << typeid(e).name() << "' exception:\n" << e.what();
1002  error_exit(1);
1003  } catch(const std::string& e) {
1004  PLAIN_LOG << "Caught a string thrown as an exception:\n" << e;
1005  error_exit(1);
1006  } catch(const char* e) {
1007  PLAIN_LOG << "Caught a string thrown as an exception:\n" << e;
1008  error_exit(1);
1009  } catch(...) {
1010  // Ensure that even when we terminate with `throw 42`, the exception
1011  // is caught and all destructors are actually called. (Apparently,
1012  // some compilers will simply terminate without calling destructors if
1013  // the exception isn't caught.)
1014  PLAIN_LOG << "Caught general exception " << utils::get_unknown_exception_type() << ". Terminating.";
1015  error_exit(1);
1016 #endif
1017  }
1018 
1019  return 0;
1020 } // 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:387
double t
Definition: astarsearch.cpp:63
Used in parsing config file.
Definition: validator.hpp:38
bool nogui
True if –nogui was given on the command line.
utils::optional< std::string > validate_wml
Non-empty if –validate 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.
bool headless_unit_test
True if –unit is used and –showgui is not present.
bool no_log_sanitize
True if –no-log-sanitize was given on the command line.
bool strict_lua
True if –strict-lua was given in the commandline.
utils::optional< std::vector< std::string > > preprocess_defines
Defines that were given to the –preprocess option.
utils::optional< std::string > usercache_dir
Non-empty if –usercache-dir was given on the command line.
utils::optional< std::string > validate_schema
Non-empty if –validate-schema was given on the command line.
utils::optional< std::string > userdata_dir
Non-empty if –userdata-dir was given on the command line.
bool nobanner
True if –nobanner was given on the command line.
utils::optional< std::vector< std::pair< lg::severity, std::string > > > log
Contains parsed arguments of –log-* (e.g.
utils::optional< std::string > generate_spritesheet
Path of which to generate a spritesheet.
utils::optional< std::string > render_image
Image path to render.
utils::optional< std::string > logdomains
Non-empty if –logdomains was given on the command line.
utils::optional< std::string > preprocess_input_macros
Non-empty if –preprocess-input-macros was given on the command line.
bool preprocess
True if –preprocess was given on the command line.
utils::optional< unsigned int > rng_seed
RNG seed specified by –rng-seed option.
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.
utils::optional< std::string > validate_with
Non-empty if –use-schema was given on the command line.
bool noaddons
True if –noaddons was given on the command line.
utils::optional< std::string > output_file
Output filename for WML diff or preprocessing.
utils::optional< std::string > data_dir
Non-empty if –data-dir was given on the command line.
utils::optional< std::string > preprocess_target
Target (output) path that was given to the –preprocess option.
bool screenshot
True if –screenshot was given on the command line.
bool log_to_file
True if –log-to-file was given on the command line.
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.
bool no_log_to_file
True if –no-log-to-file was given on the command line.
utils::optional< std::string > preprocess_output_macros
Non-empty if –preprocess-output-macros was given on the command line.
bool strict_validation
True if –strict-validation was given on the command line.
utils::optional< std::string > preprocess_path
Path to parse that was given to the –preprocess option.
bool help
True if –help was given on the command line.
bool usercache_path
True if –usercache-path 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:172
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:810
child_itors child_range(config_key_type key)
Definition: config.cpp:272
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1026
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:910
@ 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
static prefs & get()
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:320
std::vector< std::string > read_argv([[maybe_unused]] int argc, [[maybe_unused]] char **argv)
Definitions for the interface to Wesnoth Markup Language (WML).
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1028
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:300
#define PLAIN_LOG
Definition: log.hpp:299
@ 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:176
std::string get_cache_dir()
Definition: filesystem.cpp:837
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:827
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:325
std::string get_exe_dir()
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
std::string autodetect_game_data_dir(std::string exe_dir)
Try to autodetect the location of the game data dir.
void set_cache_dir(const std::string &newcachedir)
Definition: filesystem.cpp:816
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
std::string get_logs_dir()
Definition: filesystem.cpp:832
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:54
bool is_userdata_initialized()
Definition: filesystem.cpp:608
std::string get_intl_dir()
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:720
bool load_font_config()
Definition: font_config.cpp:58
std::string path
Definition: filesystem.cpp:90
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:78
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:96
bool check_migration
Definition: filesystem.cpp:98
void init()
Initializes the GUI subsystems.
Definition: gui.cpp:34
void switch_theme(const std::string &current_theme)
Set and activate the given gui2 theme.
Definition: gui.cpp:135
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:148
retval
Default window/dialog return values.
Definition: retval.hpp:30
void build_spritesheet_from(const std::string &entry_point)
bool using_own_console()
Returns true if a console was allocated by the Wesnoth process.
severity
Definition: log.hpp:83
std::string list_log_domains(const std::string &filter)
Definition: log.cpp:380
bool broke_strict()
Definition: log.cpp:400
void set_log_to_file()
Do the initial redirection to a log file if the logs directory is writable.
Definition: log.cpp:234
void set_log_sanitize(bool sanitize)
toggle log sanitization
Definition: log.cpp:428
utils::optional< bool > log_dir_writable()
Returns the result set by check_log_dir_writable().
Definition: log.cpp:280
void do_console_redirect()
Allocates a console if needed and redirects output to CONOUT.
void precise_timestamps(bool pt)
Definition: log.cpp:305
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:347
void set_strict_severity(severity severity)
Definition: log.cpp:390
void empty_playlist()
Definition: sound.cpp:612
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition: sound.cpp:715
void stop_music()
Definition: sound.cpp:557
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:622
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 &)
Reports time elapsed at the end of an object scope.
Definition: optimer.hpp:37
An error specifically indicating video subsystem problems.
Definition: video.hpp:313
Helper class, don't construct this directly.
bool strict_validation_enabled
Definition: validator.cpp:21
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define PACKAGE
Definition: wesconfig.h:23
static lg::log_domain log_preprocessor("preprocessor")
static void safe_exit(int res)
Definition: wesnoth.cpp:120
static int do_gameloop(commandline_options &cmdline_opts)
Setups the game environment and enters the titlescreen or game loops.
Definition: wesnoth.cpp:668
static int handle_validate_command(const std::string &file, abstract_validator &validator, const std::vector< std::string > &defines)
Definition: wesnoth.cpp:244
int main(int argc, char **argv)
Definition: wesnoth.cpp:913
static int process_command_args(commandline_options &cmdline_opts)
Process commandline-arguments.
Definition: wesnoth.cpp:272
static void handle_preprocess_command(const commandline_options &cmdline_opts)
Definition: wesnoth.cpp:126
static void check_fpu()
Definition: wesnoth.cpp:639
#define LOG_PREPROC
Definition: wesnoth.cpp:115
static void init_locale()
I would prefer to setup locale first so that early error messages can get localized,...
Definition: wesnoth.cpp:539
static void handle_lua_script_args(game_launcher *game, commandline_options &)
Handles the lua script command line arguments if present.
Definition: wesnoth.cpp:576
#define error_exit(res)
Definition: wesnoth.cpp:906
static void warn_early_init_failure()
Print an alert and instructions to stderr about early initialization errors.
Definition: wesnoth.cpp:563
#define LOG_GENERAL
Definition: wesnoth.cpp:112
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