mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-22 01:45:19 +01:00
425 lines
9.6 KiB
C
425 lines
9.6 KiB
C
/*
|
|
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_IRIX
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
// do not waste precious CPU cycles on mobiles or low memory devices
|
|
#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY
|
|
#define XASH_COLORIZE_CONSOLE true
|
|
// use with caution, running engine in Qt Creator may cause a freeze in read() call
|
|
// I have never encountered this bug anywhere else, so still enable by default
|
|
#define XASH_USE_SELECT 1
|
|
#else
|
|
#define XASH_COLORIZE_CONSOLE false
|
|
#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
|
|
if( Host_IsDedicated( ))
|
|
{
|
|
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( "%s: Exiting!\n", __func__ );
|
|
#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";
|
|
|
|
if( Host_IsDedicated( ))
|
|
Q_strncpy( s_ld.title, XASH_DEDICATED_SERVER_NAME " " XASH_VERSION, sizeof( s_ld.title ));
|
|
else Q_strncpy( s_ld.title, XASH_ENGINE_NAME " " XASH_VERSION, sizeof( s_ld.title ));
|
|
|
|
// 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 );
|
|
|
|
// fit to 80 columns for easier read on standard terminal
|
|
fputs( "================================================================================\n", s_ld.logfile );
|
|
fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
|
|
fprintf( s_ld.logfile, "Game started at %s\n", Q_timestamp( TIME_FULL ));
|
|
fputs( "================================================================================\n", s_ld.logfile );
|
|
fflush( s_ld.logfile );
|
|
}
|
|
}
|
|
|
|
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 )
|
|
{
|
|
fputc( '\n', s_ld.logfile );
|
|
fputs( "================================================================================\n", s_ld.logfile );
|
|
fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
|
|
fprintf( s_ld.logfile, "Stopped with reason \"%s\" at %s\n", event_name, Q_timestamp( TIME_FULL ));
|
|
fputs( "================================================================================\n", s_ld.logfile );
|
|
fclose( s_ld.logfile );
|
|
s_ld.logfile = NULL;
|
|
}
|
|
}
|
|
|
|
#if XASH_COLORIZE_CONSOLE == true
|
|
static void Sys_WriteEscapeSequenceForColorcode( int fd, int c )
|
|
{
|
|
static const char *q3ToAnsi[ 8 ] =
|
|
{
|
|
"\033[1;30m", // COLOR_BLACK
|
|
"\033[1;31m", // COLOR_RED
|
|
"\033[1;32m", // COLOR_GREEN
|
|
"\033[1;33m", // COLOR_YELLOW
|
|
"\033[1;34m", // COLOR_BLUE
|
|
"\033[1;36m", // COLOR_CYAN
|
|
"\033[1;35m", // COLOR_MAGENTA
|
|
"\033[0m", // COLOR_WHITE
|
|
};
|
|
const char *esc = q3ToAnsi[c];
|
|
|
|
if( c == 7 )
|
|
write( fd, esc, 4 );
|
|
else write( fd, esc, 7 );
|
|
}
|
|
#else
|
|
static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) {}
|
|
#endif
|
|
|
|
static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg, const qboolean colorize )
|
|
{
|
|
const char *p = msg;
|
|
|
|
write( fd, logtime, Q_strlen( logtime ) );
|
|
|
|
while( p && *p )
|
|
{
|
|
p = Q_strchr( msg, '^' );
|
|
|
|
if( p == NULL )
|
|
{
|
|
if( write( fd, msg, Q_strlen( msg )) < 0 )
|
|
{
|
|
// don't call engine Msg, might cause recursion
|
|
fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno ));
|
|
}
|
|
break;
|
|
}
|
|
else if( IsColorString( p ))
|
|
{
|
|
if( p != msg )
|
|
{
|
|
if( write( fd, msg, p - msg ) < 0 )
|
|
fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno ));
|
|
}
|
|
msg = p + 2;
|
|
|
|
if( colorize )
|
|
Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] ));
|
|
}
|
|
else
|
|
{
|
|
if( write( fd, msg, p - msg + 1 ) < 0 )
|
|
fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno ));
|
|
msg = p + 1;
|
|
}
|
|
}
|
|
|
|
// flush the color
|
|
if( colorize )
|
|
Sys_WriteEscapeSequenceForColorcode( fd, 7 );
|
|
}
|
|
|
|
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_write( ANDROID_LOG_DEBUG, "Xash", buf );
|
|
#endif // XASH_ANDROID && !XASH_DEDICATED
|
|
|
|
#if TARGET_OS_IOS
|
|
void IOS_Log( const char * );
|
|
IOS_Log( buf );
|
|
#endif // TARGET_OS_IOS
|
|
|
|
#if XASH_NSWITCH && NSWITCH_DEBUG
|
|
// just spew it to stderr normally in debug mode
|
|
fprintf( stderr, "%s %s", logtime, buf );
|
|
#endif // XASH_NSWITCH && NSWITCH_DEBUG
|
|
|
|
#if XASH_PSVITA
|
|
// spew to stderr only in developer mode
|
|
if( host_developer.value )
|
|
{
|
|
fprintf( stderr, "%s %s", logtime, buf );
|
|
}
|
|
#endif
|
|
|
|
#elif !XASH_WIN32 // Wcon does the job
|
|
Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE );
|
|
Sys_FlushStdout();
|
|
#endif
|
|
}
|
|
|
|
void Sys_PrintLog( const char *pMsg )
|
|
{
|
|
time_t crt_time;
|
|
const struct tm *crt_tm;
|
|
char logtime[32] = "";
|
|
static char lastchar;
|
|
size_t len;
|
|
|
|
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
|
|
Sys_PrintStdout( logtime, pMsg );
|
|
|
|
len = Q_strlen( pMsg );
|
|
|
|
if( !s_ld.logfile )
|
|
{
|
|
// save last char to detect when line was not ended
|
|
lastchar = len > 0 ? pMsg[len - 1] : 0;
|
|
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 = len > 0 ? pMsg[len - 1] : 0;
|
|
|
|
Sys_PrintLogfile( s_ld.logfileno, logtime, pMsg, false );
|
|
Sys_FlushLogfile();
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
CONSOLE PRINT
|
|
|
|
=============================================================================
|
|
*/
|
|
static void Con_Printfv( qboolean debug, const char *szFmt, va_list args )
|
|
{
|
|
static char buffer[MAX_PRINT_MSG];
|
|
qboolean add_newline;
|
|
|
|
add_newline = Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ) < 0;
|
|
|
|
if( debug && !Q_strcmp( buffer, "0\n" ))
|
|
return; // hlrally spam
|
|
|
|
Sys_Print( buffer );
|
|
if( add_newline )
|
|
Sys_Print( "\n" );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Con_Printf
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT Con_Printf( const char *szFmt, ... )
|
|
{
|
|
va_list args;
|
|
|
|
if( !host.allow_console )
|
|
return;
|
|
|
|
va_start( args, szFmt );
|
|
Con_Printfv( false, szFmt, args );
|
|
va_end( args );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Con_DPrintf
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT Con_DPrintf( const char *szFmt, ... )
|
|
{
|
|
va_list args;
|
|
|
|
if( host_developer.value < DEV_NORMAL )
|
|
return;
|
|
|
|
va_start( args, szFmt );
|
|
Con_Printfv( true, szFmt, args );
|
|
va_end( args );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Con_Reportf
|
|
|
|
=============
|
|
*/
|
|
void Con_Reportf( const char *szFmt, ... )
|
|
{
|
|
va_list args;
|
|
|
|
if( host_developer.value < DEV_EXTENDED )
|
|
return;
|
|
|
|
va_start( args, szFmt );
|
|
Con_Printfv( false, szFmt, args );
|
|
va_end( args );
|
|
}
|
|
|
|
|
|
#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
|
|
|