main.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. #include "supertux/main.hpp"
  17. #include <config.h>
  18. #include <version.h>
  19. #include <filesystem>
  20. #include <fstream>
  21. #include <SDL_image.h>
  22. #include <SDL_ttf.h>
  23. #include <physfs.h>
  24. #include <tinygettext/log.hpp>
  25. #include <fmt/format.h>
  26. extern "C" {
  27. #include <findlocale.h>
  28. }
  29. #ifdef WIN32
  30. #include <codecvt>
  31. #endif
  32. #include "addon/addon_manager.hpp"
  33. #include "addon/downloader.hpp"
  34. #include "audio/sound_manager.hpp"
  35. #include "editor/editor.hpp"
  36. #include "editor/layer_icon.hpp"
  37. #include "editor/object_info.hpp"
  38. #include "editor/tile_selection.hpp"
  39. #include "editor/tip.hpp"
  40. #include "editor/tool_icon.hpp"
  41. #include "gui/dialog.hpp"
  42. #include "gui/menu_manager.hpp"
  43. #include "gui/notification.hpp"
  44. #include "math/random.hpp"
  45. #include "object/player.hpp"
  46. #include "object/spawnpoint.hpp"
  47. #include "physfs/physfs_file_system.hpp"
  48. #include "physfs/physfs_sdl.hpp"
  49. #include "physfs/util.hpp"
  50. #include "port/emscripten.hpp"
  51. #include "sdk/integration.hpp"
  52. #include "sprite/sprite_data.hpp"
  53. #include "sprite/sprite_manager.hpp"
  54. #include "supertux/command_line_arguments.hpp"
  55. #include "supertux/console.hpp"
  56. #include "supertux/error_handler.hpp"
  57. #include "supertux/game_manager.hpp"
  58. #include "supertux/game_session.hpp"
  59. #include "supertux/gameconfig.hpp"
  60. #include "supertux/globals.hpp"
  61. #include "supertux/level.hpp"
  62. #include "supertux/level_parser.hpp"
  63. #include "supertux/player_status.hpp"
  64. #include "supertux/resources.hpp"
  65. #include "supertux/savegame.hpp"
  66. #include "supertux/screen_fade.hpp"
  67. #include "supertux/screen_manager.hpp"
  68. #include "supertux/sector.hpp"
  69. #include "supertux/tile.hpp"
  70. #include "supertux/tile_manager.hpp"
  71. #include "supertux/title_screen.hpp"
  72. #include "supertux/world.hpp"
  73. #include "supertux/menu/download_dialog.hpp"
  74. #include "util/file_system.hpp"
  75. #include "util/gettext.hpp"
  76. #include "util/reader_document.hpp"
  77. #include "util/reader_mapping.hpp"
  78. #include "util/string_util.hpp"
  79. #include "util/timelog.hpp"
  80. #include "util/string_util.hpp"
  81. #include "video/sdl_surface.hpp"
  82. #include "video/sdl_surface_ptr.hpp"
  83. #include "video/ttf_surface_manager.hpp"
  84. #include "worldmap/worldmap.hpp"
  85. #include "worldmap/worldmap_screen.hpp"
  86. static Timelog s_timelog;
  87. ConfigSubsystem::ConfigSubsystem() :
  88. m_config()
  89. {
  90. g_config = &m_config;
  91. try {
  92. m_config.load();
  93. }
  94. catch(const std::exception& e)
  95. {
  96. log_info << "Couldn't load config file: " << e.what() << ", using default settings" << std::endl;
  97. }
  98. // init random number stuff
  99. gameRandom.seed(m_config.random_seed);
  100. graphicsRandom.seed(0);
  101. //const char *how = config->random_seed? ", user fixed.": ", from time().";
  102. //log_info << "Using random seed " << config->random_seed << how << std::endl;
  103. }
  104. ConfigSubsystem::~ConfigSubsystem()
  105. {
  106. try
  107. {
  108. m_config.save();
  109. }
  110. catch(std::exception& e)
  111. {
  112. log_warning << "Error saving config: " << e.what() << std::endl;
  113. }
  114. }
  115. Main::Main() :
  116. m_physfs_subsystem(),
  117. m_config_subsystem(),
  118. m_sdl_subsystem(),
  119. m_console_buffer(),
  120. m_input_manager(),
  121. m_video_system(),
  122. m_ttf_surface_manager(),
  123. m_sound_manager(),
  124. m_squirrel_virtual_machine(),
  125. m_tile_manager(),
  126. m_sprite_manager(),
  127. m_profile_manager(),
  128. m_resources(),
  129. m_addon_manager(),
  130. m_console(),
  131. m_game_manager(),
  132. m_screen_manager(),
  133. m_savegame(),
  134. m_downloader() // Used for getting the version of the latest SuperTux release.
  135. {
  136. }
  137. void
  138. Main::init_tinygettext()
  139. {
  140. g_dictionary_manager.reset(new tinygettext::DictionaryManager(std::make_unique<PhysFSFileSystem>(), "UTF-8"));
  141. tinygettext::Log::set_log_info_callback(log_info_callback);
  142. tinygettext::Log::set_log_warning_callback(log_warning_callback);
  143. tinygettext::Log::set_log_error_callback(log_error_callback);
  144. g_dictionary_manager->add_directory("locale");
  145. // Config setting "locale" overrides language detection
  146. if (!g_config->locale.empty())
  147. {
  148. g_dictionary_manager->set_language(tinygettext::Language::from_name(g_config->locale));
  149. }
  150. else
  151. {
  152. FL_Locale *locale;
  153. FL_FindLocale(&locale);
  154. tinygettext::Language language = tinygettext::Language::from_spec( locale->lang?locale->lang:"", locale->country?locale->country:"", locale->variant?locale->variant:"");
  155. FL_FreeLocale(&locale);
  156. g_dictionary_manager->set_language(language);
  157. }
  158. }
  159. PhysfsSubsystem::PhysfsSubsystem(const char* argv0,
  160. std::optional<std::string> forced_datadir,
  161. std::optional<std::string> forced_userdir) :
  162. m_forced_datadir(std::move(forced_datadir)),
  163. m_forced_userdir(std::move(forced_userdir)),
  164. m_datadir(),
  165. m_userdir()
  166. {
  167. if (!PHYSFS_init(argv0))
  168. {
  169. std::stringstream msg;
  170. msg << "Couldn't initialize physfs: " << physfsutil::get_last_error();
  171. throw std::runtime_error(msg.str());
  172. }
  173. else
  174. {
  175. // allow symbolic links
  176. PHYSFS_permitSymbolicLinks(1);
  177. find_mount_datadir();
  178. find_mount_userdir();
  179. }
  180. }
  181. void PhysfsSubsystem::find_mount_datadir()
  182. {
  183. #ifndef __EMSCRIPTEN__
  184. if (const char* assetpack = getenv("ANDROID_ASSET_PACK_PATH"))
  185. {
  186. // Android asset pack has a hardcoded prefix for data files, and PhysFS cannot strip it, so we mount an archive inside an archive
  187. if (!PHYSFS_mount(std::filesystem::canonical(assetpack).string().c_str(), nullptr, 1))
  188. {
  189. log_warning << "Couldn't add '" << assetpack << "' to physfs searchpath: " << physfsutil::get_last_error() << std::endl;
  190. return;
  191. }
  192. PHYSFS_File* data = PHYSFS_openRead("assets/data.zip");
  193. if (!data)
  194. {
  195. log_warning << "Couldn't open assets/data.zip inside '" << assetpack << "' : " << physfsutil::get_last_error() << std::endl;
  196. return;
  197. }
  198. if (!PHYSFS_mountHandle(data, "assets/data.zip", nullptr, 1))
  199. {
  200. log_warning << "Couldn't add assets/data.zip inside '" << assetpack << "' to physfs searchpath: " << physfsutil::get_last_error() << std::endl;
  201. }
  202. return;
  203. }
  204. if (m_forced_datadir)
  205. {
  206. m_datadir = *m_forced_datadir;
  207. }
  208. else if (const char* env_datadir = getenv("SUPERTUX2_DATA_DIR"))
  209. {
  210. m_datadir = env_datadir;
  211. }
  212. else if (const char* env_datadir3 = getenv("ANDROID_MY_OWN_APP_FILE"))
  213. {
  214. m_datadir = env_datadir3;
  215. }
  216. else
  217. {
  218. // check if we run from source dir
  219. char* basepath_c = SDL_GetBasePath();
  220. std::string basepath = basepath_c ? basepath_c : "./";
  221. SDL_free(basepath_c);
  222. if (FileSystem::exists(FileSystem::join(BUILD_DATA_DIR, "credits.stxt")))
  223. {
  224. m_datadir = BUILD_DATA_DIR;
  225. // Add config dir for supplemental files
  226. if (FileSystem::is_directory(BUILD_CONFIG_DATA_DIR))
  227. {
  228. PHYSFS_mount(std::filesystem::canonical(BUILD_CONFIG_DATA_DIR).string().c_str(), nullptr, 1);
  229. }
  230. }
  231. else
  232. {
  233. // if the game is not run from the source directory, try to find
  234. // the global install location
  235. m_datadir = basepath.substr(0, basepath.rfind(INSTALL_SUBDIR_BIN));
  236. m_datadir = FileSystem::join(m_datadir, INSTALL_SUBDIR_SHARE);
  237. }
  238. }
  239. if (!PHYSFS_mount(std::filesystem::canonical(m_datadir).string().c_str(), nullptr, 1))
  240. {
  241. log_warning << "Couldn't add '" << m_datadir << "' to PhysFS searchpath: " << physfsutil::get_last_error() << std::endl;
  242. }
  243. #else
  244. if (!PHYSFS_mount(BUILD_CONFIG_DATA_DIR, nullptr, 1))
  245. {
  246. log_warning << "Couldn't add '" << BUILD_CONFIG_DATA_DIR << "' to PhysFS searchpath: " << physfsutil::get_last_error() << std::endl;
  247. }
  248. #endif
  249. }
  250. /** Re-mounts all essential directories, relative to the data directory, which may have been
  251. overriden in the search path by the user directory or add-ons. */
  252. void PhysfsSubsystem::remount_datadir_static() const
  253. {
  254. add_data_to_search_path("images/credits");
  255. add_data_to_search_path("levels");
  256. add_data_to_search_path("locale");
  257. add_data_to_search_path("scripts");
  258. add_data_to_search_path("shader");
  259. // Re-mount levels from the user directory
  260. const std::string userdir_levels = FileSystem::join(m_userdir, "levels");
  261. if (FileSystem::exists(userdir_levels) &&
  262. !PHYSFS_mount(userdir_levels.c_str(), "levels", 0))
  263. {
  264. log_warning << "Couldn't mount levels from the user directory '" << m_userdir << "' to PhysFS searchpath: " << physfsutil::get_last_error() << std::endl;
  265. }
  266. }
  267. void PhysfsSubsystem::add_data_to_search_path(const std::string& dir) const
  268. {
  269. #ifndef __EMSCRIPTEN__
  270. if (!PHYSFS_mount(FileSystem::join(std::filesystem::canonical(m_datadir).string(), dir).c_str(), dir.c_str(), 0))
  271. {
  272. log_warning << "Couldn't add '" << m_datadir << "/" << dir << "' to PhysFS searchpath: " << physfsutil::get_last_error() << std::endl;
  273. }
  274. #else
  275. if (!PHYSFS_mount(FileSystem::join(BUILD_CONFIG_DATA_DIR, dir).c_str(), dir.c_str(), 0))
  276. {
  277. log_warning << "Couldn't add '" << BUILD_CONFIG_DATA_DIR << "/" << dir << "' to PhysFS searchpath: " << physfsutil::get_last_error() << std::endl;
  278. }
  279. #endif
  280. }
  281. void PhysfsSubsystem::find_mount_userdir()
  282. {
  283. if (m_forced_userdir)
  284. {
  285. m_userdir = *m_forced_userdir;
  286. }
  287. else if (const char* env_userdir = getenv("SUPERTUX2_USER_DIR"))
  288. {
  289. m_userdir = env_userdir;
  290. }
  291. else
  292. {
  293. m_userdir = PHYSFS_getPrefDir("SuperTux","supertux2");
  294. }
  295. //Kept for backwards-compatability only, hence the silence
  296. #ifdef __GNUC__
  297. #pragma GCC diagnostic push
  298. #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  299. #endif
  300. std::string physfs_userdir = PHYSFS_getUserDir();
  301. #ifdef __GNUC__
  302. #pragma GCC diagnostic pop
  303. #endif
  304. #ifndef __HAIKU__
  305. #ifdef _WIN32
  306. std::string olduserdir = FileSystem::join(physfs_userdir, PACKAGE_NAME);
  307. #else
  308. std::string olduserdir = FileSystem::join(physfs_userdir, "." PACKAGE_NAME);
  309. #endif
  310. if (FileSystem::is_directory(olduserdir)) {
  311. std::filesystem::path olduserpath(olduserdir);
  312. std::filesystem::path userpath(m_userdir);
  313. std::filesystem::directory_iterator end_itr;
  314. bool success = true;
  315. // cycle through the directory
  316. for (std::filesystem::directory_iterator itr(olduserpath); itr != end_itr; ++itr) {
  317. try
  318. {
  319. std::filesystem::rename(itr->path().string().c_str(), userpath / itr->path().filename());
  320. }
  321. catch (const std::filesystem::filesystem_error& err)
  322. {
  323. success = false;
  324. log_warning << "Failed to move contents of config directory: " << err.what() << std::endl;
  325. }
  326. }
  327. if (success) {
  328. try
  329. {
  330. std::filesystem::remove_all(olduserpath);
  331. }
  332. catch (const std::filesystem::filesystem_error& err)
  333. {
  334. success = false;
  335. log_warning << "Failed to remove old config directory: " << err.what();
  336. }
  337. }
  338. if (success) {
  339. log_info << "Moved old config dir " << olduserdir << " to " << m_userdir << std::endl;
  340. }
  341. }
  342. #endif
  343. #ifdef EMSCRIPTEN
  344. m_userdir = "/home/web_user/.local/share/supertux2/";
  345. #endif
  346. if (!FileSystem::is_directory(m_userdir))
  347. {
  348. FileSystem::mkdir(m_userdir);
  349. log_info << "Created SuperTux userdir: " << m_userdir << std::endl;
  350. }
  351. #ifdef EMSCRIPTEN
  352. EM_ASM({
  353. try {
  354. FS.mount(IDBFS, {}, m_userdir);
  355. FS.syncfs(true, (err) => { console.log(err); });
  356. } catch(err) {}
  357. }, 0); // EM_ASM is a variadic macro and Clang requires at least 1 value for the variadic argument
  358. #endif
  359. if (!PHYSFS_setWriteDir(m_userdir.c_str()))
  360. {
  361. std::ostringstream msg;
  362. msg << "Failed to use userdir directory '"
  363. << m_userdir << "': errorcode: " << physfsutil::get_last_error();
  364. throw std::runtime_error(msg.str());
  365. }
  366. if (!PHYSFS_mount(m_userdir.c_str(), nullptr, 0))
  367. {
  368. log_warning << "Couldn't add user directory '" << m_userdir << "' to PhysFS searchpath: " << physfsutil::get_last_error() << std::endl;
  369. }
  370. }
  371. void PhysfsSubsystem::print_search_path()
  372. {
  373. const char* writedir = PHYSFS_getWriteDir();
  374. log_info << "PhysfsWriteDir: " << (writedir ? writedir : "(null)") << std::endl;
  375. log_info << "PhysfsSearchPath:" << std::endl;
  376. char** searchpath = PHYSFS_getSearchPath();
  377. for (char** i = searchpath; *i != nullptr; ++i)
  378. {
  379. log_info << " " << *i << std::endl;
  380. }
  381. PHYSFS_freeList(searchpath);
  382. }
  383. PhysfsSubsystem::~PhysfsSubsystem()
  384. {
  385. PHYSFS_deinit();
  386. }
  387. SDLSubsystem::SDLSubsystem()
  388. {
  389. Uint32 flags = SDL_INIT_TIMER | SDL_INIT_VIDEO;
  390. #ifndef UBUNTU_TOUCH
  391. flags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
  392. #endif
  393. if (SDL_Init(flags) < 0)
  394. {
  395. std::stringstream msg;
  396. msg << "Couldn't initialize SDL: " << SDL_GetError();
  397. throw std::runtime_error(msg.str());
  398. }
  399. if (TTF_Init() < 0)
  400. {
  401. std::stringstream msg;
  402. msg << "Couldn't initialize SDL TTF: " << SDL_GetError();
  403. throw std::runtime_error(msg.str());
  404. }
  405. // just to be sure
  406. atexit(TTF_Quit);
  407. atexit(SDL_Quit);
  408. }
  409. SDLSubsystem::~SDLSubsystem()
  410. {
  411. TTF_Quit();
  412. SDL_Quit();
  413. }
  414. void
  415. Main::init_video()
  416. {
  417. VideoSystem::current()->set_title("SuperTux " PACKAGE_VERSION);
  418. const char* icon_fname = "images/engine/icons/supertux-256x256.png";
  419. SDLSurfacePtr icon = SDLSurface::from_file(icon_fname);
  420. VideoSystem::current()->set_icon(*icon);
  421. SDL_ShowCursor(g_config->custom_mouse_cursor ? 0 : 1);
  422. log_info << (g_config->use_fullscreen?"fullscreen ":"window ")
  423. << " Window: " << g_config->window_size
  424. << " Fullscreen: " << g_config->fullscreen_size << "@" << g_config->fullscreen_refresh_rate
  425. << " Area: " << g_config->aspect_size << std::endl;
  426. }
  427. void
  428. Main::resave(const std::string& input_filename, const std::string& output_filename)
  429. {
  430. Editor::s_resaving_in_progress = true;
  431. std::ifstream in(input_filename);
  432. if (!in) {
  433. log_fatal << input_filename << ": couldn't open file for reading" << std::endl;
  434. } else {
  435. log_info << "loading level: " << input_filename << std::endl;
  436. auto level = LevelParser::from_stream(in, input_filename, StringUtil::has_suffix(input_filename, ".stwm"), true);
  437. in.close();
  438. std::ofstream out(output_filename);
  439. if (!out) {
  440. log_fatal << output_filename << ": couldn't open file for writing" << std::endl;
  441. } else {
  442. log_info << "saving level: " << output_filename << std::endl;
  443. level->save(out);
  444. }
  445. }
  446. Editor::s_resaving_in_progress = false;
  447. }
  448. void
  449. Main::launch_game(const CommandLineArguments& args)
  450. {
  451. s_timelog.log("addons");
  452. m_addon_manager.reset(new AddonManager("addons", g_config->addons));
  453. /** Add-ons or the user directory may have possibly overriden essential files,
  454. so re-mount the directories, containing those files. */
  455. m_physfs_subsystem->remount_datadir_static();
  456. m_sdl_subsystem.reset(new SDLSubsystem());
  457. m_console_buffer.reset(new ConsoleBuffer());
  458. #ifdef ENABLE_TOUCHSCREEN_SUPPORT
  459. if (getenv("ANDROID_TV")) {
  460. g_config->mobile_controls = false;
  461. }
  462. #endif
  463. s_timelog.log("controller");
  464. m_input_manager.reset(new InputManager(g_config->keyboard_config, g_config->joystick_config));
  465. s_timelog.log("commandline");
  466. #ifndef EMSCRIPTEN
  467. auto video = g_config->video;
  468. if (args.resave && *args.resave) {
  469. if (args.video) {
  470. video = *args.video;
  471. } else {
  472. video = VideoSystem::VIDEO_NULL;
  473. }
  474. }
  475. s_timelog.log("video");
  476. m_video_system = VideoSystem::create(video);
  477. #else
  478. // Force SDL for WASM builds, as OpenGL is reportedly slow on some devices
  479. m_video_system = VideoSystem::create(VideoSystem::VIDEO_SDL);
  480. #endif
  481. init_video();
  482. m_ttf_surface_manager.reset(new TTFSurfaceManager());
  483. s_timelog.log("audio");
  484. m_sound_manager.reset(new SoundManager());
  485. m_sound_manager->enable_sound(g_config->sound_enabled);
  486. m_sound_manager->enable_music(g_config->music_enabled);
  487. m_sound_manager->set_sound_volume(g_config->sound_volume);
  488. m_sound_manager->set_music_volume(g_config->music_volume);
  489. s_timelog.log("scripting");
  490. m_squirrel_virtual_machine.reset(new SquirrelVirtualMachine(g_config->enable_script_debugger));
  491. s_timelog.log("resources");
  492. m_tile_manager.reset(new TileManager());
  493. m_sprite_manager.reset(new SpriteManager());
  494. m_profile_manager.reset(new ProfileManager());
  495. m_resources.reset(new Resources());
  496. s_timelog.log("integrations");
  497. Integration::setup();
  498. m_console.reset(new Console(*m_console_buffer));
  499. s_timelog.log(nullptr);
  500. m_savegame = std::make_unique<Savegame>(m_profile_manager->get_current_profile(), "");
  501. m_game_manager.reset(new GameManager());
  502. m_screen_manager.reset(new ScreenManager(*m_video_system, *m_input_manager));
  503. if (!args.filenames.empty())
  504. {
  505. for(const auto& start_level : args.filenames)
  506. {
  507. // we have a normal path specified at commandline, not a physfs path.
  508. // So we simply mount that path here...
  509. std::string dir = FileSystem::dirname(start_level);
  510. const std::string filename = FileSystem::basename(start_level);
  511. const std::string fileProtocol = "file://";
  512. const std::string::size_type position = dir.find(fileProtocol);
  513. if (position != std::string::npos) {
  514. dir = dir.replace(position, fileProtocol.length(), "");
  515. }
  516. log_debug << "Adding dir: " << dir << std::endl;
  517. PHYSFS_mount(dir.c_str(), nullptr, true);
  518. if (args.resave && *args.resave)
  519. {
  520. resave(start_level, start_level);
  521. }
  522. else if (args.editor)
  523. {
  524. if (PHYSFS_exists(start_level.c_str())) {
  525. auto editor = std::make_unique<Editor>();
  526. editor->set_level(start_level);
  527. editor->setup();
  528. editor->update(0, Controller());
  529. m_screen_manager->push_screen(std::move(editor));
  530. MenuManager::instance().clear_menu_stack();
  531. m_sound_manager->stop_music(0.5);
  532. } else {
  533. log_warning << "Level " << start_level << " doesn't exist." << std::endl;
  534. }
  535. }
  536. else if (StringUtil::has_suffix(start_level, ".stwm"))
  537. {
  538. m_screen_manager->push_screen(std::make_unique<worldmap::WorldMapScreen>(
  539. std::make_unique<worldmap::WorldMap>(filename, *m_savegame)));
  540. }
  541. else
  542. { // launch game
  543. std::unique_ptr<GameSession> session = std::make_unique<GameSession>(filename, *m_savegame);
  544. gameRandom.seed(g_config->random_seed);
  545. graphicsRandom.seed(0);
  546. if (args.sector || args.spawnpoint)
  547. {
  548. std::string sectorname = args.sector.value_or("main");
  549. const auto& spawnpoints = session->get_current_sector().get_objects_by_type<SpawnPointMarker>();
  550. std::string default_spawnpoint = (spawnpoints.begin() != spawnpoints.end()) ?
  551. "" : spawnpoints.begin()->get_name();
  552. std::string spawnpointname = args.spawnpoint.value_or(default_spawnpoint);
  553. session->set_start_point(sectorname, spawnpointname);
  554. session->restart_level();
  555. }
  556. if (g_config->tux_spawn_pos)
  557. {
  558. // FIXME: Specify start pos for multiple players
  559. session->get_current_sector().get_players()[0]->set_pos(*g_config->tux_spawn_pos);
  560. }
  561. m_screen_manager->push_screen(std::move(session));
  562. }
  563. }
  564. }
  565. else
  566. {
  567. if (args.editor)
  568. {
  569. m_screen_manager->push_screen(std::make_unique<Editor>());
  570. }
  571. else
  572. {
  573. m_screen_manager->push_screen(std::make_unique<TitleScreen>(*m_savegame, g_config->is_christmas()));
  574. if (g_config->do_release_check)
  575. release_check();
  576. }
  577. }
  578. m_screen_manager->run();
  579. }
  580. int
  581. Main::run(int argc, char** argv)
  582. {
  583. // First and foremost, set error handlers (to print stack trace on SIGSEGV, etc.)
  584. ErrorHandler::set_handlers();
  585. #ifdef __EMSCRIPTEN__
  586. init_emscripten();
  587. #endif
  588. #ifdef WIN32
  589. //SDL is used instead of PHYSFS because both create the same path in app data
  590. //However, PHYSFS is not yet initizlized, and this should be run before anything is initialized
  591. std::string prefpath = SDL_GetPrefPath("SuperTux", "supertux2");
  592. std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
  593. //All this conversion stuff is necessary to make this work for internationalized usernames
  594. std::string outpath = prefpath + u8"/console.out";
  595. std::wstring w_outpath = converter.from_bytes(outpath);
  596. _wfreopen(w_outpath.c_str(), L"a", stdout);
  597. std::string errpath = prefpath + u8"/console.err";
  598. std::wstring w_errpath = converter.from_bytes(errpath);
  599. _wfreopen(w_errpath.c_str(), L"a", stderr);
  600. // Create and install global locale - this can fail on some situations:
  601. // - with bad values for env vars (LANG, LC_ALL, ...)
  602. // - targets where libstdc++ uses its generic locales code (https://gcc.gnu.org/legacy-ml/libstdc++/2003-02/msg00345.html)
  603. try
  604. {
  605. std::locale::global(std::locale::classic());
  606. }
  607. catch(const std::runtime_error& err)
  608. {
  609. std::cout << "Warning: " << err.what() << std::endl;
  610. }
  611. #endif
  612. int result = 0;
  613. try
  614. {
  615. CommandLineArguments args;
  616. try
  617. {
  618. args.parse_args(argc, argv);
  619. g_log_level = args.get_log_level();
  620. }
  621. catch(const std::exception& err)
  622. {
  623. std::cout << "Error: " << err.what() << std::endl;
  624. return EXIT_FAILURE;
  625. }
  626. m_physfs_subsystem.reset(new PhysfsSubsystem(argv[0], args.datadir, args.userdir));
  627. m_physfs_subsystem->print_search_path();
  628. s_timelog.log("config");
  629. m_config_subsystem.reset(new ConfigSubsystem());
  630. args.merge_into(*g_config);
  631. s_timelog.log("tinygettext");
  632. init_tinygettext();
  633. switch (args.get_action())
  634. {
  635. case CommandLineArguments::PRINT_VERSION:
  636. args.print_version();
  637. return 0;
  638. case CommandLineArguments::PRINT_HELP:
  639. args.print_help(argv[0]);
  640. return 0;
  641. case CommandLineArguments::PRINT_DATADIR:
  642. args.print_datadir();
  643. return 0;
  644. case CommandLineArguments::PRINT_ACKNOWLEDGEMENTS:
  645. args.print_acknowledgements();
  646. return 0;
  647. default:
  648. launch_game(args);
  649. break;
  650. }
  651. }
  652. catch(const std::exception& e)
  653. {
  654. log_fatal << "Unexpected exception: " << e.what() << std::endl;
  655. result = 1;
  656. }
  657. catch(...)
  658. {
  659. log_fatal << "Unexpected exception" << std::endl;
  660. result = 1;
  661. }
  662. g_dictionary_manager.reset();
  663. #ifdef __ANDROID__
  664. // SDL2 keeps shared libraries loaded after the app is closed,
  665. // when we launch the app again the static initializers will run twice and crash the app.
  666. // So we just need to terminate the app process 'gracefully', without running destructors or atexit() functions.
  667. _exit(result);
  668. #endif
  669. return result;
  670. }
  671. void
  672. Main::release_check()
  673. {
  674. // Detect a potential new release of SuperTux. If a release, other than
  675. // the current one is indicated on the given web file, show a notification on the main menu screen.
  676. const std::string target_file = "ver_info.nfo";
  677. TransferStatusPtr status = m_downloader.request_download("https://raw.githubusercontent.com/SuperTux/addons/master/ver_info.nfo", target_file);
  678. status->then([target_file, status](bool success)
  679. {
  680. if (!success)
  681. {
  682. log_warning << "Error performing new release check: Failed to download \"supertux-versioninfo\" file: " << status->error_msg << std::endl;
  683. return;
  684. }
  685. auto doc = ReaderDocument::from_file(target_file);
  686. auto root = doc.get_root();
  687. if (root.get_name() != "supertux-versioninfo")
  688. {
  689. log_warning << "File is not a \"supertux-versioninfo\" file." << std::endl;
  690. return;
  691. }
  692. auto mapping = root.get_mapping();
  693. std::string latest_ver;
  694. if (mapping.get("latest", latest_ver))
  695. {
  696. const std::string version_full = std::string(PACKAGE_VERSION);
  697. const std::string version = version_full.substr(version_full.find("v") + 1, version_full.find("-") - 1);
  698. if (version != latest_ver)
  699. {
  700. auto notif = std::make_unique<Notification>("new_release_" + latest_ver);
  701. notif->set_text(fmt::format(fmt::runtime(_("New release: SuperTux v{}!")), latest_ver));
  702. notif->on_press([latest_ver]()
  703. {
  704. Dialog::show_confirmation(fmt::format(fmt::runtime(_("A new release of SuperTux (v{}) is available!\nFor more information, you can visit the SuperTux website.\n\nDo you want to visit the website now?")), latest_ver), []()
  705. {
  706. FileSystem::open_url("https://supertux.org");
  707. });
  708. });
  709. MenuManager::instance().set_notification(std::move(notif));
  710. }
  711. }
  712. });
  713. // Set up a download dialog to update the transfer status.
  714. auto dialog = std::make_unique<DownloadDialog>(status, true, true, true);
  715. dialog->set_title(_("Checking for new releases..."));
  716. MenuManager::instance().set_dialog(std::move(dialog));
  717. }
  718. /* EOF */