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