//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: A redirection tool that allows the DLLs to reside elsewhere. // //=====================================================================================// #if defined( _WIN32 ) && !defined( _X360 ) #include #include #include #include #endif #if defined( _X360 ) #define _XBOX #include #include #include #include #include "xbox\xbox_core.h" #include "xbox\xbox_launch.h" #endif #ifdef POSIX #include #include #include #include #include #define MAX_PATH PATH_MAX #endif #include "tier0/basetypes.h" #ifdef WIN32 typedef int (*LauncherMain_t)( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ); #elif POSIX typedef int (*LauncherMain_t)( int argc, char **argv ); #else #error #endif #ifdef WIN32 // hinting the nvidia driver to use the dedicated graphics card in an optimus configuration // for more info, see: http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf extern "C" { _declspec( dllexport ) DWORD NvOptimusEnablement = 0x00000001; } // same thing for AMD GPUs using v13.35 or newer drivers extern "C" { __declspec( dllexport ) int AmdPowerXpressRequestHighPerformance = 1; } #endif //----------------------------------------------------------------------------- // Purpose: Return the directory where this .exe is running from // Output : char //----------------------------------------------------------------------------- #if !defined( _X360 ) static char *GetBaseDir( const char *pszBuffer ) { static char basedir[ MAX_PATH ]; char szBuffer[ MAX_PATH ]; size_t j; char *pBuffer = NULL; strcpy( szBuffer, pszBuffer ); pBuffer = strrchr( szBuffer,'\\' ); if ( pBuffer ) { *(pBuffer+1) = '\0'; } strcpy( basedir, szBuffer ); j = strlen( basedir ); if (j > 0) { if ( ( basedir[ j-1 ] == '\\' ) || ( basedir[ j-1 ] == '/' ) ) { basedir[ j-1 ] = 0; } } return basedir; } #ifdef WIN32 int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { // Must add 'bin' to the path.... char* pPath = getenv("PATH"); // Use the .EXE name to determine the root directory char moduleName[ MAX_PATH ]; char szBuffer[4096]; if ( !GetModuleFileName( hInstance, moduleName, MAX_PATH ) ) { MessageBox( 0, "Failed calling GetModuleFileName", "Launcher Error", MB_OK ); return 0; } // Get the root directory the .exe is in char* pRootDir = GetBaseDir( moduleName ); #ifdef _DEBUG int len = #endif _snprintf( szBuffer, sizeof( szBuffer ), "PATH=%s\\bin\\;%s", pRootDir, pPath ); szBuffer[sizeof( szBuffer ) - 1] = '\0'; assert( len < sizeof( szBuffer ) ); _putenv( szBuffer ); // Assemble the full path to our "launcher.dll" _snprintf( szBuffer, sizeof( szBuffer ), "%s\\bin\\launcher.dll", pRootDir ); szBuffer[sizeof( szBuffer ) - 1] = '\0'; // STEAM OK ... filesystem not mounted yet #if defined(_X360) HINSTANCE launcher = LoadLibrary( szBuffer ); #else HINSTANCE launcher = LoadLibraryEx( szBuffer, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); #endif if ( !launcher ) { char *pszError; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&pszError, 0, NULL); char szBuf[1024]; _snprintf(szBuf, sizeof( szBuf ), "Failed to load the launcher DLL:\n\n%s", pszError); szBuf[sizeof( szBuf ) - 1] = '\0'; MessageBox( 0, szBuf, "Launcher Error", MB_OK ); LocalFree(pszError); return 0; } LauncherMain_t main = (LauncherMain_t)GetProcAddress( launcher, "LauncherMain" ); return main( hInstance, hPrevInstance, lpCmdLine, nCmdShow ); } #elif defined (POSIX) #if defined( LINUX ) #include #include static bool IsDebuggerPresent( int time ) { // Need to get around the __wrap_open() stuff. Just find the open symbol // directly and use it... typedef int (open_func_t)( const char *pathname, int flags, mode_t mode ); open_func_t *open_func = (open_func_t *)dlsym( RTLD_NEXT, "open" ); if ( open_func ) { for ( int i = 0; i < time; i++ ) { int tracerpid = -1; int fd = (*open_func)( "/proc/self/status", O_RDONLY, S_IRUSR ); if (fd >= 0) { char buf[ 4096 ]; static const char tracerpid_str[] = "TracerPid:"; const int len = read( fd, buf, sizeof(buf) - 1 ); if ( len > 0 ) { buf[ len ] = 0; const char *str = strstr( buf, tracerpid_str ); tracerpid = str ? atoi( str + sizeof( tracerpid_str ) ) : -1; } close( fd ); } if ( tracerpid > 0 ) return true; sleep( 1 ); } } return false; } static void WaitForDebuggerConnect( int argc, char *argv[], int time ) { for ( int i = 1; i < argc; i++ ) { if ( strstr( argv[i], "-wait_for_debugger" ) ) { printf( "\nArg -wait_for_debugger found.\nWaiting %dsec for debugger...\n", time ); printf( " pid = %d\n", getpid() ); if ( IsDebuggerPresent( time ) ) printf("Debugger connected...\n\n"); break; } } } #else static void WaitForDebuggerConnect( int argc, char *argv[], int time ) { } #endif // !LINUX int main( int argc, char *argv[] ) { void *launcher = dlopen( "liblauncher" DLL_EXT_STRING, RTLD_NOW ); if ( !launcher ) { fprintf( stderr, "%s\nFailed to load the launcher\n", dlerror() ); launcher = dlopen( "launcher" DLL_EXT_STRING, RTLD_NOW ); } if ( !launcher ) { fprintf( stderr, "%s\nFailed to load the launcher\n", dlerror() ); return 0; } LauncherMain_t main = (LauncherMain_t)dlsym( launcher, "LauncherMain" ); if ( !main ) { fprintf( stderr, "Failed to load the launcher entry proc\n" ); return 0; } #if defined(__clang__) && !defined(OSX) // When building with clang we absolutely need the allocator to always // give us 16-byte aligned memory because if any objects are tagged as // being 16-byte aligned then clang will generate SSE instructions to move // and initialize them, and an allocator that does not respect the // contract will lead to crashes. On Linux we normally use the default // allocator which does not give us this alignment guarantee. // The google tcmalloc allocator gives us this guarantee. // Test the current allocator to make sure it gives us the required alignment. void* pointers[20]; for (int i = 0; i < ARRAYSIZE(pointers); ++i) { void* p = malloc( 16 ); pointers[ i ] = p; if ( ( (size_t)p ) & 0xF ) { printf( "%p is not 16-byte aligned. Aborting.\n", p ); printf( "Pass /define:CLANG to VPC to correct this.\n" ); return -10; } } for (int i = 0; i < ARRAYSIZE(pointers); ++i) { if ( pointers[ i ] ) free( pointers[ i ] ); } if ( __has_feature(address_sanitizer) ) { printf( "Address sanitizer is enabled.\n" ); } else { printf( "No address sanitizer!\n" ); } #endif WaitForDebuggerConnect( argc, argv, 30 ); return main( argc, argv ); } #else #error #endif // WIN32 || POSIX #else // X360 //----------------------------------------------------------------------------- // 360 Quick and dirty command line parsing. Returns true if key found, // false otherwise. Caller can optionally get next argument. //----------------------------------------------------------------------------- bool ParseCommandLineArg( const char *pCmdLine, const char* pKey, char* pValueBuff = NULL, int valueBuffSize = 0 ) { int keyLen = (int)strlen( pKey ); const char* pArg = pCmdLine; for ( ;; ) { // scan for match pArg = strstr( (char*)pArg, pKey ); if ( !pArg ) { return false; } // found, but could be a substring if ( pArg[keyLen] == '\0' || pArg[keyLen] == ' ' ) { // exact match break; } pArg += keyLen; } if ( pValueBuff ) { // caller wants next token // skip past key and whitespace pArg += keyLen; while ( *pArg == ' ' ) { pArg++; } int i; for ( i=0; i 0 && pNewCmdLine[len-1] == ' ' ) { len--; } pNewCmdLine[len] = '\0'; } //----------------------------------------------------------------------------- // 360 Conditional spew //----------------------------------------------------------------------------- void Spew( const char *pFormat, ... ) { #if defined( _DEBUG ) char msg[2048]; va_list argptr; va_start( argptr, pFormat ); vsprintf( msg, pFormat, argptr ); va_end( argptr ); OutputDebugString( msg ); #endif } //----------------------------------------------------------------------------- // Adheres to possible xbox exclude paths in for -dvddev mode. //----------------------------------------------------------------------------- bool IsBinExcluded( const char *pRemotePath, bool *pExcludeAll ) { *pExcludeAll = false; bool bIsBinExcluded = false; // find optional exclusion file char szExcludeFile[MAX_PATH]; sprintf( szExcludeFile, "%s\\xbox_exclude_paths.txt", pRemotePath ); HANDLE hFile = CreateFile( szExcludeFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL ); if ( hFile != INVALID_HANDLE_VALUE ) { int len = GetFileSize( hFile, NULL ); if ( len > 0 ) { char *pBuffer = ( char *)malloc( len + 1 ); memset( pBuffer, 0, len+1 ); DWORD numBytesRead; if ( ReadFile( hFile, pBuffer, len, &numBytesRead, NULL ) ) { strlwr( pBuffer ); if ( strstr( pBuffer, "\"*\"" ) ) { *pExcludeAll = true; bIsBinExcluded = true; } else if ( strstr( pBuffer, "\"\\bin\"" ) ) { // override file either specifies an exclusion of the root or the bin directory bIsBinExcluded = true; } } free( pBuffer ); } CloseHandle( hFile ); } return bIsBinExcluded; } //----------------------------------------------------------------------------- // Get the new entry point and command line //----------------------------------------------------------------------------- LauncherMain_t GetLaunchEntryPoint( char *pNewCommandLine ) { HMODULE hModule; char *pCmdLine; // determine source of our invocation, internal or external // a valid launch payload will have an embedded command line // command line could be from internal restart in dev or retail mode CXboxLaunch xboxLaunch; int payloadSize; unsigned int launchID; char *pPayload; bool bInternalRestart = xboxLaunch.GetLaunchData( &launchID, (void**)&pPayload, &payloadSize ); if ( !bInternalRestart || !payloadSize || launchID != VALVE_LAUNCH_ID ) { // launch is not ours if ( launchID == LAUNCH_DATA_DEMO_ID ) { // data is a demo blob, not ready to handle yet, so ignore } // could be first time, get command line from system pCmdLine = GetCommandLine(); if ( !stricmp( pCmdLine, "\"default.xex\"" ) ) { // matches retail xex and no arguments, mut be first time retail launch pCmdLine = "default.xex -dvd"; #if defined( _MEMTEST ) pCmdLine = "default.xex -dvd +mat_picmip 2"; #endif } } else { // get embedded command line from payload pCmdLine = pPayload; } int launchFlags = xboxLaunch.GetLaunchFlags(); #if !defined( _CERT ) if ( launchFlags & LF_ISDEBUGGING ) { while ( !DmIsDebuggerPresent() ) { } Sleep( 1000 ); Spew( "Resuming debug session.\n" ); } #endif if ( launchID == VALVE_LAUNCH_ID ) { // unforunately, the xbox erases its internal store upon first fetch // must re-establish it so the payload that contains other data (past command line) can be accessed by the game // the launch data will be owned by tier0 and supplied to game xboxLaunch.SetLaunchData( pPayload, payloadSize, launchFlags ); } // The 360 has no paths and therefore the xex must reside in the same location as the dlls. // Only the xex must reside locally, on the box, but the dlls can be mounted from the remote share. // Resolve all known implicitly loaded dlls to be explicitly loaded now to allow their remote location. const char *pImplicitDLLs[] = { "tier0_360.dll", "vstdlib_360.dll", "vxbdm_360.dll", // last slot reserved, as dynamic, used to determine which application gets launched "???" }; // Corresponds to pImplicitDLLs. A dll load failure is only an error if that dll is tagged as required. const bool bDllRequired[] = { true, // tier0 true, // vstdlib false, // vxbdm true, // ??? }; char gameName[32]; bool bDoChooser = false; if ( !ParseCommandLineArg( pCmdLine, "-game", gameName, sizeof( gameName ) ) ) { // usage of remote share requires a game (default to hl2) // remote share mandates an absolute game path which is detected by the host strcpy( gameName, "hl2" ); bDoChooser = true; } else { // sanitize a possible absolute game path back to expected game name char *pSlash = strrchr( gameName, '\\' ); if ( pSlash ) { memcpy( gameName, pSlash+1, strlen( pSlash+1 )+1 ); } } char shareName[32]; if ( !ParseCommandLineArg( pCmdLine, "-share", shareName, sizeof( shareName ) ) ) { // usage of remote share requires a share name for the game folder (default to game) strcpy( shareName, "game" ); } if ( ( xboxLaunch.GetLaunchFlags() & LF_EXITFROMGAME ) && !( xboxLaunch.GetLaunchFlags() & LF_GAMERESTART ) ) { // exiting from a game back to chooser bDoChooser = true; } // If we're restarting from an invite, we're funneling into TF2 if ( launchFlags & LF_INVITERESTART ) { strcpy( gameName, "tf" ); bDoChooser = false; } // resolve which application gets launched if ( bDoChooser ) { // goto high level 1 of N game selector pImplicitDLLs[ARRAYSIZE( pImplicitDLLs )-1] = "AppChooser_360.dll"; } else { pImplicitDLLs[ARRAYSIZE( pImplicitDLLs )-1] = "launcher_360.dll"; } // the base path is the where the game is predominantly anchored char basePath[128]; // a remote path is for development mode only, on the host pc char remotePath[128]; #if !defined( _CERT ) if ( !ParseCommandLineArg( pCmdLine, "-dvd" ) ) { // development mode only, using host pc // auto host name detection can be overriden via command line char hostName[32]; if ( !ParseCommandLineArg( pCmdLine, "-host", hostName, sizeof( hostName ) ) ) { // auto detect, the 360 machine name must be _360 DWORD length = sizeof( hostName ); HRESULT hr = DmGetXboxName( hostName, &length ); if ( hr != XBDM_NOERR ) { Spew( "FATAL: Could not get xbox name: %s\n", hostName ); return NULL; } char *p = strstr( hostName, "_360" ); if ( !p ) { Spew( "FATAL: Xbox name must be _360\n" ); return NULL; } *p = '\0'; } sprintf( remotePath, "net:\\smb\\%s\\%s", hostName, shareName ); // network remote shares seem to be buggy, but always manifest as the gamedir being unaccessible // validate now, otherwise longer wait until process eventually fails char szFullPath[MAX_PATH]; WIN32_FIND_DATA findData; sprintf( szFullPath, "%s\\%s\\*.*", remotePath, gameName ); HANDLE hFindFile = FindFirstFile( szFullPath, &findData ); if ( hFindFile == INVALID_HANDLE_VALUE ) { Spew( "*******************************************************************\n" ); Spew( "FATAL: Access to remote share '%s' on host PC lost. Forcing cold reboot.\n", szFullPath ); Spew( "FATAL: After reboot completes to dashboard, restart application.\n" ); Spew( "*******************************************************************\n" ); DmRebootEx( DMBOOT_COLD, NULL, NULL, NULL ); return NULL; } FindClose( hFindFile ); } #endif char *searchPaths[2]; searchPaths[0] = basePath; int numSearchPaths = 1; bool bExcludeAll = false; bool bAddRemotePath = false; if ( ParseCommandLineArg( pCmdLine, "-dvd" ) ) { // game runs from dvd only strcpy( basePath, "d:" ); } else if ( ParseCommandLineArg( pCmdLine, "-dvddev" ) ) { // dvd development, game runs from dvd and can fall through to access remote path // check user configuration for possible \bin exclusion from Xbox HDD strcpy( basePath, "d:" ); if ( IsBinExcluded( remotePath, &bExcludeAll ) ) { searchPaths[0] = remotePath; numSearchPaths = 1; } else { searchPaths[0] = basePath; searchPaths[1] = remotePath; numSearchPaths = 2; } if ( bExcludeAll ) { // override, user has excluded everything, game runs from remote path only strcpy( basePath, remotePath ); } else { // -dvddev appends a -remote for the filesystem to detect bAddRemotePath = true; } } else { // game runs from remote path only strcpy( basePath, remotePath ); } // load all the dlls specified char dllPath[MAX_PATH]; for ( int i=0; i