The Battle for Wesnoth  1.19.2+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 "utils/optional_fwd.hpp"
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(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.no_log_sanitize) {
275  lg::set_log_sanitize(false);
276  }
277 
278  // decide whether to redirect output to a file or not
279  if(cmdline_opts.log_to_file) {
280  cmdline_opts.final_log_redirect_to_file = true;
281  } else if(cmdline_opts.no_log_to_file) {
282  cmdline_opts.final_log_redirect_to_file = false;
283  } else {
284  // write_to_log_file means that writing to the log file will be done, if true.
285  // if false, output will be written to the terminal
286  // on windows, if wesnoth was not started from a console, then it will allocate one
287  cmdline_opts.final_log_redirect_to_file = !getenv("WESNOTH_NO_LOG_FILE")
288  // command line options that imply not redirecting output to a log file
289  // Some switches force a Windows console to be attached to the process even
290  // if Wesnoth is an IMAGE_SUBSYSTEM_WINDOWS_GUI executable because they
291  // turn it into a CLI application. Also, --no-log-to-file in particular attaches
292  // a console to a regular GUI game session.
293  && !cmdline_opts.data_path
294  && !cmdline_opts.help
295  && !cmdline_opts.logdomains
296  && !cmdline_opts.nogui
297  && !cmdline_opts.report
298  && !cmdline_opts.simple_version
299  && !cmdline_opts.userdata_path
300  && !cmdline_opts.version
301  && !cmdline_opts.do_diff
302  && !cmdline_opts.do_patch
303  && !cmdline_opts.preprocess
304  && !cmdline_opts.render_image
305  && !cmdline_opts.screenshot
306  && !cmdline_opts.headless_unit_test
307  && !cmdline_opts.validate_schema
308  && !cmdline_opts.validate_wml;
309  }
310 
311  if(cmdline_opts.usercache_dir) {
313  }
314 
315  if(cmdline_opts.userdata_dir) {
317  }
318 
319  // earliest possible point to ensure the userdata directory is known
321  filesystem::set_user_data_dir(std::string());
322  }
323 
324  // userdata is initialized, so initialize logging to file if enabled
325  if(cmdline_opts.final_log_redirect_to_file) {
327  }
328 
329  if(cmdline_opts.log) {
330  for(const auto& log_pair : *cmdline_opts.log) {
331  const std::string log_domain = log_pair.second;
332  const lg::severity severity = log_pair.first;
333  if(!lg::set_log_domain_severity(log_domain, severity)) {
334  PLAIN_LOG << "unknown log domain: " << log_domain;
335  return 2;
336  }
337  }
338  }
339 
340  if(cmdline_opts.usercache_path) {
341  std::cout << filesystem::get_cache_dir();
342  return 0;
343  }
344 
345  if(cmdline_opts.userdata_path) {
346  std::cout << filesystem::get_user_data_dir();
347  return 0;
348  }
349 
350  if(cmdline_opts.data_dir) {
351  const std::string datadir = *cmdline_opts.data_dir;
352  PLAIN_LOG << "Starting with directory: '" << datadir << "'";
353 #ifdef _WIN32
354  // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
355  if(datadir.c_str()[1] == ':') {
356 #else
357  if(datadir[0] == '/') {
358 #endif
359  game_config::path = datadir;
360  } else {
361  game_config::path = filesystem::get_cwd() + '/' + datadir;
362  }
363 
364  PLAIN_LOG << "Now have with directory: '" << game_config::path << "'";
366  if(!cmdline_opts.nobanner) {
367  PLAIN_LOG << "Overriding data directory with '" << game_config::path << "'";
368  }
369 
371  PLAIN_LOG << "Could not find directory '" << game_config::path << "'";
372  throw config::error("directory not found");
373  }
374 
375  // don't update font as we already updating it in game ctor
376  // font_manager_.update_font_path();
377  }
378 
379  if(!cmdline_opts.nobanner) {
380  PLAIN_LOG << "Battle for Wesnoth v" << game_config::revision << " " << game_config::build_arch();
381  const std::time_t t = std::time(nullptr);
382  PLAIN_LOG << "Started on " << ctime(&t);
383  }
384 
385  if(std::string exe_dir = filesystem::get_exe_dir(); !exe_dir.empty()) {
386  if(std::string auto_dir = filesystem::autodetect_game_data_dir(std::move(exe_dir)); !auto_dir.empty()) {
387  if(!cmdline_opts.nobanner) {
388  PLAIN_LOG << "Automatically found a possible data directory at: " << auto_dir;
389  }
390  game_config::path = std::move(auto_dir);
391  } else if(game_config::path.empty()) {
392  if (!cmdline_opts.data_dir.has_value()) {
393  PLAIN_LOG << "Cannot find a data directory. Specify one with --data-dir";
394  return 1;
395  }
396  }
397  }
398 
399  if(cmdline_opts.data_path) {
400  std::cout << game_config::path;
401  return 0;
402  }
403 
404  if(cmdline_opts.debug_lua) {
405  game_config::debug_lua = true;
406  }
407 
408  if(cmdline_opts.allow_insecure) {
410  }
411 
412  if(cmdline_opts.strict_lua) {
414  }
415 
416  if(cmdline_opts.help) {
417  std::cout << cmdline_opts;
418  return 0;
419  }
420 
421  if(cmdline_opts.logdomains) {
422  std::cout << lg::list_log_domains(*cmdline_opts.logdomains);
423  return 0;
424  }
425 
426  if(cmdline_opts.log_precise_timestamps) {
428  }
429 
430  if(cmdline_opts.rng_seed) {
431  srand(*cmdline_opts.rng_seed);
432  }
433 
434  if(cmdline_opts.render_image) {
435  SDL_setenv("SDL_VIDEODRIVER", "dummy", 1);
436  }
437 
438  if(cmdline_opts.strict_validation) {
440  }
441 
442  if(cmdline_opts.version) {
443  std::cout << "Battle for Wesnoth" << " " << game_config::wesnoth_version.str() << "\n\n";
444  std::cout << "Library versions:\n" << game_config::library_versions_report() << '\n';
445  std::cout << "Optional features:\n" << game_config::optional_features_report();
446 
447  return 0;
448  }
449 
450  if(cmdline_opts.simple_version) {
451  std::cout << game_config::wesnoth_version.str() << "\n";
452 
453  return 0;
454  }
455 
456  if(cmdline_opts.report) {
457  std::cout << "\n========= BUILD INFORMATION =========\n\n" << game_config::full_build_report();
458  return 0;
459  }
460 
461  if(cmdline_opts.validate_schema) {
463  validator.set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
464  return handle_validate_command(*cmdline_opts.validate_schema, validator, {});
465  }
466 
467  if(cmdline_opts.do_diff) {
468  config left, right;
469  std::ifstream in_left(cmdline_opts.diff_left);
470  std::ifstream in_right(cmdline_opts.diff_right);
471  read(left, in_left);
472  read(right, in_right);
473  std::ostream* os = &std::cout;
474  if(cmdline_opts.output_file) {
475  os = new std::ofstream(*cmdline_opts.output_file);
476  }
478  out.write(right.get_diff(left));
479  if(os != &std::cout) delete os;
480  return 0;
481  }
482 
483  if(cmdline_opts.do_patch) {
484  config base, diff;
485  std::ifstream in_base(cmdline_opts.diff_left);
486  std::ifstream in_diff(cmdline_opts.diff_right);
487  read(base, in_base);
488  read(diff, in_diff);
489  base.apply_diff(diff);
490  std::ostream* os = &std::cout;
491  if(cmdline_opts.output_file) {
492  os = new std::ofstream(*cmdline_opts.output_file);
493  }
495  out.write(base);
496  if(os != &std::cout) delete os;
497  return 0;
498  }
499 
500  // Options changing their behavior dependent on some others should be checked below.
501 
502  if(cmdline_opts.preprocess) {
503  handle_preprocess_command(cmdline_opts);
504  return 0;
505  }
506 
507  if(cmdline_opts.validate_wml) {
508  std::string schema_path;
509  if(cmdline_opts.validate_with) {
510  schema_path = *cmdline_opts.validate_with;
511  if(!filesystem::file_exists(schema_path)) {
512  if(auto check = filesystem::get_wml_location(schema_path)) {
513  schema_path = check.value();
514  } else {
515  PLAIN_LOG << "Could not find schema file: " << schema_path;
516  }
517  } else {
518  schema_path = filesystem::normalize_path(schema_path);
519  }
520  } else {
521  schema_path = filesystem::get_wml_location("schema/game_config.cfg").value();
522  }
523  schema_validation::schema_validator validator(schema_path);
524  validator.set_create_exceptions(false); // Don't crash if there's an error, just go ahead anyway
525  return handle_validate_command(*cmdline_opts.validate_wml, validator,
526  cmdline_opts.preprocess_defines.value_or<decltype(cmdline_opts.preprocess_defines)::value_type>({}));
527  }
528 
529  if(cmdline_opts.preprocess_defines || cmdline_opts.preprocess_input_macros || cmdline_opts.preprocess_path) {
530  // It would be good if this was supported for running tests too, possibly for other uses.
531  // For the moment show an error message instead of leaving the user wondering why it doesn't work.
532  PLAIN_LOG << "That --preprocess-* option is only supported when using --preprocess or --validate-wml.";
533  // Return an error status other than -1, because in our caller -1 means no error
534  return -2;
535  }
536 
537  // Not the most intuitive solution, but I wanted to leave current semantics for now
538  return -1;
539 }
540 
541 /**
542  * I would prefer to setup locale first so that early error
543  * messages can get localized, but we need the game_launcher
544  * initialized to have filesystem::get_intl_dir() to work. Note: setlocale()
545  * does not take GUI language setting into account.
546  */
547 static void init_locale()
548 {
549 #if defined _WIN32 || defined __APPLE__
550  setlocale(LC_ALL, "English");
551 #else
552  std::setlocale(LC_ALL, "C");
553 #endif
554 
555  const std::string& intl_dir = filesystem::get_intl_dir();
556 
557  translation::bind_textdomain(PACKAGE, intl_dir.c_str(), "UTF-8");
558  translation::bind_textdomain(PACKAGE "-lib", intl_dir.c_str(), "UTF-8");
560 }
561 
562 /**
563  * Print an alert and instructions to stderr about early initialization errors.
564  *
565  * This is provided as an aid for users dealing with potential data dir
566  * configuration issues. The first code to read core WML *has* the
567  * responsibility to call this function in the event of a problem, to inform
568  * the user of the most likely possible cause and suggest a course of action
569  * to solve the issue.
570  */
572 {
573  // NOTE: wrap output to 80 columns.
574  PLAIN_LOG << '\n'
575  << "An error at this point during initialization usually indicates that the data\n"
576  << "directory above was not correctly set or detected. Try passing the correct path\n"
577  << "in the command line with the --data-dir switch or as the only argument.";
578 }
579 
580 /**
581  * Handles the lua script command line arguments if present.
582  * This function will only run once.
583  */
585 {
586  static bool first_time = true;
587 
588  if(!first_time) {
589  return;
590  }
591 
592  first_time = false;
593 
594  if(!game->init_lua_script()) {
595  // PLAIN_LOG << "error when loading lua scripts at startup";
596  // PLAIN_LOG << "could not load lua script: " << *cmdline_opts.script_file;
597  }
598 }
599 
600 #ifdef _MSC_VER
601 static void check_fpu()
602 {
603  uint32_t f_control;
604 
605  if(_controlfp_s(&f_control, 0, 0) == 0) {
606  uint32_t unused;
607  uint32_t rounding_mode = f_control & _MCW_RC;
608 
609  if(rounding_mode != _RC_NEAR) {
610  PLAIN_LOG << "Floating point rounding mode is currently '"
611  << ((rounding_mode == _RC_CHOP)
612  ? "chop"
613  : (rounding_mode == _RC_UP)
614  ? "up"
615  : (rounding_mode == _RC_DOWN)
616  ? "down"
617  : (rounding_mode == _RC_NEAR) ? "near" : "unknown")
618  << "' setting to 'near'";
619 
620  if(_controlfp_s(&unused, _RC_NEAR, _MCW_RC)) {
621  PLAIN_LOG << "failed to set floating point rounding type to 'near'";
622  }
623  }
624 
625 #ifndef _M_AMD64
626  uint32_t precision_mode = f_control & _MCW_PC;
627  if(precision_mode != _PC_53) {
628  PLAIN_LOG << "Floating point precision mode is currently '"
629  << ((precision_mode == _PC_53)
630  ? "double"
631  : (precision_mode == _PC_24)
632  ? "single"
633  : (precision_mode == _PC_64) ? "double extended" : "unknown")
634  << "' setting to 'double'";
635 
636  if(_controlfp_s(&unused, _PC_53, _MCW_PC)) {
637  PLAIN_LOG << "failed to set floating point precision type to 'double'";
638  }
639  }
640 #endif
641 
642  } else {
643  PLAIN_LOG << "_controlfp_s failed.";
644  }
645 }
646 #else
647 static void check_fpu()
648 {
649  switch(fegetround()) {
650  case FE_TONEAREST:
651  break;
652  case FE_DOWNWARD:
653  STREAMING_LOG << "Floating point precision mode is currently 'downward'";
654  goto reset_fpu;
655  case FE_TOWARDZERO:
656  STREAMING_LOG << "Floating point precision mode is currently 'toward-zero'";
657  goto reset_fpu;
658  case FE_UPWARD:
659  STREAMING_LOG << "Floating point precision mode is currently 'upward'";
660  goto reset_fpu;
661  default:
662  STREAMING_LOG << "Floating point precision mode is currently 'unknown'";
663  goto reset_fpu;
664  reset_fpu:
665  STREAMING_LOG << " - setting to 'nearest'\n";
666  fesetround(FE_TONEAREST);
667  break;
668  }
669 }
670 #endif
671 
672 /**
673  * Setups the game environment and enters
674  * the titlescreen or game loops.
675  */
676 static int do_gameloop(commandline_options& cmdline_opts)
677 {
678  srand(std::time(nullptr));
679 
680  const auto game = std::make_unique<game_launcher>(cmdline_opts);
681  const int start_ticks = SDL_GetTicks();
682 
683  init_locale();
684 
685  bool res;
686 
687  // Do initialize fonts before reading the game config, to have game
688  // config error messages displayed. fonts will be re-initialized later
689  // when the language is read from the game config.
690  res = font::load_font_config();
691  if(res == false) {
692  PLAIN_LOG << "could not initialize fonts";
693  // The most common symptom of a bogus data dir path -- warn the user.
695  return 1;
696  }
697 
698  res = game->init_language();
699  if(res == false) {
700  PLAIN_LOG << "could not initialize the language";
701  return 1;
702  }
703 
704  res = game->init_video();
705  if(res == false) {
706  PLAIN_LOG << "could not initialize display";
707  return 1;
708  }
709 
710  check_fpu();
711  const cursor::manager cursor_manager;
713 
714 #if(defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
715  SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
716 #endif
717 
718  gui2::init();
719  const gui2::event::manager gui_event_manager;
720 
721  // if the log directory is not writable, then this is the error condition so show the error message.
722  // if the log directory is writable, then there's no issue.
723  // if the optional isn't set, then logging to file has been disabled, so there's no issue.
724  if(!lg::log_dir_writable().value_or(true)) {
725  utils::string_map symbols;
726  symbols["logdir"] = filesystem::get_logs_dir();
727  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);
729  }
730 
731  game_config_manager config_manager(cmdline_opts);
732 
736  }
737 
738  gui2::dialogs::loading_screen::display([&res, &config_manager, &cmdline_opts]() {
741 
742  if(res == false) {
743  PLAIN_LOG << "could not initialize game config";
744  return;
745  }
746 
748 
749  res = font::load_font_config();
750  if(res == false) {
751  PLAIN_LOG << "could not re-initialize fonts for the current language";
752  return;
753  }
754 
755  if(!game_config::no_addons && !cmdline_opts.noaddons) {
757 
759  }
760  });
761 
762  if(res == false) {
763  return 1;
764  }
765 
766  LOG_CONFIG << "time elapsed: " << (SDL_GetTicks() - start_ticks) << " ms";
767 
768  plugins_manager plugins_man(new application_lua_kernel);
769 
770  const plugins_context::reg_vec callbacks {
771  {"play_multiplayer", std::bind(&game_launcher::play_multiplayer, game.get(), game_launcher::mp_mode::CONNECT)},
772  };
773 
774  const plugins_context::areg_vec accessors {
775  {"command_line", std::bind(&commandline_options::to_config, &cmdline_opts)},
776  };
777 
778  plugins_context plugins("titlescreen", callbacks, accessors);
779 
780  plugins.set_callback("exit", [](const config& cfg) { safe_exit(cfg["code"].to_int(0)); }, false);
781 
782  while(true) {
783  if(!game->has_load_data()) {
784  auto cfg = config_manager.game_config().optional_child("titlescreen_music");
785  if(cfg) {
786  for(const config& i : cfg->child_range("music")) {
788  }
789 
790  config title_music_config;
791  title_music_config["name"] = game_config::title_music;
792  title_music_config["append"] = true;
793  title_music_config["immediate"] = true;
794  sound::play_music_config(title_music_config);
795  } else {
798  }
799  }
800 
801  handle_lua_script_args(&*game, cmdline_opts);
802 
803  plugins.play_slice();
804  plugins.play_slice();
805 
806  if(!cmdline_opts.unit_test.empty()) {
807  return static_cast<int>(game->unit_test());
808  }
809 
810  if(game->play_test() == false) {
811  return 0;
812  }
813 
814  if(game->play_screenshot_mode() == false) {
815  return 0;
816  }
817 
818  if(game->play_render_image_mode() == false) {
819  return 0;
820  }
821 
822  // Start directly a campaign
823  if(game->goto_campaign() == false) {
824  if(game->jump_to_campaign_id().empty())
825  continue; // Go to main menu
826  else
827  return 1; // we got an error starting the campaign from command line
828  }
829 
830  // Start directly a multiplayer
831  // Eventually with a specified server
832  if(game->goto_multiplayer() == false) {
833  continue; // Go to main menu
834  }
835 
836  // Start directly a commandline multiplayer game
837  if(game->play_multiplayer_commandline() == false) {
838  return 0;
839  }
840 
841  if(game->goto_editor() == false) {
842  return 0;
843  }
844 
845  const font::floating_label_context label_manager;
846 
848 
849  // If loading a game, skip the titlescreen entirely
850  if(game->has_load_data() && game->load_game()) {
852  continue;
853  }
854 
855  int retval;
856  { // scope to not keep the title screen alive all game
858 
859  // Allows re-layout on resize
861  dlg.show();
862  }
863  retval = dlg.get_retval();
864  }
865 
866  switch(retval) {
868  LOG_GENERAL << "quitting game...";
869  return 0;
872  game->play_multiplayer(game_launcher::mp_mode::CONNECT);
873  break;
876  game->play_multiplayer(game_launcher::mp_mode::HOST);
877  break;
880  game->play_multiplayer(game_launcher::mp_mode::LOCAL);
881  break;
883  gui2::dialogs::loading_screen::display([&config_manager]() {
884  config_manager.reload_changed_game_config();
885  });
886  break;
888  game->start_editor();
889  break;
892  break;
894  break;
895  }
896  }
897 }
898 
899 #ifdef _WIN32
900 #define error_exit(res) \
901  do { \
902  if(lg::using_own_console()) { \
903  std::cerr << "Press enter to continue..." << std::endl; \
904  std::cin.get(); \
905  } \
906  return res; \
907  } while(false)
908 #else
909 #define error_exit(res) return res
910 #endif
911 
912 #ifdef __APPLE__
913 extern "C" int wesnoth_main(int argc, char** argv);
914 int wesnoth_main(int argc, char** argv)
915 #else
916 int main(int argc, char** argv)
917 #endif
918 {
919  auto args = read_argv(argc, argv);
920  assert(!args.empty());
921 
922 #ifdef _WIN32
923  _putenv("PANGOCAIRO_BACKEND=fontconfig");
924  _putenv("FONTCONFIG_PATH=fonts");
925 #endif
926 
927  try {
928  commandline_options cmdline_opts = commandline_options(args);
929  int finished = process_command_args(cmdline_opts);
930 
931 #ifndef _WIN32
932  if(finished != -1) {
933  safe_exit(finished);
934  }
935 #else
936  // else handle redirecting the output and potentially attaching a console on windows
937  if(!cmdline_opts.final_log_redirect_to_file) {
938  if(!cmdline_opts.no_console) {
940  }
941  if(finished != -1) {
942  if(lg::using_own_console()) {
943  std::cerr << "Press enter to continue..." << std::endl;
944  std::cin.get();
945  }
946  safe_exit(finished);
947  }
948  }
949 #endif
950 
951  SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
952  // Is there a reason not to just use SDL_INIT_EVERYTHING?
953  if(SDL_Init(SDL_INIT_TIMER) < 0) {
954  PLAIN_LOG << "Couldn't initialize SDL: " << SDL_GetError();
955  return (1);
956  }
957  atexit(SDL_Quit);
958 
959  // Mac's touchpad generates touch events too.
960  // Ignore them until Macs have a touchscreen: https://forums.libsdl.org/viewtopic.php?p=45758
961 #if defined(__APPLE__) && !defined(__IPHONEOS__)
962  SDL_EventState(SDL_FINGERMOTION, SDL_DISABLE);
963  SDL_EventState(SDL_FINGERDOWN, SDL_DISABLE);
964  SDL_EventState(SDL_FINGERUP, SDL_DISABLE);
965 #endif
966 
967  // declare this here so that it will always be at the front of the event queue.
968  events::event_context global_context;
969 
970  SDL_StartTextInput();
971 
972  const int res = do_gameloop(cmdline_opts);
973  safe_exit(res);
974  } catch(const boost::program_options::error& e) {
975  // logging hasn't been initialized by this point
976  std::string error = "Error parsing command line arguments: ";
977  error += e.what();
978  SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", error.c_str(), nullptr);
979  std::cerr << "Error in command line: " << e.what();
980  error_exit(1);
981  } catch(const video::error& e) {
982  PLAIN_LOG << "Video system error: " << e.what();
983  error_exit(1);
984  } catch(const font::error& e) {
985  PLAIN_LOG << "Could not initialize fonts.\n\n" << e.what() << "\n\nExiting.";
986  error_exit(1);
987  } catch(const config::error& e) {
988  PLAIN_LOG << e.message;
989  error_exit(1);
990  } catch(const gui::button::error&) {
991  PLAIN_LOG << "Could not create button: Image could not be found";
992  error_exit(1);
993  } catch(const video::quit&) {
994  // just means the game should quit
995  } catch(const return_to_play_side_exception&) {
996  PLAIN_LOG << "caught return_to_play_side_exception, please report this bug (quitting)";
997  } catch(const quit_game_exception&) {
998  PLAIN_LOG << "caught quit_game_exception (quitting)";
999  } catch(const wml_exception& e) {
1000  PLAIN_LOG << "WML exception:\nUser message: " << e.user_message << "\nDev message: " << e.dev_message;
1001  error_exit(1);
1002  } catch(const wfl::formula_error& e) {
1003  PLAIN_LOG << e.what() << "\n\nGame will be aborted.";
1004  error_exit(1);
1005  } catch(const sdl::exception& e) {
1006  PLAIN_LOG << e.what();
1007  error_exit(1);
1008  } catch(const game::error& e) {
1009  PLAIN_LOG << "Game error: " << e.what();
1010  error_exit(1);
1011  } catch(const std::bad_alloc&) {
1012  PLAIN_LOG << "Ran out of memory. Aborted.";
1013  error_exit(ENOMEM);
1014 #if !defined(NO_CATCH_AT_GAME_END)
1015  } catch(const std::exception& e) {
1016  // Try to catch unexpected exceptions.
1017  PLAIN_LOG << "Caught general '" << typeid(e).name() << "' exception:\n" << e.what();
1018  error_exit(1);
1019  } catch(const std::string& e) {
1020  PLAIN_LOG << "Caught a string thrown as an exception:\n" << e;
1021  error_exit(1);
1022  } catch(const char* e) {
1023  PLAIN_LOG << "Caught a string thrown as an exception:\n" << e;
1024  error_exit(1);
1025  } catch(...) {
1026  // Ensure that even when we terminate with `throw 42`, the exception
1027  // is caught and all destructors are actually called. (Apparently,
1028  // some compilers will simply terminate without calling destructors if
1029  // the exception isn't caught.)
1030  PLAIN_LOG << "Caught general exception " << utils::get_unknown_exception_type() << ". Terminating.";
1031  error_exit(1);
1032 #endif
1033  }
1034 
1035  return 0;
1036 } // 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
const std::string name_
Definition: validator.hpp:101
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 > 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.
bool final_log_redirect_to_file
final result of determining whether to log to file or not
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: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:299
#define PLAIN_LOG
Definition: log.hpp:298
@ 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:803
utils::optional< 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, if either exists.
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:793
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:324
std::string get_exe_dir()
Definition: filesystem.cpp:969
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
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:782
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:52
std::string get_logs_dir()
Definition: filesystem.cpp:798
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:53
bool is_userdata_initialized()
Definition: filesystem.cpp:574
std::string get_intl_dir()
std::string get_cwd()
Definition: filesystem.cpp:885
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:686
bool load_font_config()
Definition: font_config.cpp:79
std::string path
Definition: filesystem.cpp:89
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:75
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:94
bool check_migration
Definition: filesystem.cpp:97
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:377
bool broke_strict()
Definition: log.cpp:397
void set_log_to_file()
Do the initial redirection to a log file if the logs directory is writable.
Definition: log.cpp:231
void set_log_sanitize(bool sanitize)
toggle log sanitization
Definition: log.cpp:437
utils::optional< bool > log_dir_writable()
Returns the result set by check_log_dir_writable().
Definition: log.cpp:277
void do_console_redirect()
Allocates a console if needed and redirects output to CONOUT.
void precise_timestamps(bool pt)
Definition: log.cpp:302
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:344
void set_strict_severity(severity severity)
Definition: log.cpp:387
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:713
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.
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 int do_gameloop(commandline_options &cmdline_opts)
Setups the game environment and enters the titlescreen or game loops.
Definition: wesnoth.cpp:676
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:916
static int process_command_args(commandline_options &cmdline_opts)
Process commandline-arguments.
Definition: wesnoth.cpp:270
static void handle_preprocess_command(const commandline_options &cmdline_opts)
Definition: wesnoth.cpp:123
static void check_fpu()
Definition: wesnoth.cpp:647
#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:547
static void handle_lua_script_args(game_launcher *game, commandline_options &)
Handles the lua script command line arguments if present.
Definition: wesnoth.cpp:584
#define error_exit(res)
Definition: wesnoth.cpp:909
static void warn_early_init_failure()
Print an alert and instructions to stderr about early initialization errors.
Definition: wesnoth.cpp:571
#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