/* sys_con.c - stdout and log Copyright (C) 2007 Uncle Mike 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. */ #include "common.h" #if XASH_WIN32 #define STDOUT_FILENO 1 #include <io.h> #elif XASH_ANDROID #include <android/log.h> #endif #include <string.h> #include <errno.h> #if !XASH_WIN32 && !XASH_MOBILE_PLATFORM // #define XASH_COLORIZE_CONSOLE // use with caution, running engine in Qt Creator may cause a freeze in read() call // I was never encountered this bug anywhere else, so still enable by default // #define XASH_USE_SELECT 1 #endif #if XASH_USE_SELECT // non-blocking console input #include <sys/select.h> #endif typedef struct { char title[64]; qboolean log_active; char log_path[MAX_SYSPATH]; FILE *logfile; int logfileno; } LogData; static LogData s_ld; char *Sys_Input( void ) { #if XASH_USE_SELECT { fd_set rfds; static char line[1024]; static int len; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO( &rfds ); FD_SET( 0, &rfds); // stdin while( select( 1, &rfds, NULL, NULL, &tv ) > 0 ) { if( read( 0, &line[len], 1 ) != 1 ) break; if( line[len] == '\n' || len > 1022 ) { line[ ++len ] = 0; len = 0; return line; } len++; tv.tv_sec = 0; tv.tv_usec = 0; } } #endif #if XASH_WIN32 return Wcon_Input(); #endif return NULL; } void Sys_DestroyConsole( void ) { // last text message into console or log Con_Reportf( "Sys_DestroyConsole: Exiting!\n" ); #if XASH_WIN32 Wcon_DestroyConsole(); #endif } /* =============================================================================== SYSTEM LOG =============================================================================== */ int Sys_LogFileNo( void ) { return s_ld.logfileno; } static void Sys_FlushStdout( void ) { // never printing anything to stdout on mobiles #if !XASH_MOBILE_PLATFORM fflush( stdout ); #endif } static void Sys_FlushLogfile( void ) { if( s_ld.logfile ) fflush( s_ld.logfile ); } void Sys_InitLog( void ) { const char *mode; if( Sys_CheckParm( "-log" ) && host.allow_console != 0 ) { s_ld.log_active = true; Q_strncpy( s_ld.log_path, "engine.log", sizeof( s_ld.log_path )); } if( host.change_game && host.type != HOST_DEDICATED ) mode = "a"; else mode = "w"; // create log if needed if( s_ld.log_active ) { s_ld.logfile = fopen( s_ld.log_path, mode ); if ( !s_ld.logfile ) { Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ) ); return; } s_ld.logfileno = fileno( s_ld.logfile ); fprintf( s_ld.logfile, "=================================================================================\n" ); fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL ) ); fprintf( s_ld.logfile, "=================================================================================\n" ); } } void Sys_CloseLog( void ) { char event_name[64]; // continue logged switch( host.status ) { case HOST_CRASHED: Q_strncpy( event_name, "crashed", sizeof( event_name )); break; case HOST_ERR_FATAL: Q_strncpy( event_name, "stopped with error", sizeof( event_name )); break; default: if( !host.change_game ) Q_strncpy( event_name, "stopped", sizeof( event_name )); else Q_strncpy( event_name, host.finalmsg, sizeof( event_name )); break; } Sys_FlushStdout(); // flush to stdout to ensure all data was written if( s_ld.logfile ) { fprintf( s_ld.logfile, "\n"); fprintf( s_ld.logfile, "================================================================================="); if( host.change_game ) fprintf( s_ld.logfile, "\n\t%s (build %i) %s\n", s_ld.title, Q_buildnum(), event_name ); else fprintf( s_ld.logfile, "\n\t%s (build %i) %s at %s\n", s_ld.title, Q_buildnum(), event_name, Q_timestamp( TIME_FULL )); fprintf( s_ld.logfile, "=================================================================================\n"); fclose( s_ld.logfile ); s_ld.logfile = NULL; } } static void Sys_PrintColorized( const char *logtime, const char *msg ) { char colored[4096]; int len = 0; while( *msg && ( len < 4090 ) ) { static char q3ToAnsi[ 8 ] = { '0', // COLOR_BLACK '1', // COLOR_RED '2', // COLOR_GREEN '3', // COLOR_YELLOW '4', // COLOR_BLUE '6', // COLOR_CYAN '5', // COLOR_MAGENTA 0 // COLOR_WHITE }; if( IsColorString( msg ) ) { int color; msg++; color = q3ToAnsi[ *msg++ % 8 ]; colored[len++] = '\033'; colored[len++] = '['; if( color ) { colored[len++] = '3'; colored[len++] = color; } else colored[len++] = '0'; colored[len++] = 'm'; } else colored[len++] = *msg++; } colored[len] = 0; printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); } static void Sys_PrintFile( int fd, const char *logtime, const char *msg ) { const char *p = msg; write( fd, logtime, Q_strlen( logtime ) ); while( p && *p ) { p = Q_strchr( msg, '^' ); if( p == NULL ) { write( fd, msg, Q_strlen( msg )); break; } else if( IsColorString( p )) { if( p != msg ) { write( fd, msg, p - msg ); } msg = p + 2; } else { write( fd, msg, p - msg + 1 ); msg = p + 1; } } } static void Sys_PrintStdout( const char *logtime, const char *msg ) { #if XASH_MOBILE_PLATFORM static char buf[MAX_PRINT_MSG]; // strip color codes COM_StripColors( msg, buf ); // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", buf ); #endif // XASH_ANDROID && !XASH_DEDICATED #if TARGET_OS_IOS void IOS_Log( const char * ); IOS_Log( buf ); #endif // TARGET_OS_IOS #else // XASH_MOBILE_PLATFORM Sys_PrintFile( STDOUT_FILENO, logtime, msg ); #endif } void Sys_PrintLog( const char *pMsg ) { time_t crt_time; const struct tm *crt_tm; char logtime[32] = ""; static char lastchar; time( &crt_time ); crt_tm = localtime( &crt_time ); if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time // spew to stdout #ifdef XASH_COLORIZE_CONSOLE Sys_PrintColorized( logtime, pMsg ); #elif !XASH_WIN32 // Wcon already does the job Sys_PrintStdout( logtime, pMsg ); #endif Sys_FlushStdout(); if( !s_ld.logfile ) return; if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time // save last char to detect when line was not ended lastchar = pMsg[Q_strlen( pMsg ) - 1]; Sys_PrintFile( s_ld.logfileno, logtime, pMsg ); Sys_FlushLogfile(); } /* ============================================================================= CONSOLE PRINT ============================================================================= */ /* ============= Con_Printf ============= */ void GAME_EXPORT Con_Printf( const char *szFmt, ... ) { static char buffer[MAX_PRINT_MSG]; va_list args; if( !host.allow_console ) return; va_start( args, szFmt ); Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); va_end( args ); Sys_Print( buffer ); } /* ============= Con_DPrintf ============= */ void GAME_EXPORT Con_DPrintf( const char *szFmt, ... ) { static char buffer[MAX_PRINT_MSG]; va_list args; if( host_developer.value < DEV_NORMAL ) return; va_start( args, szFmt ); Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); va_end( args ); if( buffer[0] == '0' && buffer[1] == '\n' && buffer[2] == '\0' ) return; // hlrally spam Sys_Print( buffer ); } /* ============= Con_Reportf ============= */ void Con_Reportf( const char *szFmt, ... ) { static char buffer[MAX_PRINT_MSG]; va_list args; if( host_developer.value < DEV_EXTENDED ) return; va_start( args, szFmt ); Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); va_end( args ); Sys_Print( buffer ); } #if XASH_MESSAGEBOX == MSGBOX_STDERR void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) { fprintf( stderr, "======================================\n" "%s: %s\n" "======================================\n", title, message ); } #endif