357 lines
9.1 KiB
C++
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 );
|
|
}
|
|
|
|
|