diff --git a/CMakeLists.txt b/CMakeLists.txt index 96b99e68..16b3e650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,12 @@ endif() set(COLOBOT_VERSION_FULL "${COLOBOT_VERSION_MAJOR}.${COLOBOT_VERSION_MINOR}.${COLOBOT_VERSION_REVISION}${COLOBOT_VERSION_UNRELEASED}${COLOBOT_VERSION_RELEASE_CODENAME}") message(STATUS "Building Colobot \"${COLOBOT_VERSION_CODENAME}\" (${COLOBOT_VERSION_FULL})") +set(BUILD_NUMBER 0) +if(NOT "$ENV{BUILD_NUMBER}" STREQUAL "") + set(BUILD_NUMBER "$ENV{BUILD_NUMBER}") + message(STATUS "CI build #${BUILD_NUMBER}") +endif() + ## # Platform detection and some related checks @@ -214,7 +220,7 @@ endif() # Warn about development build if(DEV_BUILD) - message("Building with development extensions") + message(STATUS "Building with development extensions") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4a385275..5dac3972 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -78,6 +78,7 @@ set(BASE_SOURCES app/controller.cpp app/input.cpp app/pausemanager.cpp + app/signal_handlers.cpp app/system.cpp app/${SYSTEM_CPP_MODULE} app/system_other.cpp diff --git a/src/app/main.cpp b/src/app/main.cpp index 1d5a7061..722bb186 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -25,6 +25,7 @@ #include "common/config.h" #include "app/app.h" +#include "app/signal_handlers.h" #include "app/system.h" #if PLATFORM_WINDOWS #include "app/system_windows.h" @@ -89,7 +90,7 @@ extern "C" int SDL_MAIN_FUNC(int argc, char *argv[]) { - CLogger logger; // single istance of logger + CLogger logger; // single instance of logger // Workaround for character encoding in argv on Windows #if PLATFORM_WINDOWS @@ -120,20 +121,22 @@ int SDL_MAIN_FUNC(int argc, char *argv[]) LocalFree(wargv); #endif + logger.Info("%s starting\n", COLOBOT_FULLNAME); + + auto systemUtils = CSystemUtils::Create(); // platform-specific utils + systemUtils->Init(); + + CSignalHandlers::Init(systemUtils.get()); + CResourceManager manager(argv[0]); // Initialize static string arrays InitializeRestext(); InitializeEventTypeTexts(); - logger.Info("%s starting\n", COLOBOT_FULLNAME); - int code = 0; while (true) { - auto systemUtils = CSystemUtils::Create(); // platform-specific utils - systemUtils->Init(); - CApplication app(systemUtils.get()); // single instance of the application ParseArgsStatus status = app.ParseArguments(argc, argv); @@ -173,4 +176,3 @@ int SDL_MAIN_FUNC(int argc, char *argv[]) } } // extern "C" - diff --git a/src/app/signal_handlers.cpp b/src/app/signal_handlers.cpp new file mode 100644 index 00000000..6506daff --- /dev/null +++ b/src/app/signal_handlers.cpp @@ -0,0 +1,129 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2015, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsiteс.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "app/signal_handlers.h" + +#include "common/config.h" + +#include "app/system.h" + +#include "common/stringutils.h" + +#include "level/robotmain.h" + +#include +#include +#include + +CSystemUtils* CSignalHandlers::m_systemUtils = nullptr; + +void CSignalHandlers::Init(CSystemUtils* systemUtils) +{ + m_systemUtils = systemUtils; + signal(SIGSEGV, SignalHandler); + signal(SIGABRT, SignalHandler); + signal(SIGFPE, SignalHandler); + signal(SIGILL, SignalHandler); + std::set_terminate(UnhandledExceptionHandler); +} + +void CSignalHandlers::SignalHandler(int sig) +{ + std::string signalStr = StrUtils::ToString(signal); + switch(sig) + { + case SIGSEGV: signalStr = "SIGSEGV, segmentation fault"; break; + case SIGABRT: signalStr = "SIGABRT, abort"; break; + case SIGFPE: signalStr = "SIGFPE, arithmetic exception"; break; + case SIGILL: signalStr = "SIGILL, illegal instruction"; break; + } + ReportError(signalStr); +} + +// TODO: How portable across compilers is this? +#include +#include +#include +std::string demangle(const char* name) { + int status; + std::unique_ptr result { + abi::__cxa_demangle(name, nullptr, nullptr, &status), + std::free + }; + + return result != nullptr ? result.get() : name; +} +// END OF TODO + +void CSignalHandlers::UnhandledExceptionHandler() +{ + std::exception_ptr exptr = std::current_exception(); + try + { + std::rethrow_exception(exptr); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "Type: " << demangle(typeid(e).name()) << std::endl; + ss << "Message: " << e.what(); + ReportError(ss.str()); + } + catch (...) + { + ReportError("Unknown unhandled exception (not inherited from std::exception)"); + } +} + +void CSignalHandlers::ReportError(const std::string& errorMessage) +{ + std::stringstream msg; + msg << "Unhandled exception occured!" << std::endl; + msg << "==============================" << std::endl; + msg << errorMessage << std::endl; + msg << "==============================" << std::endl; + msg << std::endl; + msg << "This is usually caused by a bug. Please report this on http://github.com/colobot/colobot/issues" << std::endl; + msg << "including information on what you were doing before this happened and all the information below." << std::endl; + msg << "==============================" << std::endl; + #if BUILD_NUMBER == 0 + // COLOBOT_VERSION_DISPLAY doesn't update if you don't run CMake after "git pull" + msg << "You seem to be running a custom compilation of version " << COLOBOT_VERSION_DISPLAY << ", but please verify that." << std::endl; + #else + msg << "You are running version " << COLOBOT_VERSION_DISPLAY << " from CI build #" << BUILD_NUMBER << std::endl; + #endif + msg << std::endl; + if (!CRobotMain::IsCreated()) + { + msg << "CRobotMain instance does not seem to exist" << std::endl; + } + else + { + CRobotMain* robotMain = CRobotMain::GetInstancePointer(); + msg << "The game was in phase " << PhaseToString(robotMain->GetPhase()) << " (ID=" << robotMain->GetPhase() << ")" << std::endl; + msg << "Last started level was: category=" << GetLevelCategoryDir(robotMain->GetLevelCategory()) << " chap=" << robotMain->GetLevelChap() << " rank=" << robotMain->GetLevelRank() << std::endl; + } + msg << "==============================" << std::endl; + msg << std::endl; + msg << "Sorry for inconvenience!"; + + std::cerr << std::endl << msg.str() << std::endl; + m_systemUtils->SystemDialog(SDT_ERROR, "Unhandled exception occured!", msg.str()); + exit(1); +} diff --git a/src/app/signal_handlers.h b/src/app/signal_handlers.h new file mode 100644 index 00000000..8adf2c38 --- /dev/null +++ b/src/app/signal_handlers.h @@ -0,0 +1,38 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2015, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsiteс.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include + +class CSystemUtils; + +class CSignalHandlers +{ +public: + static void Init(CSystemUtils* systemUtils); + +private: + static void SignalHandler(int sig); + static void UnhandledExceptionHandler(); + static void ReportError(const std::string& errorMessage); + +private: + static CSystemUtils* m_systemUtils; +}; diff --git a/src/common/config.h.cmake b/src/common/config.h.cmake index 52125029..05af962f 100644 --- a/src/common/config.h.cmake +++ b/src/common/config.h.cmake @@ -32,3 +32,4 @@ #define COLOBOT_DEFAULT_DATADIR "@COLOBOT_INSTALL_DATA_DIR@" #define COLOBOT_I18N_DIR "@COLOBOT_INSTALL_I18N_DIR@" +#define BUILD_NUMBER @BUILD_NUMBER@ diff --git a/src/level/robotmain.cpp b/src/level/robotmain.cpp index f4c2f977..4e9bff8a 100644 --- a/src/level/robotmain.cpp +++ b/src/level/robotmain.cpp @@ -323,6 +323,35 @@ void CRobotMain::LoadConfigFile() m_settings->LoadSettings(); } +std::string PhaseToString(Phase phase) +{ + if (phase == PHASE_WELCOME1) return "PHASE_WELCOME1"; + if (phase == PHASE_WELCOME2) return "PHASE_WELCOME2"; + if (phase == PHASE_WELCOME3) return "PHASE_WELCOME3"; + if (phase == PHASE_PLAYER_SELECT) return "PHASE_PLAYER_SELECT"; + if (phase == PHASE_APPERANCE) return "PHASE_APPERANCE"; + if (phase == PHASE_MAIN_MENU) return "PHASE_MAIN_MENU"; + if (phase == PHASE_LEVEL_LIST) return "PHASE_LEVEL_LIST"; + if (phase == PHASE_SIMUL) return "PHASE_SIMUL"; + if (phase == PHASE_SETUPd) return "PHASE_SETUPd"; + if (phase == PHASE_SETUPg) return "PHASE_SETUPg"; + if (phase == PHASE_SETUPp) return "PHASE_SETUPp"; + if (phase == PHASE_SETUPc) return "PHASE_SETUPc"; + if (phase == PHASE_SETUPs) return "PHASE_SETUPs"; + if (phase == PHASE_SETUPds) return "PHASE_SETUPds"; + if (phase == PHASE_SETUPgs) return "PHASE_SETUPgs"; + if (phase == PHASE_SETUPps) return "PHASE_SETUPps"; + if (phase == PHASE_SETUPcs) return "PHASE_SETUPcs"; + if (phase == PHASE_SETUPss) return "PHASE_SETUPss"; + if (phase == PHASE_WRITEs) return "PHASE_WRITEs"; + if (phase == PHASE_READ) return "PHASE_READ"; + if (phase == PHASE_READs) return "PHASE_READs"; + if (phase == PHASE_WIN) return "PHASE_WIN"; + if (phase == PHASE_LOST) return "PHASE_LOST"; + if (phase == PHASE_QUIT_SCREEN) return "PHASE_QUIT_SCREEN"; + return "(unknown)"; +} + bool IsInSimulationConfigPhase(Phase phase) { return (phase >= PHASE_SETUPds && phase <= PHASE_SETUPss) || phase == PHASE_READs || phase == PHASE_WRITEs; @@ -579,6 +608,11 @@ void CRobotMain::ChangePhase(Phase phase) m_engine->LoadAllTextures(); } +Phase CRobotMain::GetPhase() +{ + return m_phase; +} + //! Processes an event bool CRobotMain::ProcessEvent(Event &event) { diff --git a/src/level/robotmain.h b/src/level/robotmain.h index 021d8f32..16f53629 100644 --- a/src/level/robotmain.h +++ b/src/level/robotmain.h @@ -72,6 +72,7 @@ enum Phase PHASE_LOST, PHASE_QUIT_SCREEN, }; +std::string PhaseToString(Phase phase); bool IsInSimulationConfigPhase(Phase phase); bool IsPhaseWithWorld(Phase phase); bool IsMainMenuPhase(Phase phase); @@ -166,6 +167,7 @@ public: void ChangePhase(Phase phase); bool ProcessEvent(Event &event); + Phase GetPhase(); bool CreateShortcuts(); void ScenePerso();