2023-10-19 16:19:09 +02:00
/*
* CConsoleHandler . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
# include "CConsoleHandler.h"
# include "CConfigHandler.h"
# include "CThreadHelper.h"
2024-08-01 21:54:24 +00:00
# include <boost/stacktrace.hpp>
2025-01-01 20:58:30 +00:00
# if defined(NDEBUG) && !defined(VCMI_ANDROID)
# define USE_ON_TERMINATE
# endif
# if defined(NDEBUG) && defined(VCMI_WINDOWS)
# define USE_UNHANDLED_EXCEPTION_FILTER
# define CREATE_MEMORY_DUMP
# endif
2023-10-19 16:19:09 +02:00
# ifndef VCMI_WINDOWS
2025-02-28 11:37:56 +00:00
constexpr const char * CONSOLE_GREEN = " \x1b [1;32m " ;
constexpr const char * CONSOLE_RED = " \x1b [1;31m " ;
constexpr const char * CONSOLE_MAGENTA = " \x1b [1;35m " ;
constexpr const char * CONSOLE_YELLOW = " \x1b [1;33m " ;
constexpr const char * CONSOLE_WHITE = " \x1b [1;37m " ;
constexpr const char * CONSOLE_GRAY = " \x1b [1;30m " ;
constexpr const char * CONSOLE_TEAL = " \x1b [1;36m " ;
2023-10-19 16:19:09 +02:00
# else
2025-02-28 11:37:56 +00:00
# include <windows.h>
# include <dbghelp.h>
2023-10-19 16:19:09 +02:00
# ifndef __MINGW32__
# pragma comment(lib, "dbghelp.lib")
# endif
2025-02-28 11:37:56 +00:00
HANDLE handleIn ;
HANDLE handleOut ;
HANDLE handleErr ;
constexpr int32_t CONSOLE_GREEN = FOREGROUND_GREEN | FOREGROUND_INTENSITY ;
constexpr int32_t CONSOLE_RED = FOREGROUND_RED | FOREGROUND_INTENSITY ;
constexpr int32_t CONSOLE_MAGENTA = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ;
constexpr int32_t CONSOLE_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY ;
constexpr int32_t CONSOLE_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY ;
constexpr int32_t CONSOLE_GRAY = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE ;
constexpr int32_t CONSOLE_TEAL = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY ;
2023-10-19 16:19:09 +02:00
# endif
VCMI_LIB_NAMESPACE_BEGIN
2025-01-01 20:58:30 +00:00
# ifdef CREATE_MEMORY_DUMP
2023-10-19 16:19:09 +02:00
2025-01-01 20:58:30 +00:00
static void createMemoryDump ( MINIDUMP_EXCEPTION_INFORMATION * meinfo )
2023-10-19 16:19:09 +02:00
{
2025-01-01 20:58:30 +00:00
//create file where dump will be placed
char * mname = nullptr ;
char buffer [ MAX_PATH + 1 ] ;
HMODULE hModule = nullptr ;
GetModuleFileNameA ( hModule , buffer , MAX_PATH ) ;
mname = strrchr ( buffer , ' \\ ' ) ;
if ( mname ! = nullptr )
mname + + ;
else
mname = buffer ;
strcat ( mname , " _crashinfo.dmp " ) ;
HANDLE dfile = CreateFileA ( mname , GENERIC_READ | GENERIC_WRITE , FILE_SHARE_WRITE | FILE_SHARE_READ , 0 , CREATE_ALWAYS , 0 , 0 ) ;
logGlobal - > error ( " Crash info will be put in %s " , mname ) ;
auto dumpType = MiniDumpWithDataSegs ;
if ( settings [ " general " ] [ " extraDump " ] . Bool ( ) )
2023-10-19 16:19:09 +02:00
{
2025-01-01 20:58:30 +00:00
dumpType = ( MINIDUMP_TYPE ) (
MiniDumpWithFullMemory
| MiniDumpWithFullMemoryInfo
| MiniDumpWithHandleData
| MiniDumpWithUnloadedModules
| MiniDumpWithThreadInfo ) ;
2023-10-19 16:19:09 +02:00
}
2025-01-01 20:58:30 +00:00
MiniDumpWriteDump ( GetCurrentProcess ( ) , GetCurrentProcessId ( ) , dfile , dumpType , meinfo , nullptr , nullptr ) ;
MessageBoxA ( 0 , " VCMI has crashed. We are sorry. File with information about encountered problem has been created. " , " VCMI Crashhandler " , MB_OK | MB_ICONERROR ) ;
2023-10-19 16:19:09 +02:00
}
2025-01-01 20:58:30 +00:00
# endif
# ifdef USE_UNHANDLED_EXCEPTION_FILTER
2023-10-19 16:19:09 +02:00
const char * exceptionName ( DWORD exc )
{
# define EXC_CASE(EXC) case EXCEPTION_##EXC : return "EXCEPTION_" #EXC
switch ( exc )
{
EXC_CASE ( ACCESS_VIOLATION ) ;
EXC_CASE ( DATATYPE_MISALIGNMENT ) ;
EXC_CASE ( BREAKPOINT ) ;
EXC_CASE ( SINGLE_STEP ) ;
EXC_CASE ( ARRAY_BOUNDS_EXCEEDED ) ;
EXC_CASE ( FLT_DENORMAL_OPERAND ) ;
EXC_CASE ( FLT_DIVIDE_BY_ZERO ) ;
EXC_CASE ( FLT_INEXACT_RESULT ) ;
EXC_CASE ( FLT_INVALID_OPERATION ) ;
EXC_CASE ( FLT_OVERFLOW ) ;
EXC_CASE ( FLT_STACK_CHECK ) ;
EXC_CASE ( FLT_UNDERFLOW ) ;
EXC_CASE ( INT_DIVIDE_BY_ZERO ) ;
EXC_CASE ( INT_OVERFLOW ) ;
EXC_CASE ( PRIV_INSTRUCTION ) ;
EXC_CASE ( IN_PAGE_ERROR ) ;
EXC_CASE ( ILLEGAL_INSTRUCTION ) ;
EXC_CASE ( NONCONTINUABLE_EXCEPTION ) ;
EXC_CASE ( STACK_OVERFLOW ) ;
EXC_CASE ( INVALID_DISPOSITION ) ;
EXC_CASE ( GUARD_PAGE ) ;
EXC_CASE ( INVALID_HANDLE ) ;
default :
return " UNKNOWN EXCEPTION " ;
}
# undef EXC_CASE
}
2024-08-01 21:54:24 +00:00
LONG WINAPI onUnhandledException ( EXCEPTION_POINTERS * exception )
{
logGlobal - > error ( " Disaster happened. " ) ;
PEXCEPTION_RECORD einfo = exception - > ExceptionRecord ;
logGlobal - > error ( " Reason: 0x%x - %s at %04x:%x " , einfo - > ExceptionCode , exceptionName ( einfo - > ExceptionCode ) , exception - > ContextRecord - > SegCs , ( void * ) einfo - > ExceptionAddress ) ;
if ( einfo - > ExceptionCode = = EXCEPTION_ACCESS_VIOLATION )
{
logGlobal - > error ( " Attempt to %s 0x%8x " , ( einfo - > ExceptionInformation [ 0 ] = = 1 ? " write to " : " read from " ) , ( void * ) einfo - > ExceptionInformation [ 1 ] ) ;
}
const DWORD threadId = : : GetCurrentThreadId ( ) ;
logGlobal - > error ( " Thread ID: %d " , threadId ) ;
//exception info to be placed in the dump
MINIDUMP_EXCEPTION_INFORMATION meinfo = { threadId , exception , TRUE } ;
2025-01-01 20:58:30 +00:00
# ifdef CREATE_MEMORY_DUMP
2024-08-01 21:54:24 +00:00
createMemoryDump ( & meinfo ) ;
2025-01-01 20:58:30 +00:00
# endif
2024-08-01 21:54:24 +00:00
return EXCEPTION_EXECUTE_HANDLER ;
}
# endif
2025-01-01 20:58:30 +00:00
# ifdef USE_ON_TERMINATE
2024-07-15 07:46:40 +00:00
[[noreturn]] static void onTerminate ( )
2024-06-05 20:49:54 +00:00
{
logGlobal - > error ( " Disaster happened. " ) ;
try
{
std : : exception_ptr eptr { std : : current_exception ( ) } ;
if ( eptr )
{
std : : rethrow_exception ( eptr ) ;
}
else
{
logGlobal - > error ( " ...but no current exception found! " ) ;
}
}
catch ( const std : : exception & exc )
{
logGlobal - > error ( " Reason: %s " , exc . what ( ) ) ;
}
catch ( . . . )
{
logGlobal - > error ( " Reason: unknown exception! " ) ;
}
2024-08-01 21:54:24 +00:00
logGlobal - > error ( " Call stack information: " ) ;
std : : stringstream stream ;
stream < < boost : : stacktrace : : stacktrace ( ) ;
logGlobal - > error ( " %s " , stream . str ( ) ) ;
2025-01-01 20:58:30 +00:00
# ifdef CREATE_MEMORY_DUMP
2024-06-05 20:49:54 +00:00
const DWORD threadId = : : GetCurrentThreadId ( ) ;
logGlobal - > error ( " Thread ID: %d " , threadId ) ;
createMemoryDump ( nullptr ) ;
2024-08-01 21:54:24 +00:00
# endif
2024-06-05 20:49:54 +00:00
std : : abort ( ) ;
}
2024-08-13 22:31:23 +00:00
# endif
2024-06-05 20:49:54 +00:00
2025-02-28 11:37:56 +00:00
void CConsoleHandler : : setColor ( EConsoleTextColor color )
2023-10-19 16:19:09 +02:00
{
TColor colorCode ;
switch ( color )
{
case EConsoleTextColor : : DEFAULT :
colorCode = defColor ;
break ;
case EConsoleTextColor : : GREEN :
colorCode = CONSOLE_GREEN ;
break ;
case EConsoleTextColor : : RED :
colorCode = CONSOLE_RED ;
break ;
case EConsoleTextColor : : MAGENTA :
colorCode = CONSOLE_MAGENTA ;
break ;
case EConsoleTextColor : : YELLOW :
colorCode = CONSOLE_YELLOW ;
break ;
case EConsoleTextColor : : WHITE :
colorCode = CONSOLE_WHITE ;
break ;
case EConsoleTextColor : : GRAY :
colorCode = CONSOLE_GRAY ;
break ;
case EConsoleTextColor : : TEAL :
colorCode = CONSOLE_TEAL ;
break ;
default :
colorCode = defColor ;
break ;
}
# ifdef VCMI_WINDOWS
SetConsoleTextAttribute ( handleOut , colorCode ) ;
if ( color = = EConsoleTextColor : : DEFAULT )
colorCode = defErrColor ;
SetConsoleTextAttribute ( handleErr , colorCode ) ;
# else
std : : cout < < colorCode ;
# endif
}
2025-02-27 23:10:11 +00:00
int CConsoleHandler : : run ( )
2023-10-19 16:19:09 +02:00
{
setThreadName ( " consoleHandler " ) ;
//disabling sync to make in_avail() work (othervice always returns 0)
{
2025-02-27 23:10:11 +00:00
std : : lock_guard guard ( smx ) ;
2023-10-19 16:19:09 +02:00
std : : ios : : sync_with_stdio ( false ) ;
}
std : : string buffer ;
while ( std : : cin . good ( ) )
{
2025-03-02 11:26:55 +00:00
# ifndef _MSC_VER
2023-10-19 16:19:09 +02:00
//check if we have some unreaded symbols
if ( std : : cin . rdbuf ( ) - > in_avail ( ) )
{
if ( getline ( std : : cin , buffer ) . good ( ) )
2025-02-27 23:10:11 +00:00
if ( cb )
cb ( buffer , false ) ;
2023-10-19 16:19:09 +02:00
}
else
2025-03-01 22:34:33 +00:00
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 100 ) ) ;
2023-10-19 16:19:09 +02:00
2025-03-01 22:34:33 +00:00
if ( shutdownPending )
return - 1 ;
2023-10-19 16:19:09 +02:00
# else
std : : getline ( std : : cin , buffer ) ;
2025-02-28 11:37:56 +00:00
if ( cb )
cb ( buffer , false ) ;
2023-10-19 16:19:09 +02:00
# endif
}
return - 1 ;
}
2025-02-27 23:10:11 +00:00
CConsoleHandler : : CConsoleHandler ( )
: CConsoleHandler ( std : : function < void ( const std : : string & , bool ) > { } )
{ }
2025-02-28 11:37:56 +00:00
CConsoleHandler : : CConsoleHandler ( const std : : function < void ( const std : : string & , bool ) > & callback )
2025-02-27 23:10:11 +00:00
: cb ( callback )
2023-10-19 16:19:09 +02:00
{
# ifdef VCMI_WINDOWS
handleIn = GetStdHandle ( STD_INPUT_HANDLE ) ;
handleOut = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
handleErr = GetStdHandle ( STD_ERROR_HANDLE ) ;
CONSOLE_SCREEN_BUFFER_INFO csbi ;
GetConsoleScreenBufferInfo ( handleOut , & csbi ) ;
defColor = csbi . wAttributes ;
GetConsoleScreenBufferInfo ( handleErr , & csbi ) ;
defErrColor = csbi . wAttributes ;
# else
defColor = " \x1b [0m " ;
# endif
2024-08-01 21:54:24 +00:00
2025-01-01 20:58:30 +00:00
# ifdef USE_UNHANDLED_EXCEPTION_FILTER
SetUnhandledExceptionFilter ( onUnhandledException ) ;
# endif
# ifdef USE_ON_TERMINATE
2024-08-01 21:54:24 +00:00
std : : set_terminate ( onTerminate ) ;
# endif
2023-10-19 16:19:09 +02:00
}
2025-01-01 20:58:30 +00:00
2023-10-19 16:19:09 +02:00
CConsoleHandler : : ~ CConsoleHandler ( )
{
logGlobal - > info ( " Killing console... " ) ;
end ( ) ;
logGlobal - > info ( " Killing console... done! " ) ;
}
void CConsoleHandler : : end ( )
{
2025-02-27 23:10:11 +00:00
if ( thread . joinable ( ) )
2023-10-19 16:19:09 +02:00
{
2025-03-02 11:26:55 +00:00
# ifndef _MSC_VER
2025-03-01 22:34:33 +00:00
shutdownPending = true ;
2023-10-19 16:19:09 +02:00
# else
2025-02-28 11:37:56 +00:00
TerminateThread ( thread . native_handle ( ) , 0 ) ;
2023-10-19 16:19:09 +02:00
# endif
2025-02-27 23:10:11 +00:00
thread . join ( ) ;
2023-10-19 16:19:09 +02:00
}
}
void CConsoleHandler : : start ( )
{
2025-03-02 10:50:34 +00:00
thread = std : : thread ( std : : bind ( & CConsoleHandler : : run , this ) ) ;
2023-10-19 16:19:09 +02:00
}
VCMI_LIB_NAMESPACE_END