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