|
|
|
/*
|
|
|
|
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
|
|
|
|
|