//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include #include #include "isys.h" #include "conproc.h" #include "dedicated.h" #include "engine_hlds_api.h" #include "checksum_md5.h" #include "mathlib/mathlib.h" #include "tier0/dbg.h" #include "tier1/strtools.h" #include "tier0/icommandline.h" #include "idedicatedexports.h" #include "vgui/vguihelpers.h" #include "appframework/AppFramework.h" #include "filesystem_init.h" #include "tier2/tier2.h" #include "dedicated.h" #include "vstdlib/cvar.h" #ifdef LINUX #include #endif #ifdef _WIN32 #include #include #include "keyvalues.h" // filesystem_steam.cpp implements this useful function - mount all the caches for a given app ID. extern void MountDependencies( int iAppId, CUtlVector &depList ); #else #define _chdir chdir #include #endif void* FileSystemFactory( const char *pName, int *pReturnCode ); bool InitInstance( ); void ProcessConsoleInput( void ); const char *UTIL_GetExecutableDir( ); bool NET_Init( void ); void NET_Shutdown( void ); const char *UTIL_GetBaseDir( void ); bool g_bVGui = false; #if defined( CSTRIKE15 ) const char *g_gameName = "csgo"; #else const char *g_gameName = "hl2"; #endif #if defined ( _WIN32 ) #include "console/TextConsoleWin32.h" CTextConsoleWin32 console; #else #include "console/TextConsoleUnix.h" CTextConsoleUnix console; #endif extern char *gpszCvars; IDedicatedServerAPI *engine = NULL; int g_nSubProcessId = 0; #ifdef POSIX extern char g_szEXEName[ 256 ]; #endif class CDedicatedServerLoggingListener : public ILoggingListener { public: virtual void Log( const LoggingContext_t *pContext, const tchar *pMessage ) { if ( sys ) { if ( g_nSubProcessId ) { sys->Printf( " #%0x2d:%s", g_nSubProcessId, pMessage ); } else { sys->Printf( "#%s", pMessage ); } } #ifdef _WIN32 Plat_DebugString( pMessage ); #endif if ( pContext->m_Severity == LS_ERROR ) { // In Windows vgui mode, make a message box or they won't ever see the error. #ifdef _WIN32 if ( g_bVGui ) { MessageBox( NULL, pMessage, "Error", MB_OK | MB_TASKMODAL ); } TerminateProcess( GetCurrentProcess(), 1 ); #elif POSIX fflush(stdout); _exit(1); #else #error "Implement me" #endif } } }; #if defined(POSIX) && !defined(_PS3) #define MAX_LINUX_CMDLINE 2048 static char linuxCmdline[ MAX_LINUX_CMDLINE +7 ]; // room for -steam void BuildCmdLine( int argc, char **argv ) { int len; int i; for (len = 0, i = 0; i < argc; i++) { len += strlen(argv[i]); } if ( len > MAX_LINUX_CMDLINE ) { printf( "command line too long, %i max\n", MAX_LINUX_CMDLINE ); exit(-1); return; } linuxCmdline[0] = '\0'; for ( i = 0; i < argc; i++ ) { if ( i > 0 ) { strcat( linuxCmdline, " " ); } strcat( linuxCmdline, argv[ i ] ); } strcat( linuxCmdline, " -steam" ); } char *GetCommandLine() { return linuxCmdline; } #endif static CNonFatalLoggingResponsePolicy s_NonFatalLoggingResponsePolicy; static CDedicatedServerLoggingListener s_DedicatedServerLoggingListener; bool RunServerIteration( bool bSupressStdIOBecauseWeAreAForkedChild ) { bool bDone = false; #if defined ( _WIN32 ) MSG msg; while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { //if (!GetMessage( &msg, NULL, 0, 0)) if ( msg.message == WM_QUIT ) { bDone = true; break; } TranslateMessage( &msg ); DispatchMessage( &msg ); } if ( IsPC() ) { // NOTE: Under some implementations of Win9x, // dispatching messages can cause the FPU control word to change SetupFPUControlWord(); } if ( bDone /*|| gbAppHasBeenTerminated*/ ) return bDone; #endif // _WIN32 if ( g_bVGui ) { #ifdef _WIN32 RunVGUIFrame(); #endif } else { if (! bSupressStdIOBecauseWeAreAForkedChild ) { // Calling ProcessConsoleInput can cost about a tenth of a millisecond. // We used to call it up to 1,000 times a second. Even calling it once // a frame is wasteful since the console hardly needs that level of // responsiveness, and calling it too frequently is a waste of CPU time // and power. static int s_nProcessCount; // Don't set this too high since the users keystrokes are not reflected // until this ProcessConsoleInput is called. const int nConsoleInputFrames = 5; ++s_nProcessCount; if ( s_nProcessCount > nConsoleInputFrames ) { s_nProcessCount = 0; ProcessConsoleInput(); } } } if ( !engine->RunFrame() ) { bDone = true; } sys->UpdateStatus( 0 /* don't force */ ); return bDone; } //----------------------------------------------------------------------------- // // Server loop // //----------------------------------------------------------------------------- void RunServer( bool bSupressStdIOBecauseWeAreAForkedChild ) { #ifdef _WIN32 if(gpszCvars) { engine->AddConsoleText(gpszCvars); } #endif // run 2 engine frames first to get the engine to load its resources if (g_bVGui) { #ifdef _WIN32 RunVGUIFrame(); #endif } if ( !engine->RunFrame() ) { return; } if (g_bVGui) { #ifdef _WIN32 RunVGUIFrame(); #endif } if ( !engine->RunFrame() ) { return; } if (g_bVGui) { #ifdef _WIN32 VGUIFinishedConfig(); RunVGUIFrame(); #endif } bool bDone = false; while ( ! bDone ) { bDone = RunServerIteration( bSupressStdIOBecauseWeAreAForkedChild ); } } //----------------------------------------------------------------------------- // // initialize the console or wait for vgui to start the server // //----------------------------------------------------------------------------- bool ConsoleStartup( CreateInterfaceFn dedicatedFactory ) { #ifdef _WIN32 if ( g_bVGui ) { StartVGUI( dedicatedFactory ); RunVGUIFrame(); // Run the config screen while (VGUIIsInConfig() && VGUIIsRunning()) { RunVGUIFrame(); } if ( VGUIIsStopping() ) { return false; } } else #endif // _WIN32 { if ( !console.Init() ) { return false; } } return true; } //----------------------------------------------------------------------------- // Instantiate all main libraries //----------------------------------------------------------------------------- bool CDedicatedAppSystemGroup::Create( ) { // Hook the debug output stuff (override the spew func in the appframework) LoggingSystem_PushLoggingState(); LoggingSystem_SetLoggingResponsePolicy( &s_NonFatalLoggingResponsePolicy ); LoggingSystem_RegisterLoggingListener( &s_DedicatedServerLoggingListener ); // Added the dedicated exports module for the engine to grab AppModule_t dedicatedModule = LoadModule( Sys_GetFactoryThis() ); IAppSystem *pSystem = AddSystem( dedicatedModule, VENGINE_DEDICATEDEXPORTS_API_VERSION ); if ( !pSystem ) return false; return sys->LoadModules( this ); } bool CDedicatedAppSystemGroup::PreInit( ) { // A little hack needed because dedicated links directly to filesystem .cpp files g_pFullFileSystem = NULL; if ( !BaseClass::PreInit() ) return false; CFSSteamSetupInfo steamInfo; steamInfo.m_pDirectoryName = NULL; steamInfo.m_bOnlyUseDirectoryName = false; steamInfo.m_bToolsMode = false; steamInfo.m_bSetSteamDLLPath = false; steamInfo.m_bSteam = g_pFullFileSystem->IsSteam(); steamInfo.m_bNoGameInfo = steamInfo.m_bSteam; if ( FileSystem_SetupSteamEnvironment( steamInfo ) != FS_OK ) return false; CFSMountContentInfo fsInfo; fsInfo.m_pFileSystem = g_pFullFileSystem; fsInfo.m_bToolsMode = false; fsInfo.m_pDirectoryName = steamInfo.m_GameInfoPath; if ( FileSystem_MountContent( fsInfo ) != FS_OK ) return false; if ( !NET_Init() ) return false; // Needs to be done prior to init material system config CFSSearchPathsInit initInfo; initInfo.m_pFileSystem = g_pFullFileSystem; initInfo.m_pDirectoryName = CommandLine()->ParmValue( "-game" ); // Load gameinfo.txt and setup all the search paths, just like the tools do. FileSystem_LoadSearchPaths( initInfo ); #ifdef _WIN32 if ( CommandLine()->CheckParm( "-console" ) ) { g_bVGui = false; } else { g_bVGui = true; } #else // no VGUI under linux g_bVGui = false; #endif if ( !g_bVGui ) { if ( !sys->CreateConsoleWindow() ) return false; } return true; } int CDedicatedAppSystemGroup::Main( ) { if ( !ConsoleStartup( GetFactory() ) ) return -1; #ifdef _WIN32 if ( g_bVGui ) { RunVGUIFrame(); } else { // mount the caches if (CommandLine()->CheckParm("-steam")) { // Add a search path for the base dir char fullLocationPath[MAX_PATH]; if ( _getcwd( fullLocationPath, MAX_PATH ) ) { g_pFullFileSystem->AddSearchPath( fullLocationPath, "MAIN" ); } // Find the gameinfo.txt for our mod and mount it's caches char gameInfoFilename[MAX_PATH]; Q_snprintf( gameInfoFilename, sizeof(gameInfoFilename) - 1, "%s\\gameinfo.txt", CommandLine()->ParmValue( "-game", g_gameName ) ); KeyValues *gameData = new KeyValues( "GameInfo" ); if ( gameData->LoadFromFile( g_pFullFileSystem, gameInfoFilename ) ) { KeyValues *pFileSystem = gameData->FindKey( "FileSystem" ); int iAppId = pFileSystem->GetInt( "SteamAppId" ); if ( iAppId ) { CUtlVector depList; MountDependencies( iAppId, depList ); } } gameData->deleteThis(); // remove our base search path g_pFullFileSystem->RemoveSearchPaths( "MAIN" ); } } #endif // Set up mod information ModInfo_t info; info.m_pInstance = GetAppInstance(); info.m_pBaseDirectory = UTIL_GetBaseDir(); info.m_pInitialMod = CommandLine()->ParmValue( "-game", g_gameName ); info.m_pInitialGame = CommandLine()->ParmValue( "-defaultgamedir", g_gameName ); info.m_pParentAppSystemGroup = this; info.m_bTextMode = CommandLine()->CheckParm( "-textmode" ) ? true : false; if ( engine->ModInit( info ) ) { engine->ModShutdown(); } // if engine->ModInit return 0; } void CDedicatedAppSystemGroup::PostShutdown() { #ifdef _WIN32 if ( g_bVGui ) { StopVGUI(); } #endif sys->DestroyConsoleWindow(); console.ShutDown(); NET_Shutdown(); BaseClass::PostShutdown(); } void CDedicatedAppSystemGroup::Destroy() { LoggingSystem_PopLoggingState(); } //----------------------------------------------------------------------------- // Gets the executable name //----------------------------------------------------------------------------- bool GetExecutableName( char *out, int nMaxLen ) { #ifdef _WIN32 if ( !::GetModuleFileName( ( HINSTANCE )GetModuleHandle( NULL ), out, nMaxLen ) ) { return false; } return true; #elif POSIX Q_strncpy( out, g_szEXEName, nMaxLen ); return true; #endif } //----------------------------------------------------------------------------- // Purpose: Return the directory where this .exe is running from // Output : char //----------------------------------------------------------------------------- void UTIL_ComputeBaseDir( char *pBaseDir, int nMaxLen ) { int j; char *pBuffer = NULL; pBaseDir[ 0 ] = 0; if ( GetExecutableName( pBaseDir, nMaxLen ) ) { pBuffer = strrchr( pBaseDir, CORRECT_PATH_SEPARATOR ); if ( pBuffer && *pBuffer ) { *(pBuffer+1) = '\0'; } j = strlen( pBaseDir ); if (j > 0) { if ( ( pBaseDir[ j-1 ] == '\\' ) || ( pBaseDir[ j-1 ] == '/' ) ) { pBaseDir[ j-1 ] = 0; } } } char const *pOverrideDir = CommandLine()->CheckParm( "-basedir" ); if ( pOverrideDir ) { strcpy( pBaseDir, pOverrideDir ); } Q_strlower( pBaseDir ); Q_FixSlashes( pBaseDir ); } //----------------------------------------------------------------------------- // This class is a helper class used for steam-based applications. // It loads up the file system in preparation for using it to load other // required modules from steam. // // I couldn't use the one in appframework because the dedicated server // inlines all the filesystem code. //----------------------------------------------------------------------------- class CDedicatedSteamApplication : public CSteamApplication { public: CDedicatedSteamApplication( CSteamAppSystemGroup *pAppSystemGroup ); virtual bool Create( ); }; //----------------------------------------------------------------------------- // This class is a helper class used for steam-based applications. // It loads up the file system in preparation for using it to load other // required modules from steam. // // I couldn't use the one in appframework because the dedicated server // inlines all the filesystem code. //----------------------------------------------------------------------------- CDedicatedSteamApplication::CDedicatedSteamApplication( CSteamAppSystemGroup *pAppSystemGroup ) : CSteamApplication( pAppSystemGroup ) { } //----------------------------------------------------------------------------- // Implementation of IAppSystemGroup //----------------------------------------------------------------------------- bool CDedicatedSteamApplication::Create( ) { // Add in the cvar factory AppModule_t cvarModule = LoadModule( VStdLib_GetICVarFactory() ); AddSystem( cvarModule, CVAR_INTERFACE_VERSION ); AppModule_t fileSystemModule = LoadModule( FileSystemFactory ); m_pFileSystem = (IFileSystem*)AddSystem( fileSystemModule, FILESYSTEM_INTERFACE_VERSION ); if ( !m_pFileSystem ) { Warning( "Unable to load the file system!\n" ); return false; } return true; } static bool s_GameInfoSuggestFN( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ) { V_strncpy( pchPathBuffer, "left4dead", nBufferLength ); return true; } //----------------------------------------------------------------------------- // // Main entry point for dedicated server, shared between win32 and linux // //----------------------------------------------------------------------------- int main(int argc, char **argv) { #if !defined( POSIX ) && !defined( _WIN64 ) _asm { fninit } #endif SetupFPUControlWord(); #ifdef POSIX strcpy(g_szEXEName, *argv); // Store off command line for argument searching BuildCmdLine(argc, argv); #endif MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); // Store off command line for argument searching CommandLine()->CreateCmdLine( GetCommandLine() ); #ifndef _WIN32 Plat_SetCommandLine( CommandLine()->GetCmdLine() ); #endif #ifdef LINUX if ( CommandLine()->CheckParm( "-mtrace" ) ) { mtrace(); } #ifndef DEDICATED if ( CommandLine()->CheckParm( "-logmem" ) ) { EnableMemoryLogging( true ); } #endif #endif // Figure out the directory the executable is running from // and make that be the current working directory char pBasedir[ MAX_PATH ]; UTIL_ComputeBaseDir( pBasedir, MAX_PATH ); _chdir( pBasedir ); // Rehook the command line. CommandLine()->CreateCmdLine( GetCommandLine() ); if ( !InitInstance() ) return -1; SetSuggestGameInfoDirFn( s_GameInfoSuggestFN ); CDedicatedAppSystemGroup dedicatedSystems; CDedicatedSteamApplication steamApplication( &dedicatedSystems ); int nRet = steamApplication.Run( ); #ifdef LINUX #ifndef DEDICATED EnableMemoryLogging( false ); if ( CommandLine()->CheckParm( "-mtrace" ) ) { muntrace(); } #endif #endif return nRet; }