main.cpp 25 KB

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