/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code 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. Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "Common_local.h" idCVar com_logFile( "logFile", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "1 = buffer log, 2 = flush after each print", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); idCVar com_logFileName( "logFileName", "qconsole.log", CVAR_SYSTEM | CVAR_NOCHEAT, "name of log file, if empty, qconsole.log will be used" ); idCVar com_timestampPrints( "com_timestampPrints", "0", CVAR_SYSTEM, "print time with each console print, 1 = msec, 2 = sec", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); #ifndef ID_RETAIL idCVar com_printFilter( "com_printFilter", "", CVAR_SYSTEM, "only print lines that contain this, add multiple filters with a ; delimeter"); #endif /* ================== idCommonLocal::BeginRedirect ================== */ void idCommonLocal::BeginRedirect( char *buffer, int buffersize, void (*flush)( const char *) ) { if ( !buffer || !buffersize || !flush ) { return; } rd_buffer = buffer; rd_buffersize = buffersize; rd_flush = flush; *rd_buffer = 0; } /* ================== idCommonLocal::EndRedirect ================== */ void idCommonLocal::EndRedirect() { if ( rd_flush && rd_buffer[ 0 ] ) { rd_flush( rd_buffer ); } rd_buffer = NULL; rd_buffersize = 0; rd_flush = NULL; } /* ================== idCommonLocal::CloseLogFile ================== */ void idCommonLocal::CloseLogFile() { if ( logFile ) { com_logFile.SetBool( false ); // make sure no further VPrintf attempts to open the log file again fileSystem->CloseFile( logFile ); logFile = NULL; } } /* ================== idCommonLocal::SetRefreshOnPrint ================== */ void idCommonLocal::SetRefreshOnPrint( bool set ) { com_refreshOnPrint = set; } /* ================== idCommonLocal::VPrintf A raw string should NEVER be passed as fmt, because of "%f" type crashes. ================== */ void idCommonLocal::VPrintf( const char *fmt, va_list args ) { static bool logFileFailed = false; // if the cvar system is not initialized if ( !cvarSystem->IsInitialized() ) { return; } // optionally put a timestamp at the beginning of each print, // so we can see how long different init sections are taking int timeLength = 0; char msg[MAX_PRINT_MSG_SIZE]; msg[ 0 ] = '\0'; if ( com_timestampPrints.GetInteger() ) { int t = Sys_Milliseconds(); if ( com_timestampPrints.GetInteger() == 1 ) { sprintf( msg, "[%5.2f]", t * 0.001f ); } else { sprintf( msg, "[%i]", t ); } } timeLength = strlen( msg ); // don't overflow if ( idStr::vsnPrintf( msg+timeLength, MAX_PRINT_MSG_SIZE-timeLength-1, fmt, args ) < 0 ) { msg[sizeof(msg)-2] = '\n'; msg[sizeof(msg)-1] = '\0'; // avoid output garbling Sys_Printf( "idCommon::VPrintf: truncated to %d characters\n", strlen(msg)-1 ); } if ( rd_buffer ) { if ( (int)( strlen( msg ) + strlen( rd_buffer ) ) > ( rd_buffersize - 1 ) ) { rd_flush( rd_buffer ); *rd_buffer = 0; } strcat( rd_buffer, msg ); return; } #ifndef ID_RETAIL if ( com_printFilter.GetString() != NULL && com_printFilter.GetString()[ 0 ] != '\0' ) { idStrStatic< 4096 > filterBuf = com_printFilter.GetString(); idStrStatic< 4096 > msgBuf = msg; filterBuf.ToLower(); msgBuf.ToLower(); char *sp = strtok( &filterBuf[ 0 ], ";" ); bool p = false; for( ; sp != NULL ; ) { if ( strstr( msgBuf, sp ) != NULL ) { p = true; break; } sp = strtok( NULL, ";" ); } if ( !p ) { return; } } #endif if ( !idLib::IsMainThread() ) { OutputDebugString( msg ); return; } // echo to console buffer console->Print( msg ); // remove any color codes idStr::RemoveColors( msg ); // echo to dedicated console and early console Sys_Printf( "%s", msg ); // print to script debugger server // DebuggerServerPrint( msg ); #if 0 // !@# #if defined(_DEBUG) && defined(WIN32) if ( strlen( msg ) < 512 ) { TRACE( msg ); } #endif #endif // logFile if ( com_logFile.GetInteger() && !logFileFailed && fileSystem->IsInitialized() ) { static bool recursing; if ( !logFile && !recursing ) { const char *fileName = com_logFileName.GetString()[0] ? com_logFileName.GetString() : "qconsole.log"; // fileSystem->OpenFileWrite can cause recursive prints into here recursing = true; logFile = fileSystem->OpenFileWrite( fileName ); if ( !logFile ) { logFileFailed = true; FatalError( "failed to open log file '%s'\n", fileName ); } recursing = false; if ( com_logFile.GetInteger() > 1 ) { // force it to not buffer so we get valid // data even if we are crashing logFile->ForceFlush(); } time_t aclock; time( &aclock ); struct tm * newtime = localtime( &aclock ); Printf( "log file '%s' opened on %s\n", fileName, asctime( newtime ) ); } if ( logFile ) { logFile->Write( msg, strlen( msg ) ); logFile->Flush(); // ForceFlush doesn't help a whole lot } } // don't trigger any updates if we are in the process of doing a fatal error if ( com_errorEntered != ERP_FATAL ) { // update the console if we are in a long-running command, like dmap if ( com_refreshOnPrint ) { const bool captureToImage = false; UpdateScreen( captureToImage ); } } } /* ================== idCommonLocal::Printf Both client and server can use this, and it will output to the appropriate place. A raw string should NEVER be passed as fmt, because of "%f" type crashers. ================== */ void idCommonLocal::Printf( const char *fmt, ... ) { va_list argptr; va_start( argptr, fmt ); VPrintf( fmt, argptr ); va_end( argptr ); } /* ================== idCommonLocal::DPrintf prints message that only shows up if the "developer" cvar is set ================== */ void idCommonLocal::DPrintf( const char *fmt, ... ) { va_list argptr; char msg[MAX_PRINT_MSG_SIZE]; if ( !cvarSystem->IsInitialized() || !com_developer.GetBool() ) { return; // don't confuse non-developers with techie stuff... } va_start( argptr, fmt ); idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); va_end( argptr ); msg[sizeof(msg)-1] = '\0'; // never refresh the screen, which could cause reentrency problems bool temp = com_refreshOnPrint; com_refreshOnPrint = false; Printf( S_COLOR_RED"%s", msg ); com_refreshOnPrint = temp; } /* ================== idCommonLocal::DWarning prints warning message in yellow that only shows up if the "developer" cvar is set ================== */ void idCommonLocal::DWarning( const char *fmt, ... ) { va_list argptr; char msg[MAX_PRINT_MSG_SIZE]; if ( !com_developer.GetBool() ) { return; // don't confuse non-developers with techie stuff... } va_start( argptr, fmt ); idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); va_end( argptr ); msg[sizeof(msg)-1] = '\0'; Printf( S_COLOR_YELLOW"WARNING: %s\n", msg ); } /* ================== idCommonLocal::Warning prints WARNING %s and adds the warning message to a queue to be printed later on ================== */ void idCommonLocal::Warning( const char *fmt, ... ) { va_list argptr; char msg[MAX_PRINT_MSG_SIZE]; if ( !idLib::IsMainThread() ) { return; // not thread safe! } va_start( argptr, fmt ); idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); va_end( argptr ); msg[sizeof(msg)-1] = 0; Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", msg ); if ( warningList.Num() < MAX_WARNING_LIST ) { warningList.AddUnique( msg ); } } /* ================== idCommonLocal::PrintWarnings ================== */ void idCommonLocal::PrintWarnings() { int i; if ( !warningList.Num() ) { return; } Printf( "------------- Warnings ---------------\n" ); Printf( "during %s...\n", warningCaption.c_str() ); for ( i = 0; i < warningList.Num(); i++ ) { Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", warningList[i].c_str() ); } if ( warningList.Num() ) { if ( warningList.Num() >= MAX_WARNING_LIST ) { Printf( "more than %d warnings\n", MAX_WARNING_LIST ); } else { Printf( "%d warnings\n", warningList.Num() ); } } } /* ================== idCommonLocal::ClearWarnings ================== */ void idCommonLocal::ClearWarnings( const char *reason ) { warningCaption = reason; warningList.Clear(); } /* ================== idCommonLocal::DumpWarnings ================== */ void idCommonLocal::DumpWarnings() { int i; idFile *warningFile; if ( !warningList.Num() ) { return; } warningFile = fileSystem->OpenFileWrite( "warnings.txt", "fs_savepath" ); if ( warningFile ) { warningFile->Printf( "------------- Warnings ---------------\n\n" ); warningFile->Printf( "during %s...\n", warningCaption.c_str() ); for ( i = 0; i < warningList.Num(); i++ ) { warningList[i].RemoveColors(); warningFile->Printf( "WARNING: %s\n", warningList[i].c_str() ); } if ( warningList.Num() >= MAX_WARNING_LIST ) { warningFile->Printf( "\nmore than %d warnings!\n", MAX_WARNING_LIST ); } else { warningFile->Printf( "\n%d warnings.\n", warningList.Num() ); } warningFile->Printf( "\n\n-------------- Errors ---------------\n\n" ); for ( i = 0; i < errorList.Num(); i++ ) { errorList[i].RemoveColors(); warningFile->Printf( "ERROR: %s", errorList[i].c_str() ); } warningFile->ForceFlush(); fileSystem->CloseFile( warningFile ); #ifndef ID_DEBUG idStr osPath; osPath = fileSystem->RelativePathToOSPath( "warnings.txt", "fs_savepath" ); WinExec( va( "Notepad.exe %s", osPath.c_str() ), SW_SHOW ); #endif } } /* ================== idCommonLocal::Error ================== */ void idCommonLocal::Error( const char *fmt, ... ) { va_list argptr; static int lastErrorTime; static int errorCount; int currentTime; errorParm_t code = ERP_DROP; // always turn this off after an error com_refreshOnPrint = false; if ( com_productionMode.GetInteger() == 3 ) { Sys_Quit(); } // when we are running automated scripts, make sure we // know if anything failed if ( cvarSystem->GetCVarInteger( "fs_copyfiles" ) ) { code = ERP_FATAL; } // if we don't have GL running, make it a fatal error if ( !renderSystem->IsOpenGLRunning() ) { code = ERP_FATAL; } // if we got a recursive error, make it fatal if ( com_errorEntered ) { // if we are recursively erroring while exiting // from a fatal error, just kill the entire // process immediately, which will prevent a // full screen rendering window covering the // error dialog if ( com_errorEntered == ERP_FATAL ) { Sys_Quit(); } code = ERP_FATAL; } // if we are getting a solid stream of ERP_DROP, do an ERP_FATAL currentTime = Sys_Milliseconds(); if ( currentTime - lastErrorTime < 100 ) { if ( ++errorCount > 3 ) { code = ERP_FATAL; } } else { errorCount = 0; } lastErrorTime = currentTime; com_errorEntered = code; va_start (argptr,fmt); idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); va_end (argptr); errorMessage[sizeof(errorMessage)-1] = '\0'; // copy the error message to the clip board Sys_SetClipboardData( errorMessage ); // add the message to the error list errorList.AddUnique( errorMessage ); Stop(); if ( code == ERP_DISCONNECT ) { com_errorEntered = ERP_NONE; throw idException( errorMessage ); } else if ( code == ERP_DROP ) { Printf( "********************\nERROR: %s\n********************\n", errorMessage ); com_errorEntered = ERP_NONE; throw idException( errorMessage ); } else { Printf( "********************\nERROR: %s\n********************\n", errorMessage ); } if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); } Sys_Error( "%s", errorMessage ); } /* ================== idCommonLocal::FatalError Dump out of the game to a system dialog ================== */ void idCommonLocal::FatalError( const char *fmt, ... ) { va_list argptr; if ( com_productionMode.GetInteger() == 3 ) { Sys_Quit(); } // if we got a recursive error, make it fatal if ( com_errorEntered ) { // if we are recursively erroring while exiting // from a fatal error, just kill the entire // process immediately, which will prevent a // full screen rendering window covering the // error dialog Sys_Printf( "FATAL: recursed fatal error:\n%s\n", errorMessage ); va_start( argptr, fmt ); idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); va_end( argptr ); errorMessage[sizeof(errorMessage)-1] = '\0'; Sys_Printf( "%s\n", errorMessage ); // write the console to a log file? Sys_Quit(); } com_errorEntered = ERP_FATAL; va_start( argptr, fmt ); idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); va_end( argptr ); errorMessage[sizeof(errorMessage)-1] = '\0'; if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); } Sys_SetFatalError( errorMessage ); Sys_Error( "%s", errorMessage ); }