//================ Copyright (c) 1996-2009 Valve Corporation. All Rights Reserved. ================= // // // //================================================================================================== #ifdef _WIN32 #define _WIN32_WINNT 0x0500 #include #include #else #include #include #endif #include #include "tier0/platform.h" #include "tier0/basetypes.h" #include "ilaunchabledll.h" #define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') #ifdef PLATFORM_WINDOWS #pragma warning(disable : 4127) #define CORRECT_PATH_SEPARATOR_S "\\" #define CORRECT_PATH_SEPARATOR '\\' #define INCORRECT_PATH_SEPARATOR '/' #else #define CORRECT_PATH_SEPARATOR '/' #define CORRECT_PATH_SEPARATOR_S "/" #define INCORRECT_PATH_SEPARATOR '\\' #endif #undef stricmp #ifdef COMPILER_MSVC #define V_stricmp stricmp #else #define V_stricmp strcasecmp #endif #define CREATEINTERFACE_PROCNAME "CreateInterface" typedef void* (*CreateInterfaceFn)(const char *pName, int *pReturnCode); static void V_strncpy( char *pDest, char const *pSrc, int maxLen ) { strncpy( pDest, pSrc, maxLen ); if ( maxLen > 0 ) { pDest[maxLen-1] = 0; } } static int V_strlen( const char *pStr ) { return (int)strlen( pStr ); } static void V_strncat( char *pDest, const char *pSrc, int destSize ) { strncat( pDest, pSrc, destSize ); pDest[destSize-1] = 0; } static void V_AppendSlash( char *pStr, int strSize ) { int len = V_strlen( pStr ); if ( len > 0 && !PATHSEPARATOR(pStr[len-1]) ) { if ( len+1 >= strSize ) { fprintf( stderr, "V_AppendSlash: ran out of space on %s.", pStr ); exit( 1 ); } pStr[len] = CORRECT_PATH_SEPARATOR; pStr[len+1] = 0; } } static void V_FixSlashes( char *pStr ) { for ( ; *pStr; ++pStr ) { if ( *pStr == INCORRECT_PATH_SEPARATOR ) *pStr = CORRECT_PATH_SEPARATOR; } } static void V_ComposeFileName( const char *path, const char *filename, char *dest, int destSize ) { V_strncpy( dest, path, destSize ); V_AppendSlash( dest, destSize ); V_strncat( dest, filename, destSize ); V_FixSlashes( dest ); } static int V_snprintf( char *pDest, int maxLen, const char *pFormat, ... ) { va_list marker; va_start( marker, pFormat ); #ifdef _WIN32 int len = _vsnprintf( pDest, maxLen, pFormat, marker ); #elif POSIX int len = vsnprintf( pDest, maxLen, pFormat, marker ); #else #error "define vsnprintf type." #endif va_end( marker ); // Len < 0 represents an overflow if( len < 0 ) { len = maxLen; pDest[maxLen-1] = 0; } return len; } static bool V_StripLastDir( char *dirName, int maxlen ) { if( dirName[0] == 0 || !V_stricmp( dirName, "./" ) || !V_stricmp( dirName, ".\\" ) ) { return false; } int len = V_strlen( dirName ); // skip trailing slash if ( PATHSEPARATOR( dirName[len-1] ) ) { len--; } while ( len > 0 ) { if ( PATHSEPARATOR( dirName[len-1] ) ) { dirName[len] = 0; V_FixSlashes( dirName ); return true; } len--; } // Allow it to return an empty string and true. This can happen if something like "tf2/" is passed in. // The correct behavior is to strip off the last directory ("tf2") and return true. if( len == 0 ) { V_snprintf( dirName, maxlen, ".%c", CORRECT_PATH_SEPARATOR ); return true; } return true; } #ifdef _WIN32 typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) #define IO_REPARSE_TAG_SYMLINK 0xa0000003 void TranslateSymlink( const char *pInDir, char *pOutDir, int len ) { // This is the default. If it's a reparse point, it'll get replaced below. V_strncpy( pOutDir, pInDir, len ); // The equivalent of symlinks in Win32 is "NTFS reparse points". DWORD nAttribs = GetFileAttributes( pInDir ); if ( nAttribs & FILE_ATTRIBUTE_REPARSE_POINT ) { HANDLE hDir = CreateFile( pInDir, FILE_READ_EA, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL ); if ( hDir ) { DWORD dwBufSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; REPARSE_DATA_BUFFER *pReparseData = (REPARSE_DATA_BUFFER*)malloc( dwBufSize ); DWORD nBytesReturned = 0; BOOL bSuccess = DeviceIoControl( hDir, FSCTL_GET_REPARSE_POINT, NULL, 0, pReparseData, dwBufSize, &nBytesReturned, NULL ); CloseHandle( hDir ); if ( bSuccess ) { if ( IsReparseTagMicrosoft( pReparseData->ReparseTag ) ) { if ( pReparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK ) { REPARSE_DATA_BUFFER *rdata = pReparseData; // Pull out the substitution name. char szSubName[MAX_PATH*2]; wchar_t *pSrcString = &rdata->SymbolicLinkReparseBuffer.PathBuffer[rdata->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)]; size_t nConvertedChars; wcstombs_s( &nConvertedChars, szSubName, wcslen( pSrcString ) + 1, pSrcString, _TRUNCATE ); // Look for the drive letter and start there. const char *pColon = strchr( szSubName, ':' ); if ( pColon && pColon > szSubName ) { const char *pRemappedName = ( pColon - 1 ); V_strncpy( pOutDir, pRemappedName, len ); } } } } free( pReparseData ); } else { printf( "Warning: Found a reparse point (ntfs symlink) for %s but CreateFile failed\n", pInDir ); } } } #endif int main( int argc, char **argv ) { // Find the game\bin directory and setup the DLL path. char szModuleFilename[MAX_PATH], szModuleParts[MAX_PATH], szCurDir[MAX_PATH]; #ifdef WIN32 GetModuleFileName( NULL, szModuleFilename, sizeof( szModuleFilename ) ); V_FixSlashes( szModuleFilename ); #else V_strncpy( szModuleFilename, argv[0], sizeof(szModuleFilename) ); #endif V_strncpy( szModuleParts, szModuleFilename, sizeof( szModuleParts ) ); char *pFilename = strrchr( szModuleParts, CORRECT_PATH_SEPARATOR ); if ( !pFilename ) { fprintf( stderr, "%s (binlaunch): Can't get filename from GetModuleFilename (%s).\n", argv[0], szModuleFilename ); return 1; } *pFilename = 0; ++pFilename; const char *pBaseDir = szModuleParts; #ifdef WIN32 TranslateSymlink( pBaseDir, szCurDir, sizeof( szCurDir ) ); #else V_strncpy( szCurDir, pBaseDir, sizeof(szCurDir) ); #endif char szGameBinDir[MAX_PATH]; while ( 1 ) { V_ComposeFileName( szCurDir, "game" CORRECT_PATH_SEPARATOR_S "bin", szGameBinDir, sizeof( szGameBinDir ) ); // Look for stuff we know about in game\bin. char szTestFile1[MAX_PATH], szTestFile2[MAX_PATH]; V_ComposeFileName( szGameBinDir, "tier0.dll", szTestFile1, sizeof( szTestFile1 ) ); V_ComposeFileName( szGameBinDir, "vstdlib.dll", szTestFile2, sizeof( szTestFile2 ) ); if ( _access( szTestFile1, 0 ) == 0 && _access( szTestFile2, 0 ) == 0 ) { break; } // Backup a directory. if ( !V_StripLastDir( szCurDir, sizeof( szCurDir ) ) ) { fprintf( stderr, "%s (binlaunch): Unable to find game\\bin directory anywhere up the tree from %s.\n", argv[0], pBaseDir ); return 1; } } // Setup the path to include the specified directory. int nGameBinDirLen = V_strlen( szGameBinDir ); char *pOldPath = getenv( "PATH" ); int nNewLen = V_strlen( pOldPath ) + nGameBinDirLen + 5 + 1 + 1; // 5 for PATH=, 1 for the semicolon, and 1 for the null terminator. char *pNewPath = new char[nNewLen]; V_snprintf( pNewPath, nNewLen, "PATH=%s;%s", szGameBinDir, pOldPath ); _putenv( pNewPath ); delete [] pNewPath; // Get rid of the file extension on our executable name. #ifdef WIN32 char *pDot = strchr( &szModuleFilename[pFilename-szModuleParts], '.' ); if ( !pDot ) { fprintf( stderr, "%s (binlaunch): No dot character in the filename.\n", argv[0] ); return 1; } *pDot = 0; #endif char szDLLName[MAX_PATH]; V_snprintf( szDLLName, sizeof( szDLLName ), "%s%s", szModuleFilename, DLL_EXT_STRING ); // // Now go load their DLL and launch it. // #ifdef WIN32 HMODULE hModule = LoadLibrary( szDLLName ); #else HMODULE hModule = dlopen( szDLLName, RTLD_NOW ); #endif if ( !hModule ) { fprintf( stderr, "%s (binlaunch): Unable to load module %s\n\n", argv[0], szDLLName ); return 9998; } CreateInterfaceFn fn = (CreateInterfaceFn)GetProcAddress( hModule, CREATEINTERFACE_PROCNAME ); ILaunchableDLL *pLaunchable; if ( !fn ) { fprintf( stderr, "%s (binlaunch): Can't get function %s from %s\n\n", argv[0], CREATEINTERFACE_PROCNAME, szDLLName ); return 9997; } pLaunchable = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, NULL ); if ( !pLaunchable ) { fprintf( stderr, "%s (binlaunch): Can't get interface %s from from %s\n\n", argv[0], LAUNCHABLE_DLL_INTERFACE_VERSION, szDLLName ); return 9996; } return pLaunchable->main( argc, argv ); }