//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // ----------------------- // cmdlib.c // ----------------------- #include "tier0/platform.h" #ifdef IS_WINDOWS_PC #include #endif #include "cmdlib.h" #include #include #include "tier1/strtools.h" #ifdef _WIN32 #include #endif #include "utlvector.h" #include "filesystem_helpers.h" #include "utllinkedlist.h" #include "tier0/icommandline.h" #include "keyvalues.h" #include "filesystem_tools.h" #if defined( MPI ) #include "vmpi.h" #include "vmpi_tools_shared.h" #endif #if defined( _WIN32 ) || defined( WIN32 ) #include #endif #if defined( _X360 ) #include "xbox/xbox_win32stubs.h" #endif // set these before calling CheckParm int myargc; char **myargv; int newdirs = 0; char com_token[ 1024 ]; qboolean archive; char archivedir[ 1024 ]; CUtlLinkedList g_CleanupFunctions; bool g_bStopOnExit = false; #if defined( _WIN32 ) || defined( WIN32 ) void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) { static CUtlVector buf; if ( buf.Count() == 0 ) { buf.SetCount( 1024 ); } va_list marker; va_start( marker, pFormat ); while ( 1 ) { int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); if ( ret >= 0 ) { // Write the string. g_pFileSystem->Write( buf.Base(), ret, hFile ); break; } else { // Make the buffer larger. int newSize = buf.Count() * 2; buf.SetCount( newSize ); if ( buf.Count() != newSize ) { Error( "CmdLib_FPrintf: can't allocate space for text." ); } } } va_end( marker ); } char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) { int iCur=0; for ( ; iCur < (outSize-1); iCur++ ) { char c; if ( !g_pFileSystem->Read( &c, 1, hFile ) ) { if ( iCur == 0 ) return NULL; else break; } pOut[iCur] = c; if ( c == '\n' ) break; if ( c == EOF ) { if ( iCur == 0 ) return NULL; else break; } } pOut[iCur] = 0; return pOut; } #if !defined( _X360 ) #include #endif // This pauses before exiting if they use -StopOnExit. Useful for debugging. class CExitStopper { public: ~CExitStopper() { if ( g_bStopOnExit ) { Warning( "\nPress any key to quit.\n" ); getch(); } } } g_ExitStopper; static unsigned short g_InitialColor = 0xFFFF; static unsigned short g_LastColor = 0xFFFF; static unsigned short g_BadColor = 0xFFFF; static WORD g_BackgroundFlags = 0xFFFF; static void GetInitialColors( ) { #if !defined( _X360 ) // Get the old background attributes. CONSOLE_SCREEN_BUFFER_INFO oldInfo; GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); g_InitialColor = g_LastColor = oldInfo.wAttributes & ( FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY ); g_BackgroundFlags = oldInfo.wAttributes & ( BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY ); g_BadColor = 0; if (g_BackgroundFlags & BACKGROUND_RED) { g_BadColor |= FOREGROUND_RED; } if (g_BackgroundFlags & BACKGROUND_GREEN) { g_BadColor |= FOREGROUND_GREEN; } if (g_BackgroundFlags & BACKGROUND_BLUE) { g_BadColor |= FOREGROUND_BLUE; } if (g_BackgroundFlags & BACKGROUND_INTENSITY) { g_BadColor |= FOREGROUND_INTENSITY; } #endif } WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) { WORD ret = g_LastColor; #if !defined( _X360 ) g_LastColor = 0; if( red ) { g_LastColor |= FOREGROUND_RED; } if( green ) { g_LastColor |= FOREGROUND_GREEN; } if( blue ) { g_LastColor |= FOREGROUND_BLUE; } if( intensity ) { g_LastColor |= FOREGROUND_INTENSITY; } // Just use the initial color if there's a match... if (g_LastColor == g_BadColor) { g_LastColor = g_InitialColor; } SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); #endif return ret; } void RestoreConsoleTextColor( WORD color ) { #if !defined( _X360 ) SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); g_LastColor = color; #endif } #if defined( CMDLIB_NODBGLIB ) // This can go away when everything is in bin. void Error( char const *pMsg, ... ) { va_list marker; va_start( marker, pMsg ); vprintf( pMsg, marker ); va_end( marker ); exit( -1 ); } #else bool g_bSuppressPrintfOutput = false; void CCmdLibStandardLoggingListener::Log( const LoggingContext_t *pContext, const tchar *pMessage ) { if ( ( pContext->m_Flags & LCF_DO_NOT_ECHO ) != 0 ) { return; } WORD oldColor; Color spewColor = pContext->m_Color; if ( spewColor == UNSPECIFIED_LOGGING_COLOR ) { switch ( pContext->m_Severity ) { case LS_MESSAGE: spewColor = Color( 255, 255, 255, 0 ); break; case LS_WARNING: spewColor = Color( 255, 255, 0, 255 ); break; case LS_ERROR: case LS_ASSERT: spewColor = Color( 255, 0, 0, 255 ); break; } } oldColor = SetConsoleTextColor( spewColor.r(), spewColor.g(), spewColor.b(), spewColor.a() ); #ifdef MPI if ( pContext->m_Severity == LS_ASSERT ) { // VMPI workers don't want to bring up dialogs and suchlike. // They need to have a special function installed to handle // the exceptions and write the minidumps. // Install the function after VMPI_Init with a call: // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) { // Generating an exception and letting the // installed handler handle it ::RaiseException ( 0, // dwExceptionCode EXCEPTION_NONCONTINUABLE, // dwExceptionFlags 0, // nNumberOfArguments, NULL // const ULONG_PTR* lpArguments ); // Never get here (non-continuable exception) VMPI_HandleCrash( pMessage, 0, NULL, true ); exit( 0 ); } } #endif if ( !g_bSuppressPrintfOutput || pContext->m_Severity == LS_ERROR ) { printf( "%s", pMessage ); } OutputDebugString( pMessage ); if ( pContext->m_Severity == LS_ERROR ) { if ( !g_bSuppressPrintfOutput ) { printf( "\n" ); } OutputDebugString( "\n" ); } RestoreConsoleTextColor( oldColor ); } CCmdLibFileLoggingListener::CCmdLibFileLoggingListener() : m_pLogFile( FILESYSTEM_INVALID_HANDLE ) { } void CCmdLibFileLoggingListener::Log( const LoggingContext_t *pContext, const tchar *pMessage ) { if( m_pLogFile != FILESYSTEM_INVALID_HANDLE && ( pContext->m_Flags & LCF_CONSOLE_ONLY ) == 0 ) { CmdLib_FPrintf( m_pLogFile, "%s", pMessage ); g_pFileSystem->Flush( m_pLogFile ); } } void CCmdLibFileLoggingListener::Open( char const *pFilename ) { Assert( m_pLogFile == FILESYSTEM_INVALID_HANDLE ); m_pLogFile = g_pFileSystem->Open( pFilename, "a" ); Assert( m_pLogFile != FILESYSTEM_INVALID_HANDLE ); if ( !m_pLogFile ) { Error( "Can't create LogFile:\"%s\"\n", pFilename ); } CmdLib_FPrintf( m_pLogFile, "\n\n\n" ); } void CCmdLibFileLoggingListener::Close() { if ( g_pFileSystem && m_pLogFile != FILESYSTEM_INVALID_HANDLE ) { g_pFileSystem->Close( m_pLogFile ); m_pLogFile = FILESYSTEM_INVALID_HANDLE; } } CCmdLibStandardLoggingListener g_CmdLibOutputLoggingListener; CCmdLibFileLoggingListener g_CmdLibFileLoggingListener; bool g_bInstalledSpewFunction = false; void InstallSpewFunction() { Assert( !g_bInstalledSpewFunction ); if ( !g_bInstalledSpewFunction ) { g_bInstalledSpewFunction = true; setvbuf( stdout, NULL, _IONBF, 0 ); setvbuf( stderr, NULL, _IONBF, 0 ); LoggingSystem_PushLoggingState(); LoggingSystem_RegisterLoggingListener( &g_CmdLibOutputLoggingListener ); LoggingSystem_RegisterLoggingListener( &g_CmdLibFileLoggingListener ); GetInitialColors(); } } void CmdLib_AllocError( unsigned long size ) { Error( "Error trying to allocate %d bytes.\n", size ); } int CmdLib_NewHandler( size_t size ) { CmdLib_AllocError( size ); return 0; } void InstallAllocationFunctions() { _set_new_mode( 1 ); // so if malloc() fails, we exit. _set_new_handler( CmdLib_NewHandler ); } #endif void CmdLib_AtCleanup( CleanupFn pFn ) { g_CleanupFunctions.AddToTail( pFn ); } void CmdLib_Cleanup() { if ( g_bInstalledSpewFunction ) { LoggingSystem_PopLoggingState(); } g_CmdLibFileLoggingListener.Close(); CmdLib_TermFileSystem(); FOR_EACH_LL( g_CleanupFunctions, i ) { g_CleanupFunctions[ i ](); } #if defined( MPI ) // Unfortunately, when you call exit(), even if you have things registered with atexit(), // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup // everything that uses threads before exiting. VMPI_Finalize(); #endif } #endif /* =================== ExpandWildcards Mimic unix command line expansion =================== */ #define MAX_EX_ARGC 1024 int ex_argc; char *ex_argv[ MAX_EX_ARGC ]; #if defined( _WIN32 ) && !defined( _X360 ) #include "io.h" void ExpandWildcards( int *argc, char ***argv ) { struct _finddata_t fileinfo; int handle; int i; char filename[ 1024 ]; char filebase[ 1024 ]; char *path; ex_argc = 0; for ( i = 0; i < *argc; i++ ) { path = (*argv)[i]; if ( path[0] == '-' || ( !strstr( path, "*" ) && !strstr( path, "?" ) ) ) { ex_argv[ ex_argc++ ] = path; continue; } handle = _findfirst( path, &fileinfo ); if ( handle == -1 ) return; Q_ExtractFilePath( path, filebase, sizeof( filebase ) ); do { V_sprintf_safe( filename, "%s%s", filebase, fileinfo.name ); ex_argv[ ex_argc++ ] = copystring( filename ); } while ( _findnext( handle, &fileinfo ) != -1 ); _findclose (handle); } *argc = ex_argc; *argv = ex_argv; } #else void ExpandWildcards( int *argc, char ***argv ) { } #endif // only printf if in verbose mode qboolean verbose = false; void qprintf( char *format, ... ) { if ( !verbose ) return; va_list argptr; va_start( argptr, format ); char str[ 2048 ]; V_vsprintf_safe( str, format, argptr ); #if defined( CMDLIB_NODBGLIB ) printf( "%s", str ); #else Msg( "%s", str ); #endif va_end( argptr ); } // ---------------------------------------------------------------------------------------------------- // // Helpers. // ---------------------------------------------------------------------------------------------------- // static void CmdLib_getwd( char *out, int outSize ) { #if defined( _WIN32 ) || defined( WIN32 ) _getcwd( out, outSize ); Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); #else getwd( out ); strcat( out, "/" ); #endif Q_FixSlashes( out ); } char *ExpandArg( char *path ) { static char full[ 1024 ]; if ( path[ 0 ] != '/' && path[ 0 ] != '\\' && path[ 1 ] != ':' ) { CmdLib_getwd ( full, sizeof( full ) ); V_strcat_safe( full, path, COPY_ALL_CHARACTERS ); } else { V_strcpy_safe( full, path ); } return full; } char *ExpandPath (char *path) { static char full[ 1024 ]; if ( path[ 0 ] == '/' || path[ 0 ] == '\\' || path[ 1 ] == ':') return path; V_sprintf_safe( full, "%s%s", qdir, path ); return full; } char *copystring(const char *s) { char *b; b = ( char * )malloc( strlen( s ) + 1 ); V_strcpy( b, s ); return b; } void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) { } void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) { int nMinutes = nInputSeconds / 60; int nSeconds = nInputSeconds - nMinutes * 60; int nHours = nMinutes / 60; nMinutes -= nHours * 60; char *extra[ 2 ] = { "", "s" }; if ( nHours > 0 ) { Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[ nHours != 1 ], nMinutes, extra[ nMinutes != 1 ], nSeconds, extra[ nSeconds != 1 ] ); } else if ( nMinutes > 0 ) { Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[ nMinutes != 1 ], nSeconds, extra[ nSeconds != 1 ] ); } else { Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[ nSeconds != 1 ] ); } } void Q_mkdir( char *path ) { #if defined( _WIN32 ) || defined( WIN32 ) if ( _mkdir( path ) != -1) return; #else if ( mkdir( path, 0777 ) != -1) return; #endif // if (errno != EEXIST) Error( "mkdir failed %s\n", path ); } void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) { FileSystem_Init( pFilename, maxMemoryUsage ); if ( !g_pFileSystem ) { Error( "CmdLib_InitFileSystem failed." ); } } void CmdLib_TermFileSystem() { FileSystem_Term(); } CreateInterfaceFn CmdLib_GetFileSystemFactory() { return FileSystem_GetFactory(); } /* ============ FileTime returns -1 if not present ============ */ int FileTime( char *path ) { struct stat buf; if ( stat( path, &buf ) == -1 ) return -1; return buf.st_mtime; } /* ============== COM_Parse Parse a token out of a string ============== */ char *COM_Parse( char *data ) { return ( char* )ParseFile( data, com_token, NULL ); } /* ============================================================================= MISC FUNCTIONS ============================================================================= */ /* ================= CheckParm Checks for the given parameter in the program's command line arguments Returns the argument number (1 to argc-1) or 0 if not present ================= */ int CheckParm( char *check ) { int i; for ( i = 1; i < myargc; i++ ) { if ( !Q_strcasecmp( check, myargv[ i ] ) ) return i; } return 0; } /* ================ Q_filelength ================ */ int Q_filelength( FileHandle_t f ) { return g_pFileSystem->Size( f ); } FileHandle_t SafeOpenWrite( const char *filename ) { FileHandle_t f = g_pFileSystem->Open( filename, "wb" ); if ( !f ) { //Error( "Error opening %s: %s", filename, strerror( errno ) ); // BUGBUG: No way to get equivalent of errno from IFileSystem! Error( "Error opening %s! (Check for write enable)\n", filename ); } return f; } #define MAX_CMDLIB_BASE_PATHS 20 static char g_pBasePaths[ MAX_CMDLIB_BASE_PATHS ][ MAX_PATH ]; static int g_NumBasePaths = 0; void CmdLib_AddBasePath( const char *pPath ) { //printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) { V_strcpy_safe( g_pBasePaths[ g_NumBasePaths ], pPath ); Q_FixSlashes( g_pBasePaths[ g_NumBasePaths ] ); g_NumBasePaths++; } else { Assert( 0 ); } } void CmdLib_AddNewSearchPath( const char *pPath ) { static int g_nAdditionalDirectoryCount = 0; static char s_addedRelativeDirs[ MAX_CMDLIB_BASE_PATHS ][ MAX_PATH ]; static int s_originalNumBasePaths = 0; static char s_originalBasePaths[ MAX_CMDLIB_BASE_PATHS ][ MAX_PATH ]; if ( g_nAdditionalDirectoryCount == 0 ) // first call: { // remember all original paths s_originalNumBasePaths = g_NumBasePaths; for ( int nOriginalBasePath = 0; nOriginalBasePath < s_originalNumBasePaths; nOriginalBasePath++ ) { V_strcpy_safe( s_originalBasePaths[nOriginalBasePath], g_pBasePaths[nOriginalBasePath] ); } } if( g_nAdditionalDirectoryCount < MAX_CMDLIB_BASE_PATHS ) { V_strcpy_safe( s_addedRelativeDirs[g_nAdditionalDirectoryCount], pPath ); Q_FixSlashes( s_addedRelativeDirs[g_nAdditionalDirectoryCount] ); g_nAdditionalDirectoryCount++; } else { Assert( 0 ); } //update the original base paths with the new number // we'll produce a cross-product of the set of original paths and the new relative paths g_NumBasePaths = s_originalNumBasePaths + ( s_originalNumBasePaths * g_nAdditionalDirectoryCount ); if ( g_NumBasePaths > MAX_CMDLIB_BASE_PATHS ) { Error( "You have too many search paths, let SteveK know about this\n"); } //make an array of all the new search directories and copy it back into g_pBasePaths int nGeneratedPaths = 0; for ( int nOriginalBasePath = 0; nOriginalBasePath < s_originalNumBasePaths; nOriginalBasePath++ ) { for ( int nAdditionalDir = 0; nAdditionalDir < g_nAdditionalDirectoryCount; nAdditionalDir++ ) { //add in the new search dirs here V_strcpy_safe( g_pBasePaths[ nGeneratedPaths ], s_originalBasePaths[ nOriginalBasePath ] ); V_strcat_safe( g_pBasePaths[ nGeneratedPaths ], s_addedRelativeDirs[ nAdditionalDir ], sizeof( s_addedRelativeDirs[ nAdditionalDir ] ) ); nGeneratedPaths++; } //we still need the original base path, but insert it after the newly added ones V_strcpy_safe( g_pBasePaths[ nGeneratedPaths ], s_originalBasePaths[ nOriginalBasePath ] ); nGeneratedPaths++; } } bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) { char *pFileName = ( char * )stackalloc( strlen( pFileName_ ) + 1 ); V_strcpy( pFileName, pFileName_ ); Q_FixSlashes( pFileName ); pathLength = 0; for( int i = 0; i < g_NumBasePaths; i++ ) { // see if we can rip the base off of the filename. if( Q_strncasecmp( g_pBasePaths[ i ], pFileName, Q_strlen( g_pBasePaths[ i ] ) ) == 0 ) { pathLength = strlen( g_pBasePaths[ i ] ); return true; } } return false; } int CmdLib_GetNumBasePaths( void ) { return g_NumBasePaths; } const char *CmdLib_GetBasePath( int i ) { Assert( i >= 0 && i < g_NumBasePaths ); return g_pBasePaths[ i ]; } FileHandle_t SafeOpenRead( const char *filename ) { int pathLength; FileHandle_t f = 0; if( CmdLib_HasBasePath( filename, pathLength ) ) { filename = filename + pathLength; char tmp[ MAX_PATH ]; for( int i = 0; i < g_NumBasePaths; i++ ) { V_strcpy_safe( tmp, g_pBasePaths[ i ] ); V_strcat_safe( tmp, filename ); f = g_pFileSystem->Open( tmp, "rb" ); if( f ) { return f; } } Error( "Error opening %s\n",filename ); return f; } else { f = g_pFileSystem->Open( filename, "rb" ); if ( !f ) { Error( "Error opening %s",filename ); } return f; } } void SafeRead( FileHandle_t f, void *buffer, int count) { if ( g_pFileSystem->Read( buffer, count, f ) != ( size_t )count ) { Error( "File read failure" ); } } void SafeWrite ( FileHandle_t f, void *buffer, int count) { if ( g_pFileSystem->Write( buffer, count, f ) != ( size_t )count ) { Error( "File write failure" ); } } /* ============== FileExists ============== */ qboolean FileExists( const char *filename ) { FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); if ( hFile == FILESYSTEM_INVALID_HANDLE ) { return false; } else { g_pFileSystem->Close( hFile ); return true; } } /* ============== LoadFile ============== */ int LoadFile( const char *filename, void **bufferptr ) { int length = 0; void *buffer; FileHandle_t f = SafeOpenRead( filename ); if ( FILESYSTEM_INVALID_HANDLE != f ) { length = Q_filelength( f ); buffer = malloc( length + 1 ); ( ( char * )buffer )[ length ] = 0; SafeRead( f, buffer, length ); g_pFileSystem->Close( f ); *bufferptr = buffer; } else { *bufferptr = NULL; } return length; } /* ============== SaveFile ============== */ void SaveFile( const char *filename, void *buffer, int count ) { FileHandle_t f = SafeOpenWrite( filename ); SafeWrite( f, buffer, count ); g_pFileSystem->Close( f ); } /* ==================== Extract file parts ==================== */ // FIXME: should include the slash, otherwise // backing to an empty path will be wrong when appending a slash /* ============== ParseNum / ParseHex ============== */ int ParseHex( char *hex ) { char *str; int num; num = 0; str = hex; while ( *str ) { num <<= 4; if ( *str >= '0' && *str <= '9' ) { num += *str - '0'; } else if ( *str >= 'a' && *str <= 'f' ) { num += 10 + *str - 'a'; } else if ( *str >= 'A' && *str <= 'F' ) { num += 10 + *str - 'A'; } else { Error( "Bad hex number: %s", hex ); } str++; } return num; } int ParseNum( char *str ) { if ( str[ 0 ] == '$' ) return ParseHex( str + 1 ); if ( str[ 0 ] == '0' && str[ 1 ] == 'x' ) return ParseHex( str + 2 ); return atol( str ); } /* ============ CreatePath ============ */ void CreatePath( char *path ) { char *ofs, c; // strip the drive if ( path[ 1 ] == ':' ) path += 2; for ( ofs = path + 1; *ofs; ofs++) { c = *ofs; if ( c == '/' || c == '\\' ) { // create the directory *ofs = 0; Q_mkdir( path ); *ofs = c; } } } //----------------------------------------------------------------------------- // Creates a path, path may already exist //----------------------------------------------------------------------------- #if defined( _WIN32 ) || defined( WIN32 ) void SafeCreatePath( char *path ) { char *ptr; // skip past the drive path, but don't strip if ( path[ 1 ] == ':' ) { ptr = strchr( path, '\\' ); } else { ptr = path; } while ( ptr ) { ptr = strchr( ptr + 1, '\\' ); if ( ptr ) { *ptr = '\0'; _mkdir( path ); *ptr = '\\'; } } } #endif /* ============ QCopyFile Used to archive source files ============ */ void QCopyFile( char *from, char *to ) { void *buffer; int length; length = LoadFile( from, &buffer ); CreatePath( to ); SaveFile( to, buffer, length ); free( buffer ); }