The Battle for Wesnoth  1.15.0-dev
wesnoth.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "addon/manager.hpp"
16 #include "build_info.hpp"
17 #include "commandline_options.hpp" // for commandline_options, etc
18 #include "config.hpp" // for config, config::error, etc
19 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
20 #include "editor/editor_main.hpp"
21 #include "filesystem.hpp" // for filesystem::file_exists, filesystem::io_exception, etc
22 #include "floating_label.hpp"
23 #include "font/error.hpp" // for error
24 #include "font/font_config.hpp" // for load_font_config, etc
25 #include "formula/formula.hpp" // for formula_error
26 #include "game_config.hpp" // for path, debug, debug_lua, etc
27 #include "game_config_manager.hpp" // for game_config_manager, etc
28 #include "game_end_exceptions.hpp"
29 #include "game_launcher.hpp" // for game_launcher, etc
30 #include "gettext.hpp"
31 #include "gui/core/event/handler.hpp" // for tmanager
34 #include "gui/dialogs/message.hpp" // for show_error_message
35 #include "gui/dialogs/title_screen.hpp" // for title_screen, etc
36 #include "gui/gui.hpp" // for init
37 #include "image.hpp" // for flush_cache, etc
38 #include "log.hpp" // for LOG_STREAM, general, logger, etc
39 #include "preferences/general.hpp" // for core_id, etc
43 #include "sdl/exception.hpp" // for exception
44 #include "sdl/rect.hpp"
45 #include "serialization/binary_or_text.hpp" // for config_writer
46 #include "serialization/parser.hpp" // for read
47 #include "serialization/preprocessor.hpp" // for preproc_define, etc
49 #include "serialization/validator.hpp" // for strict_validation_enabled
50 #include "sound.hpp" // for commit_music_changes, etc
51 #include "statistics.hpp" // for fresh_stats
52 #include "utils/functional.hpp"
53 #include "version.hpp" // for version_info
54 #include "video.hpp" // for CVideo
55 #include "wesconfig.h" // for PACKAGE
56 #include "wml_exception.hpp" // for wml_exception
57 
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 <SDL.h> // for SDL_Init, SDL_INIT_TIMER
69 
70 #include <boost/iostreams/categories.hpp> // for input, output
71 #include <boost/iostreams/copy.hpp> // for copy
72 #include <boost/iostreams/filter/bzip2.hpp> // for bzip2_compressor, etc
73 
74 #if defined(_MSC_VER)
75 #pragma warning(push)
76 #pragma warning(disable : 4456)
77 #pragma warning(disable : 4458)
78 #endif
79 
80 #include <boost/iostreams/filter/gzip.hpp> // for gzip_compressor, etc
81 
82 #if defined(_MSC_VER)
83 #pragma warning(pop)
84 #endif
85 
86 #include <boost/iostreams/filtering_stream.hpp> // for filtering_stream
87 #include <boost/program_options/errors.hpp> // for error
88 
89 #include <algorithm> // for transform
90 #include <cerrno> // for ENOMEM
91 #include <clocale> // for setlocale, LC_ALL, etc
92 #include <cstdio> // for remove, fprintf, stderr
93 #include <cstdlib> // for srand, exit
94 #include <ctime> // for time, ctime, std::time_t
95 #include <exception> // for exception
96 #include <fstream> // for operator<<, basic_ostream, etc
97 #include <iostream> // for cerr, cout
98 #include <vector>
99 
100 //#define NO_CATCH_AT_GAME_END
101 
102 #ifdef _WIN32
103 
104 #ifdef INADDR_ANY
105 #undef INADDR_ANY
106 #endif
107 
108 #ifdef INADDR_BROADCAST
109 #undef INADDR_BROADCAST
110 #endif
111 
112 #ifdef INADDR_NONE
113 #undef INADDR_NONE
114 #endif
115 
116 #include <windows.h>
117 
118 #endif // _WIN32
119 
120 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
121 #include "gui/widgets/debug.hpp"
122 #endif
123 
124 class end_level_exception;
125 namespace game
126 {
127 struct error;
128 }
129 
130 static lg::log_domain log_config("config");
131 #define LOG_CONFIG LOG_STREAM(info, log_config)
132 
133 #define LOG_GENERAL LOG_STREAM(info, lg::general())
134 
135 static lg::log_domain log_preprocessor("preprocessor");
136 #define LOG_PREPROC LOG_STREAM(info, log_preprocessor)
137 
138 // this is needed to allow identical functionality with clean refactoring
139 // play_game only returns on an error, all returns within play_game can
140 // be replaced with this
141 static void safe_exit(int res)
142 {
143  LOG_GENERAL << "exiting with code " << res << "\n";
144  exit(res);
145 }
146 
147 // maybe this should go in a util file somewhere?
148 template<typename filter>
149 static void encode(const std::string& input_file, const std::string& output_file)
150 {
151  try {
152  std::ifstream ifile(input_file.c_str(), std::ios_base::in | std::ios_base::binary);
153  ifile.peek(); // We need to touch the stream to set the eof bit
154 
155  if(!ifile.good()) {
156  std::cerr << "Input file " << input_file
157  << " is not good for reading. Exiting to prevent bzip2 from segfaulting\n";
158  safe_exit(1);
159  }
160 
161  std::ofstream ofile(output_file.c_str(), std::ios_base::out | std::ios_base::binary);
162 
163  boost::iostreams::filtering_stream<boost::iostreams::output> stream;
164  stream.push(filter());
165  stream.push(ofile);
166 
167  boost::iostreams::copy(ifile, stream);
168  ifile.close();
169 
170  safe_exit(remove(input_file.c_str()));
171  } catch(const filesystem::io_exception& e) {
172  std::cerr << "IO error: " << e.what() << "\n";
173  }
174 }
175 
176 template<typename filter>
177 static void decode(const std::string& input_file, const std::string& output_file)
178 {
179  try {
180  std::ofstream ofile(output_file.c_str(), std::ios_base::out | std::ios_base::binary);
181  std::ifstream ifile(input_file.c_str(), std::ios_base::in | std::ios_base::binary);
182 
183  boost::iostreams::filtering_stream<boost::iostreams::input> stream;
184  stream.push(filter());
185  stream.push(ifile);
186 
187  boost::iostreams::copy(stream, ofile);
188  ifile.close();
189 
190  safe_exit(remove(input_file.c_str()));
191  } catch(const filesystem::io_exception& e) {
192  std::cerr << "IO error: " << e.what() << "\n";
193  }
194 }
195 
196 static void gzip_encode(const std::string& input_file, const std::string& output_file)
197 {
198  encode<boost::iostreams::gzip_compressor>(input_file, output_file);
199 }
200 
201 static void gzip_decode(const std::string& input_file, const std::string& output_file)
202 {
203  decode<boost::iostreams::gzip_decompressor>(input_file, output_file);
204 }
205 
206 static void bzip2_encode(const std::string& input_file, const std::string& output_file)
207 {
208  encode<boost::iostreams::bzip2_compressor>(input_file, output_file);
209 }
210 
211 static void bzip2_decode(const std::string& input_file, const std::string& output_file)
212 {
213  decode<boost::iostreams::bzip2_decompressor>(input_file, output_file);
214 }
215 
216 static void handle_preprocess_command(const commandline_options& cmdline_opts)
217 {
218  preproc_map input_macros;
219 
220  if(cmdline_opts.preprocess_input_macros) {
221  std::string file = *cmdline_opts.preprocess_input_macros;
222  if(filesystem::file_exists(file) == false) {
223  std::cerr << "please specify an existing file. File " << file << " doesn't exist.\n";
224  return;
225  }
226 
227  std::cerr << SDL_GetTicks() << " Reading cached defines from: " << file << "\n";
228 
229  config cfg;
230 
231  try {
233  read(cfg, *stream);
234  } catch(const config::error& e) {
235  std::cerr << "Caught a config error while parsing file '" << file << "':\n" << e.message << std::endl;
236  }
237 
238  int read = 0;
239 
240  // use static preproc_define::read_pair(config) to make a object
241  for(const config::any_child& value : cfg.all_children_range()) {
242  const preproc_map::value_type def = preproc_define::read_pair(value.cfg);
243  input_macros[def.first] = def.second;
244  ++read;
245  }
246 
247  std::cerr << SDL_GetTicks() << " Read " << read << " defines.\n";
248  }
249 
250  const std::string resourceToProcess(*cmdline_opts.preprocess_path);
251  const std::string targetDir(*cmdline_opts.preprocess_target);
252 
253  uint32_t startTime = SDL_GetTicks();
254 
255  // If the users add the SKIP_CORE define we won't preprocess data/core
256  bool skipCore = false;
257  bool skipTerrainGFX = false;
258 
259  // The 'core_defines_map' is the one got from data/core macros
260  preproc_map defines_map(input_macros);
261 
262  if(cmdline_opts.preprocess_defines) {
263  // add the specified defines
264  for(const std::string& define : *cmdline_opts.preprocess_defines) {
265  if(define.empty()) {
266  std::cerr << "empty define supplied\n";
267  continue;
268  }
269 
270  LOG_PREPROC << "adding define: " << define << '\n';
271  defines_map.emplace(define, preproc_define(define));
272 
273  if(define == "SKIP_CORE") {
274  std::cerr << "'SKIP_CORE' defined.\n";
275  skipCore = true;
276  } else if(define == "NO_TERRAIN_GFX") {
277  std::cerr << "'NO_TERRAIN_GFX' defined." << std::endl;
278  skipTerrainGFX = true;
279  }
280  }
281  }
282 
283  // add the WESNOTH_VERSION define
284  defines_map["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
285 
286  std::cerr << "added " << defines_map.size() << " defines.\n";
287 
288  // preprocess core macros first if we don't skip the core
289  if(skipCore == false) {
290  std::cerr << "preprocessing common macros from 'data/core' ...\n";
291 
292  // process each folder explicitly to gain speed
293  preprocess_resource(game_config::path + "/data/core/macros", &defines_map);
294 
295  if(skipTerrainGFX == false) {
296  preprocess_resource(game_config::path + "/data/core/terrain-graphics", &defines_map);
297  }
298 
299  std::cerr << "acquired " << (defines_map.size() - input_macros.size()) << " 'data/core' defines.\n";
300  } else {
301  std::cerr << "skipped 'data/core'\n";
302  }
303 
304  // preprocess resource
305  std::cerr << "preprocessing specified resource: " << resourceToProcess << " ...\n";
306 
307  preprocess_resource(resourceToProcess, &defines_map, true, true, targetDir);
308  std::cerr << "acquired " << (defines_map.size() - input_macros.size()) << " total defines.\n";
309 
310  if(cmdline_opts.preprocess_output_macros) {
311  std::string outputFileName = "_MACROS_.cfg";
312  if(!cmdline_opts.preprocess_output_macros->empty()) {
313  outputFileName = *cmdline_opts.preprocess_output_macros;
314  }
315 
316  std::string outputPath = targetDir + "/" + outputFileName;
317 
318  std::cerr << "writing '" << outputPath << "' with " << defines_map.size() << " defines.\n";
319 
321  if(!out->fail()) {
322  config_writer writer(*out, false);
323 
324  for(auto& define_pair : defines_map) {
325  define_pair.second.write(writer, define_pair.first);
326  }
327  } else {
328  std::cerr << "couldn't open the file.\n";
329  }
330  }
331 
332  std::cerr << "preprocessing finished. Took " << SDL_GetTicks() - startTime << " ticks.\n";
333 }
334 
335 /** Process commandline-arguments */
336 static int process_command_args(const commandline_options& cmdline_opts)
337 {
338  // Options that don't change behavior based on any others should be checked alphabetically below.
339 
340  if(cmdline_opts.userconfig_dir) {
342  }
343 
344  if(cmdline_opts.userconfig_path) {
345  std::cout << filesystem::get_user_config_dir() << '\n';
346  return 0;
347  }
348 
349  if(cmdline_opts.userdata_dir) {
351  }
352 
353  if(cmdline_opts.userdata_path) {
354  std::cout << filesystem::get_user_data_dir() << '\n';
355  return 0;
356  }
357 
358  if(cmdline_opts.data_dir) {
359  const std::string datadir = *cmdline_opts.data_dir;
360 #ifdef _WIN32
361  // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
362  if(datadir.c_str()[1] == ':') {
363 #else
364  if(datadir[0] == '/') {
365 #endif
366  game_config::path = datadir;
367  } else {
368  game_config::path = filesystem::get_cwd() + '/' + datadir;
369  }
370 
372  std::cerr << "Overriding data directory with " << game_config::path << std::endl;
373 
375  std::cerr << "Could not find directory '" << game_config::path << "'\n";
376  throw config::error("directory not found");
377  }
378 
379  // don't update font as we already updating it in game ctor
380  // font_manager_.update_font_path();
381  }
382 
383  if(cmdline_opts.data_path) {
384  std::cout << game_config::path << '\n';
385  return 0;
386  }
387 
388  if(cmdline_opts.debug_lua) {
389  game_config::debug_lua = true;
390  }
391 
392  if(cmdline_opts.gunzip) {
393  const std::string input_file(*cmdline_opts.gunzip);
394  if(!filesystem::is_gzip_file(input_file)) {
395  std::cerr << "file '" << input_file << "'isn't a .gz file\n";
396  return 2;
397  }
398 
399  const std::string output_file(input_file, 0, input_file.length() - 3);
400  gzip_decode(input_file, output_file);
401  }
402 
403  if(cmdline_opts.bunzip2) {
404  const std::string input_file(*cmdline_opts.bunzip2);
405  if(!filesystem::is_bzip2_file(input_file)) {
406  std::cerr << "file '" << input_file << "'isn't a .bz2 file\n";
407  return 2;
408  }
409 
410  const std::string output_file(input_file, 0, input_file.length() - 4);
411  bzip2_decode(input_file, output_file);
412  }
413 
414  if(cmdline_opts.gzip) {
415  const std::string input_file(*cmdline_opts.gzip);
416  const std::string output_file(*cmdline_opts.gzip + ".gz");
417  gzip_encode(input_file, output_file);
418  }
419 
420  if(cmdline_opts.bzip2) {
421  const std::string input_file(*cmdline_opts.bzip2);
422  const std::string output_file(*cmdline_opts.bzip2 + ".bz2");
423  bzip2_encode(input_file, output_file);
424  }
425 
426  if(cmdline_opts.help) {
427  std::cout << cmdline_opts;
428  return 0;
429  }
430 
431  if(cmdline_opts.log) {
432  for(const auto& log_pair : cmdline_opts.log.get()) {
433  const std::string log_domain = log_pair.second;
434  const int severity = log_pair.first;
435  if(!lg::set_log_domain_severity(log_domain, severity)) {
436  std::cerr << "unknown log domain: " << log_domain << '\n';
437  return 2;
438  }
439  }
440  }
441 
442  if(cmdline_opts.logdomains) {
443  std::cout << lg::list_logdomains(*cmdline_opts.logdomains);
444  return 0;
445  }
446 
447  if(cmdline_opts.path) {
448  std::cout << game_config::path << "\n";
449  return 0;
450  }
451 
452  if(cmdline_opts.log_precise_timestamps) {
454  }
455 
456  if(cmdline_opts.rng_seed) {
457  srand(*cmdline_opts.rng_seed);
458  }
459 
460  if(cmdline_opts.screenshot || cmdline_opts.render_image) {
461  SDL_setenv("SDL_VIDEODRIVER", "dummy", 1);
462  }
463 
464  if(cmdline_opts.strict_validation) {
466  }
467 
468  if(cmdline_opts.version) {
469  std::cout << "Battle for Wesnoth" << " " << game_config::wesnoth_version.str() << "\n\n";
470  std::cout << "Library versions:\n" << game_config::library_versions_report() << '\n';
471  std::cout << "Optional features:\n" << game_config::optional_features_report();
472 
473  return 0;
474  }
475 
476  if(cmdline_opts.report) {
477  std::cout << "\n========= BUILD INFORMATION =========\n\n" << game_config::full_build_report();
478  return 0;
479  }
480 
481  // Options changing their behavior dependent on some others should be checked below.
482 
483  if(cmdline_opts.preprocess) {
484  handle_preprocess_command(cmdline_opts);
485  return 0;
486  }
487 
488  // Not the most intuitive solution, but I wanted to leave current semantics for now
489  return -1;
490 }
491 
492 /**
493  * I would prefer to setup locale first so that early error
494  * messages can get localized, but we need the game_launcher
495  * initialized to have filesystem::get_intl_dir() to work. Note: setlocale()
496  * does not take GUI language setting into account.
497  */
498 static void init_locale()
499 {
500 #if defined _WIN32 || defined __APPLE__
501  setlocale(LC_ALL, "English");
502 #else
503  std::setlocale(LC_ALL, "C");
504 #endif
505 
506  const std::string& intl_dir = filesystem::get_intl_dir();
507 
508  translation::bind_textdomain(PACKAGE, intl_dir.c_str(), "UTF-8");
509  translation::bind_textdomain(PACKAGE "-lib", intl_dir.c_str(), "UTF-8");
511 }
512 
513 /**
514  * Print an alert and instructions to stderr about early initialization errors.
515  *
516  * This is provided as an aid for users dealing with potential data dir
517  * configuration issues. The first code to read core WML *has* the
518  * responsibility to call this function in the event of a problem, to inform
519  * the user of the most likely possible cause and suggest a course of action
520  * to solve the issue.
521  */
523 {
524  // NOTE: wrap output to 80 columns.
525  std::cerr << '\n'
526  << "An error at this point during initialization usually indicates that the data\n"
527  << "directory above was not correctly set or detected. Try passing the correct path\n"
528  << "in the command line with the --data-dir switch or as the only argument.\n";
529 }
530 
531 /**
532  * Handles the lua script command line arguments if present.
533  * This function will only run once.
534  */
536 {
537  static bool first_time = true;
538 
539  if(!first_time) {
540  return;
541  }
542 
543  first_time = false;
544 
545  if(!game->init_lua_script()) {
546  // std::cerr << "error when loading lua scripts at startup\n";
547  // std::cerr << "could not load lua script: " << *cmdline_opts.script_file << std::endl;
548  }
549 }
550 
551 #ifdef _MSC_VER
552 static void check_fpu()
553 {
554  uint32_t f_control;
555 
556  if(_controlfp_s(&f_control, 0, 0) == 0) {
557  uint32_t unused;
558  uint32_t rounding_mode = f_control & _MCW_RC;
559  uint32_t precision_mode = f_control & _MCW_PC;
560 
561  if(rounding_mode != _RC_NEAR) {
562  std::cerr << "Floating point rounding mode is currently '"
563  << ((rounding_mode == _RC_CHOP)
564  ? "chop"
565  : (rounding_mode == _RC_UP)
566  ? "up"
567  : (rounding_mode == _RC_DOWN)
568  ? "down"
569  : (rounding_mode == _RC_NEAR) ? "near" : "unknown")
570  << "' setting to 'near'\n";
571 
572  if(_controlfp_s(&unused, _RC_NEAR, _MCW_RC)) {
573  std::cerr << "failed to set floating point rounding type to 'near'\n";
574  }
575  }
576 
577  if(precision_mode != _PC_53) {
578  std::cerr << "Floating point precision mode is currently '"
579  << ((precision_mode == _PC_53)
580  ? "double"
581  : (precision_mode == _PC_24)
582  ? "single"
583  : (precision_mode == _PC_64) ? "double extended" : "unknown")
584  << "' setting to 'double'\n";
585 
586  if(_controlfp_s(&unused, _PC_53, _MCW_PC)) {
587  std::cerr << "failed to set floating point precision type to 'double'\n";
588  }
589  }
590  } else {
591  std::cerr << "_controlfp_s failed.\n";
592  }
593 }
594 #else
595 static void check_fpu()
596 {
597  switch(fegetround()) {
598  case FE_TONEAREST:
599  break;
600  case FE_DOWNWARD:
601  std::cerr << "Floating point precision mode is currently 'downward'";
602  goto reset_fpu;
603  case FE_TOWARDZERO:
604  std::cerr << "Floating point precision mode is currently 'toward-zero'";
605  goto reset_fpu;
606  case FE_UPWARD:
607  std::cerr << "Floating point precision mode is currently 'upward'";
608  goto reset_fpu;
609  default:
610  std::cerr << "Floating point precision mode is currently 'unknown'";
611  goto reset_fpu;
612  reset_fpu:
613  std::cerr << "setting to 'nearest'";
614  fesetround(FE_TONEAREST);
615  break;
616  }
617 }
618 #endif
619 
620 /**
621  * Setups the game environment and enters
622  * the titlescreen or game loops.
623  */
624 static int do_gameloop(const std::vector<std::string>& args)
625 {
626  srand(std::time(nullptr));
627 
628  commandline_options cmdline_opts = commandline_options(args);
630 
631  int finished = process_command_args(cmdline_opts);
632  if(finished != -1) {
633 #ifdef _WIN32
634  if(lg::using_own_console()) {
635  std::cerr << "Press enter to continue..." << std::endl;
636  std::cin.get();
637  }
638 #endif
639 
640  return finished;
641  }
642 
643  const std::unique_ptr<game_launcher> game(new game_launcher(cmdline_opts, args[0].c_str()));
644  const int start_ticks = SDL_GetTicks();
645 
646  init_locale();
647 
648  bool res;
649 
650  // Do initialize fonts before reading the game config, to have game
651  // config error messages displayed. fonts will be re-initialized later
652  // when the language is read from the game config.
653  res = font::load_font_config();
654  if(res == false) {
655  std::cerr << "could not initialize fonts\n";
656  // The most common symptom of a bogus data dir path -- warn the user.
658  return 1;
659  }
660 
661  res = game->init_language();
662  if(res == false) {
663  std::cerr << "could not initialize the language\n";
664  return 1;
665  }
666 
667  res = game->init_video();
668  if(res == false) {
669  std::cerr << "could not initialize display\n";
670  return 1;
671  }
672 
675 
677  if(res == false) {
678  std::cerr << "could not initialize image preferences\n";
679  return 1;
680  }
681 
683 
684  check_fpu();
685  const cursor::manager cursor_manager;
687 
689 
690 #if(defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
691  SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
692 #endif
693 
694  gui2::init();
695  const gui2::event::manager gui_event_manager;
696 
697  game_config_manager config_manager(cmdline_opts, game->jump_to_editor());
698 
699  gui2::dialogs::loading_screen::display([&res, &config_manager]() {
701  res = config_manager.init_game_config(game_config_manager::NO_FORCE_RELOAD);
702 
703  if(res == false) {
704  std::cerr << "could not initialize game config\n";
705  return;
706  }
707 
709 
710  res = font::load_font_config();
711  if(res == false) {
712  std::cerr << "could not re-initialize fonts for the current language\n";
713  return;
714  }
715 
717 
719  });
720 
721  if(res == false) {
722  return 1;
723  }
724 
725  LOG_CONFIG << "time elapsed: " << (SDL_GetTicks() - start_ticks) << " ms\n";
726 
727  plugins_manager plugins_man(new application_lua_kernel);
728 
729  plugins_context::Reg const callbacks[] {
730  {"play_multiplayer", std::bind(&game_launcher::play_multiplayer, game.get(), game_launcher::MP_CONNECT)},
731  };
732 
733  plugins_context::aReg const accessors[] {
734  {"command_line", std::bind(&commandline_options::to_config, &cmdline_opts)},
735  };
736 
737  plugins_context plugins("titlescreen", callbacks, accessors);
738 
739  plugins.set_callback("exit", [](const config& cfg) { safe_exit(cfg["code"].to_int(0)); }, false);
740 
741  for(;;) {
743 
744  if(!game->is_loading()) {
745  const config& cfg = config_manager.game_config().child("titlescreen_music");
746  if(cfg) {
748 
749  for(const config& i : cfg.child_range("music")) {
751  }
752 
754  } else {
757  }
758  }
759 
760  handle_lua_script_args(&*game, cmdline_opts);
761 
762  plugins.play_slice();
763  plugins.play_slice();
764 
765  if(cmdline_opts.unit_test) {
766  if(cmdline_opts.timeout) {
767  std::cerr << "The wesnoth built-in timeout feature has been removed.\n" << std::endl;
768  std::cerr << "Please use a platform-specific script which will kill the overtime process instead.\n"
769  << std::endl;
770  std::cerr << "For examples in bash, or in windows cmd, see the forums, or the wesnoth repository."
771  << std::endl;
772  std::cerr
773  << "The bash script is called `run_wml_tests`, the windows script is part of the VC project.\n"
774  << std::endl;
775  }
776 
777  int worker_result = game->unit_test();
778  std::cerr << ((worker_result == 0) ? "PASS TEST " : "FAIL TEST ")
779  << ((worker_result == 3) ? "(INVALID REPLAY)" : "")
780  << ((worker_result == 4) ? "(ERRORED REPLAY)" : "") << ": " << *cmdline_opts.unit_test
781  << std::endl;
782  return worker_result;
783  }
784 
785  if(game->play_test() == false) {
786  return 0;
787  }
788 
789  if(game->play_screenshot_mode() == false) {
790  return 0;
791  }
792 
793  if(game->play_render_image_mode() == false) {
794  return 0;
795  }
796 
797  // Start directly a campaign
798  if(game->goto_campaign() == false) {
799  if(game->jump_to_campaign_id().empty())
800  continue; // Go to main menu
801  else
802  return 1; // we got an error starting the campaign from command line
803  }
804 
805  // Start directly a multiplayer
806  // Eventually with a specified server
807  if(game->goto_multiplayer() == false) {
808  continue; // Go to main menu
809  }
810 
811  // Start directly a commandline multiplayer game
812  if(game->play_multiplayer_commandline() == false) {
813  return 0;
814  }
815 
816  if(game->goto_editor() == false) {
817  return 0;
818  }
819 
820  const font::floating_label_context label_manager;
821 
823 
825 
826  // If loading a game, skip the titlescreen entirely
827  if(game->is_loading()) {
828  if(!game->load_game()) {
829  game->clear_loaded_game();
830  }
831 
832  game->launch_game(should_reload);
833  continue;
834  }
835 
836  gui2::dialogs::title_screen dlg(*game);
837 
838  /*
839  * Quick explanation of the titlscreen loop:
840  *
841  * The dialog's redraw_background_ flag is initialized as true in the constructor, so the dialog will always
842  * display at least once when this loop is executed. Each time it's opened, the aforementioned flag is set to
843  * false, and any selection that results in leaving the dialog simply sets the window's retval and proceeds to
844  * the appropriate action.
845  *
846  * Certain actions (such as window resizing) set the flag to true, which allows the dialog to reopen with any
847  * layout changes such as those dictated by window resolution.
848  */
850  dlg.show();
851  }
852 
853  switch(dlg.get_retval()) {
855  LOG_GENERAL << "quitting game...\n";
856  return 0;
860  continue;
861  }
862  break;
866  continue;
867  }
868  break;
872  continue;
873  }
874  break;
876  gui2::dialogs::loading_screen::display([&config_manager]() {
877  config_manager.reload_changed_game_config();
878  });
879  break;
881  game->start_editor();
882  break;
884  gui2::dialogs::end_credits::display();
885  break;
887  game->launch_game(should_reload);
888  break;
890  break;
891  }
892  }
893 }
894 
895 #ifdef _WIN32
896 static bool parse_commandline_argument(const char*& next, const char* end, std::string& res)
897 {
898  // strip leading whitespace
899  while(next != end && *next == ' ') {
900  ++next;
901  }
902 
903  if(next == end) {
904  return false;
905  }
906 
907  bool is_excaped = false;
908 
909  for(; next != end; ++next) {
910  if(*next == ' ' && !is_excaped) {
911  break;
912  } else if(*next == '"' && !is_excaped) {
913  is_excaped = true;
914  continue;
915  } else if(*next == '"' && is_excaped && next + 1 != end && *(next + 1) == '"') {
916  res.push_back('"');
917  ++next;
918  continue;
919  } else if(*next == '"' && is_excaped) {
920  is_excaped = false;
921  continue;
922  } else {
923  res.push_back(*next);
924  }
925  }
926 
927  return true;
928 }
929 
930 static std::vector<std::string> parse_commandline_arguments(std::string input)
931 {
932  const char* start = &input[0];
933  const char* end = start + input.size();
934 
935  std::string buffer;
936  std::vector<std::string> res;
937 
938  while(parse_commandline_argument(start, end, buffer)) {
939  res.emplace_back();
940  res.back().swap(buffer);
941  }
942 
943  return res;
944 }
945 #endif
946 
947 #ifndef _WIN32
949 {
950  exit(0);
951 }
952 #endif
953 
954 #ifdef _WIN32
955 #define error_exit(res) \
956  do { \
957  if(lg::using_own_console()) { \
958  std::cerr << "Press enter to continue..." << std::endl; \
959  std::cin.get(); \
960  } \
961  return res; \
962  } while(false)
963 #else
964 #define error_exit(res) return res
965 #endif
966 
967 #ifdef __APPLE__
968 extern "C" int wesnoth_main(int argc, char** argv);
969 int wesnoth_main(int argc, char** argv)
970 #else
971 int main(int argc, char** argv)
972 #endif
973 {
974 #ifdef _WIN32
975  UNUSED(argc);
976  UNUSED(argv);
977 
978  // windows argv is ansi encoded by default
979  std::vector<std::string> args =
980  parse_commandline_arguments(unicode_cast<std::string>(std::wstring(GetCommandLineW())));
981 
982  // HACK: we don't parse command line arguments using program_options until
983  // the startup banner is printed. We need to get a console up and
984  // running before then if requested, so just perform a trivial search
985  // here and let program_options ignore the switch later.
986  for(std::size_t k = 0; k < args.size(); ++k) {
987  if(args[k] == "--wconsole" ||
988  args[k] == "--help" ||
989  args[k] == "--logdomains" ||
990  args[k] == "--path" ||
991  args[k] == "--render-image" ||
992  args[k] == "--report" ||
993  args[k] == "-R" ||
994  args[k] == "--screenshot" ||
995  args[k] == "--data-path" ||
996  args[k] == "--userdata-path" ||
997  args[k] == "--userconfig-path" ||
998  args[k] == "--version"
999  ) {
1001  break;
1002  }
1003  }
1004 
1006 #else
1007  std::vector<std::string> args;
1008  for(int i = 0; i < argc; ++i) {
1009  args.push_back(std::string(argv[i]));
1010  }
1011 #endif
1012 
1013  assert(!args.empty());
1014 
1015  filesystem::init();
1016 
1017  if(SDL_Init(SDL_INIT_TIMER) < 0) {
1018  fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
1019  return (1);
1020  }
1021 
1022 #ifndef _WIN32
1023  struct sigaction terminate_handler;
1024  terminate_handler.sa_handler = wesnoth_terminate_handler;
1025  terminate_handler.sa_flags = 0;
1026 
1027  sigemptyset(&terminate_handler.sa_mask);
1028  sigaction(SIGTERM, &terminate_handler, nullptr);
1029  sigaction(SIGINT, &terminate_handler, nullptr);
1030 #endif
1031 
1032  SDL_StartTextInput();
1033 
1034  try {
1035  std::cerr << "Battle for Wesnoth v" << game_config::revision << '\n';
1036  const std::time_t t = std::time(nullptr);
1037  std::cerr << "Started on " << ctime(&t) << "\n";
1038 
1039  const std::string& exe_dir = filesystem::get_exe_dir();
1040  if(!exe_dir.empty()) {
1041  // Try to autodetect the location of the game data dir. Note that
1042  // the root of the source tree currently doubles as the data dir.
1043  std::string auto_dir;
1044 
1045  // scons leaves the resulting binaries at the root of the source
1046  // tree by default.
1047  if(filesystem::file_exists(exe_dir + "/data/_main.cfg")) {
1048  auto_dir = exe_dir;
1049  }
1050  // cmake encourages creating a subdir at the root of the source
1051  // tree for the build, and the resulting binaries are found in it.
1052  else if(filesystem::file_exists(exe_dir + "/../data/_main.cfg")) {
1053  auto_dir = filesystem::normalize_path(exe_dir + "/..");
1054  }
1055  // In Windows debug builds, the EXE is placed away from the game data dir
1056  // (in projectfiles\VCx\Debug), but the working directory is set to the
1057  // game data dir. Thus, check if the working dir is the game data dir.
1058  else if(filesystem::file_exists(filesystem::get_cwd() + "/data/_main.cfg")) {
1059  auto_dir = filesystem::get_cwd();
1060  }
1061 
1062  if(!auto_dir.empty()) {
1063  std::cerr << "Automatically found a possible data directory at " << filesystem::sanitize_path(auto_dir) << '\n';
1064  game_config::path = auto_dir;
1065  }
1066  }
1067 
1068  const int res = do_gameloop(args);
1069  safe_exit(res);
1070  } catch(const boost::program_options::error& e) {
1071  std::cerr << "Error in command line: " << e.what() << '\n';
1072  error_exit(1);
1073  } catch(const CVideo::error& e) {
1074  std::cerr << "Could not initialize video.\n\n" << e.what() << "\n\nExiting.\n";
1075  error_exit(1);
1076  } catch(const font::error& e) {
1077  std::cerr << "Could not initialize fonts.\n\n" << e.what() << "\n\nExiting.\n";
1078  error_exit(1);
1079  } catch(const config::error& e) {
1080  std::cerr << e.message << "\n";
1081  error_exit(1);
1082  } catch(const CVideo::quit&) {
1083  // just means the game should quit
1084  } catch(const return_to_play_side_exception&) {
1085  std::cerr << "caught return_to_play_side_exception, please report this bug (quitting)\n";
1086  } catch(const quit_game_exception&) {
1087  std::cerr << "caught quit_game_exception (quitting)\n";
1088  } catch(const wml_exception& e) {
1089  std::cerr << "WML exception:\nUser message: " << e.user_message << "\nDev message: " << e.dev_message << '\n';
1090  error_exit(1);
1091  } catch(const wfl::formula_error& e) {
1092  std::cerr << e.what() << "\n\nGame will be aborted.\n";
1093  error_exit(1);
1094  } catch(const sdl::exception& e) {
1095  std::cerr << e.what();
1096  error_exit(1);
1097  } catch(const game::error&) {
1098  // A message has already been displayed.
1099  error_exit(1);
1100  } catch(const std::bad_alloc&) {
1101  std::cerr << "Ran out of memory. Aborted.\n";
1102  error_exit(ENOMEM);
1103 #if !defined(NO_CATCH_AT_GAME_END)
1104  } catch(const std::exception& e) {
1105  // Try to catch unexpected exceptions.
1106  std::cerr << "Caught general '" << typeid(e).name() << "' exception:\n" << e.what() << std::endl;
1107  error_exit(1);
1108  } catch(const std::string& e) {
1109  std::cerr << "Caught a string thrown as an exception:\n" << e << std::endl;
1110  error_exit(1);
1111  } catch(const char* e) {
1112  std::cerr << "Caught a string thrown as an exception:\n" << e << std::endl;
1113  error_exit(1);
1114  } catch(...) {
1115  // Ensure that even when we terminate with `throw 42`, the exception
1116  // is caught and all destructors are actually called. (Apparently,
1117  // some compilers will simply terminate without calling destructors if
1118  // the exception isn't caught.)
1119  std::cerr << "Caught unspecified general exception. Terminating." << std::endl;
1120  error_exit(1);
1121 #endif
1122  }
1123 
1124  return 0;
1125 } // end main
void empty_playlist()
Definition: sound.cpp:574
Exception used to escape form the ai or ui code to playsingle_controller::play_side.
bool log_precise_timestamps
True if –log-precise was given on the command line. Shows timestamps in log with more precision...
#define error_exit(res)
Definition: wesnoth.cpp:964
static void wesnoth_terminate_handler(int)
Definition: wesnoth.cpp:948
void set_callback(const std::string &name, callback_function)
Definition: context.cpp:51
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:423
void bind_textdomain(const char *domain, const char *directory, const char *)
Definition: gettext.cpp:432
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:874
bool using_own_console()
Returns true if a console was allocated by the Wesnoth process.
void stop_music()
Definition: sound.cpp:519
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:173
std::string library_versions_report()
Produce a plain-text report of library versions suitable for stdout/stderr.
Definition: build_info.cpp:348
static void bzip2_encode(const std::string &input_file, const std::string &output_file)
Definition: wesnoth.cpp:206
static void bzip2_decode(const std::string &input_file, const std::string &output_file)
Definition: wesnoth.cpp:211
boost::optional< std::string > bzip2
Non-empty if –bzip2 was given on the command line. Compresses a file to .bz2 and exits...
static lg::log_domain log_preprocessor("preprocessor")
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
static bool file_exists(const fs::path &fpath)
Definition: filesystem.cpp:300
std::string optional_features_report()
Produce a plain-text report of features suitable for stdout/stderr.
Definition: build_info.cpp:388
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
void set_user_data_dir(std::string newprefdir)
Definition: filesystem.cpp:604
child_itors child_range(config_key_type key)
Definition: config.cpp:366
bool userdata_path
True if –userdata-path was given on the command line. Prints path to user data directory and exits...
static void gzip_decode(const std::string &input_file, const std::string &output_file)
Definition: wesnoth.cpp:201
static void progress(loading_stage stage=loading_stage::none)
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
Definition: filesystem.cpp:894
std::string user_message
The message for the user explaining what went wrong.
This class implements the title screen.
boost::optional< std::string > gunzip
Non-empty if –gunzip was given on the command line. Uncompresses a .gz file and exits.
void init()
Some tasks to run on startup.
Definition: filesystem.cpp:259
void fresh_stats()
Definition: statistics.cpp:640
Type that can be thrown as an exception to quit to desktop.
Definition: video.hpp:202
void early_log_file_setup()
Sets up the initial temporary log file.
boost::optional< unsigned int > rng_seed
RNG seed specified by –rng-seed option. Initializes RNG with given seed.
void preprocess_resource(const std::string &res_name, preproc_map *defines_map, bool write_cfg, bool write_plain_cfg, const std::string &parent_directory)
boost::optional< std::string > data_dir
Non-empty if –data-dir was given on the command line. Sets the config dir to the specified one...
Don&#39;t reload if the previous defines equal the new defines.
static void safe_exit(int res)
Definition: wesnoth.cpp:141
This file contains information about validation abstract level interface.
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
Contains the exception interfaces used to signal completion of a scenario, campaign or turn...
bool show(const unsigned auto_close_time=0)
Shows the window.
std::string jump_to_campaign_id() const
-file util.hpp
Definitions for the interface to Wesnoth Markup Language (WML).
static void decode(const std::string &input_file, const std::string &output_file)
Definition: wesnoth.cpp:177
boost::optional< unsigned int > timeout
Non-empty if –timeout was given on the command line. Dependent on –unit.
bool preprocess
True if –preprocess was given on the command line. Starts Wesnoth in preprocessor-only mode...
bool play_multiplayer(mp_selection res)
std::string get_cwd()
Definition: filesystem.cpp:783
unsigned in
If equal to search_counter, the node is off the list.
bool play_render_image_mode()
static void warn_early_init_failure()
Print an alert and instructions to stderr about early initialization errors.
Definition: wesnoth.cpp:522
boost::optional< std::string > userconfig_dir
Non-empty if –userconfig-dir was given on the command line. Sets the user config dir to the specifie...
bool report
True if –report was given on the command line. Prints a bug report-style info dump and exits...
static void check_fpu()
Definition: wesnoth.cpp:595
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:612
Class for writing a config out to a file in pieces.
std::string get_user_data_dir()
Definition: filesystem.cpp:749
std::string get_intl_dir()
static lg::log_domain log_config("config")
bool strict_validation_enabled
Definition: validator.cpp:19
editor::EXIT_STATUS start_editor()
void init()
Initializes the GUI subsystems.
Definition: gui.cpp:35
boost::optional< std::string > logdomains
Non-empty if –logdomains was given on the command line. Prints possible logdomains filtered by given...
#define LOG_GENERAL
Definition: wesnoth.cpp:133
boost::optional< std::string > userdata_dir
Non-empty if –userdata-dir was given on the command line. Sets the user data dir to the specified on...
boost::optional< std::string > gzip
Non-empty if –gzip was given on the command line. Compresses a file to .gz and exits.
void enable_native_console_output()
Switches to using a native console instead of log file redirection.
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
Definition: filesystem.cpp:984
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
std::string path
Definition: game_config.cpp:39
boost::optional< std::string > preprocess_output_macros
Non-empty if –preprocess-output-macros was given on the command line. Outputs all preprocessed macro...
void set_default_textdomain(const char *domain)
Definition: gettext.cpp:441
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on&#39;s version information structs.
Definition: manager.cpp:283
std::string sanitize_path(const std::string &path)
Sanitizes a path to remove references to the user&#39;s name.
bool jump_to_editor() const
const char * what() const noexcept
Definition: exceptions.hpp:37
bool userconfig_path
True if –userconfig-path was given on the command line. Prints path to user config directory and exi...
static void handle_preprocess_command(const commandline_options &cmdline_opts)
Definition: wesnoth.cpp:216
bool path
True if –path was given on the command line. Prints the path to data directory and exits...
boost::optional< std::vector< std::pair< int, std::string > > > log
Contains parsed arguments of –log-* (e.g.
boost::optional< std::string > render_image
Image path to render. First parameter after –render-image.
int main(int argc, char **argv)
Definition: wesnoth.cpp:971
bool load_font_config()
#define PACKAGE
Definition: wesconfig.h:23
Helper class, don&#39;t construct this directly.
static int do_gameloop(const std::vector< std::string > &args)
Setups the game environment and enters the titlescreen or game loops.
Definition: wesnoth.cpp:624
std::string dev_message
The message for developers telling which problem was triggered, this shouldn&#39;t be translated...
static void gzip_encode(const std::string &input_file, const std::string &output_file)
Definition: wesnoth.cpp:196
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:40
bool is_gzip_file(const std::string &filename)
Returns true if the file ends with &#39;.gz&#39;.
Some defines: VERSION, PACKAGE, MIN_SAVEGAME_VERSION.
#define UNUSED(x)
Definition: global.hpp:34
static void handle_lua_script_args(game_launcher *game, commandline_options &)
Handles the lua script command line arguments if present.
Definition: wesnoth.cpp:535
std::string get_exe_dir()
Definition: filesystem.cpp:795
boost::optional< std::string > preprocess_target
Target (output) path that was given to the –preprocess option.
static void init_locale()
I would prefer to setup locale first so that early error messages can get localized, but we need the game_launcher initialized to have filesystem::get_intl_dir() to work.
Definition: wesnoth.cpp:498
bool is_loading() const
Log file control routines for Windows.
std::size_t i
Definition: function.cpp:933
void set_user_config_dir(const std::string &newconfigdir)
Definition: filesystem.cpp:706
bool debug_lua
True if –debug-lua was given in the commandline. Enables some Lua debugging mechanisms.
const std::string revision
Definition: version.cpp:42
boost::optional< std::string > preprocess_path
Path to parse that was given to the –preprocess option.
static int process_command_args(const commandline_options &cmdline_opts)
Process commandline-arguments.
Definition: wesnoth.cpp:336
EXIT_STATUS start(const config &game_conf, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
Definition: editor_main.cpp:28
An exception object used when an IO error occurs.
Definition: filesystem.hpp:48
boost::optional< std::string > bunzip2
Non-empty if –bunzip2 was given on the command line. Uncompresses a .bz2 file and exits...
Struct which will hide all current floating labels, and cause floating labels instantiated after it i...
std::string title_music
bool goto_multiplayer()
Declarations for File-IO.
static int writer(lua_State *L, const void *b, size_t size, void *B)
Definition: lstrlib.cpp:182
Contains a basic exception class for SDL operations.
const version_info wesnoth_version(VERSION)
Definition: version.hpp:210
void launch_game(RELOAD_GAME_DATA reload=RELOAD_DATA)
bool set_log_domain_severity(const std::string &name, int severity)
Definition: log.cpp:118
#define next(ls)
Definition: llex.cpp:32
bool data_path
True if –data-path was given on the command line. Prints path to data directory and exits...
std::string get_user_config_dir()
Definition: filesystem.cpp:720
bool screenshot
True if –screenshot was given on the command line. Starts Wesnoth in screenshot mode.
boost::optional< std::string > unit_test
Non-empty if –unit was given on the command line. Goes directly into unit test mode, into a scenario, if specified.
bool is_bzip2_file(const std::string &filename)
Returns true if the file ends with &#39;.bz2&#39;.
static void display(std::function< void()> f)
#define LOG_PREPROC
Definition: wesnoth.cpp:136
Contains the SDL_Rect helper code.
double t
Definition: astarsearch.cpp:63
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
std::string list_logdomains(const std::string &filter)
Definition: log.cpp:151
void play_music_repeatedly(const std::string &id)
Definition: sound.cpp:644
bool version
True if –version was given on the command line. Prints version and exits.
#define LOG_CONFIG
Definition: wesnoth.cpp:131
Standard logging facilities (interface).
std::string str() const
Serializes the version number into string form.
Definition: version.cpp:115
void run_event_loop()
Definition: events.cpp:420
void clear_loaded_game()
std::string message
Definition: exceptions.hpp:31
bool strict_validation
True if –strict-validation was given on the command line. Makes Wesnoth trust validation errors as f...
boost::optional< std::vector< std::string > > preprocess_defines
Defines that were given to the –preprocess option.
void play_music_config(const config &music_node, int i)
Definition: sound.cpp:666
std::map< std::string, struct preproc_define > preproc_map
#define e
static void encode(const std::string &input_file, const std::string &output_file)
Definition: wesnoth.cpp:149
boost::optional< std::string > preprocess_input_macros
Non-empty if –preprocess-input-macros was given on the command line. Specifies a file that contains ...
void commit_music_changes()
Definition: sound.cpp:783
bool update_from_preferences()
initialize any private data, e.g. algorithm choices from preferences
Definition: image.cpp:1295
bool play_screenshot_mode()
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
static preproc_map::value_type read_pair(const config &)
int wesnoth_main(int argc, char **argv)
Interfaces for manipulating version numbers of engine, add-ons, etc.
bool help
True if –help was given on the command line. Prints help and exits.
bool init_lua_script()
void initialise()
Definition: events.cpp:407
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
std::string full_build_report()
Produce a bug report-style info dump.
Definition: build_info.cpp:420
std::string wesnoth_program_dir
Definition: game_config.cpp:48
bool play_multiplayer_commandline()
void precise_timestamps(bool pt)
Definition: log.cpp:76
void set_debug(bool new_debug)
void play_slice()
Definition: context.cpp:96
filesystem::scoped_ostream ostream_file(const std::string &fname, bool create_directory)
Definition: filesystem.cpp:932