csgo-2018-source/utils/binlaunch/binlaunch.cpp
2021-07-24 21:11:47 -07:00

357 lines
9.1 KiB
C++

//================ Copyright (c) 1996-2009 Valve Corporation. All Rights Reserved. =================
//
//
//
//==================================================================================================
#ifdef _WIN32
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <io.h>
#else
#include <stdarg.h>
#include <dlfcn.h>
#endif
#include <stdio.h>
#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 );
}