source-engine/filesystem/filesystem_steam.cpp

1537 lines
42 KiB
C++
Raw Normal View History

2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "basefilesystem.h"
#include "steamcommon.h"
#include "SteamInterface.h"
#include "tier0/dbg.h"
#include "tier0/icommandline.h"
#include "steam/steam_api.h"
#ifdef POSIX
#include <fcntl.h>
#ifdef LINUX
#include <sys/file.h>
#endif
#include <dlfcn.h>
#define _S_IWRITE S_IWRITE
#define _S_IWRITE S_IWRITE
#define _S_IFREG S_IFREG
#define FILE_ATTRIBUTE_OFFLINE 0x1000
#endif
#ifdef _WIN32
extern "C"
{
__declspec(dllimport) int __stdcall IsDebuggerPresent();
}
#endif
ISteamInterface *steam = NULL;
static SteamHandle_t g_pLastErrorFile;
static TSteamError g_tLastError;
static TSteamError g_tLastErrorNoFile;
void CheckError( SteamHandle_t fp, TSteamError & steamError)
{
if (steamError.eSteamError == eSteamErrorContentServerConnect)
{
// fatal error
#ifdef WIN32
// kill the current window so the user can see the error
HWND hwnd = GetForegroundWindow();
if (hwnd)
{
DestroyWindow(hwnd);
}
// show the error
MessageBox(NULL, "Could not acquire necessary game files because the connection to Steam servers was lost.", "Source - Fatal Error", MB_OK | MB_ICONEXCLAMATION);
// get out of here immediately
TerminateProcess(GetCurrentProcess(), 0);
#else
fprintf( stderr, "Could not acquire necessary game files because the connection to Steam servers was lost." );
exit(-1);
#endif
return;
}
if (fp)
{
if (steamError.eSteamError != eSteamErrorNone || g_tLastError.eSteamError != eSteamErrorNone)
{
g_pLastErrorFile = fp;
g_tLastError = steamError;
}
}
else
{
// write to the NULL error checker
if (steamError.eSteamError != eSteamErrorNone || g_tLastErrorNoFile.eSteamError != eSteamErrorNone)
{
g_tLastErrorNoFile = steamError;
}
}
}
#ifdef POSIX
class CSteamFile
{
public:
explicit CSteamFile( SteamHandle_t file, bool bWriteable, const char *pchName ) : m_File( file ), m_bWriteable( bWriteable ), m_FileName(pchName) {}
~CSteamFile() {}
SteamHandle_t Handle() { return m_File; }
bool BWriteable() { return m_bWriteable; }
CUtlSymbol GetFileName() { return m_FileName; }
private:
SteamHandle_t m_File;
bool m_bWriteable;
CUtlSymbol m_FileName;
};
#endif
class CFileSystem_Steam : public CBaseFileSystem
{
public:
CFileSystem_Steam();
~CFileSystem_Steam();
// Methods of IAppSystem
virtual InitReturnVal_t Init();
virtual void Shutdown();
virtual void * QueryInterface( const char *pInterfaceName );
// Higher level filesystem methods requiring specific behavior
virtual void GetLocalCopy( const char *pFileName );
virtual int HintResourceNeed( const char *hintlist, int forgetEverything );
virtual CSysModule * LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly );
virtual bool IsFileImmediatelyAvailable(const char *pFileName);
// resource waiting
virtual WaitForResourcesHandle_t WaitForResources( const char *resourcelist );
virtual bool GetWaitForResourcesProgress( WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */ );
virtual void CancelWaitForResources( WaitForResourcesHandle_t handle );
virtual bool IsSteam() const { return true; }
virtual FilesystemMountRetval_t MountSteamContent( int nExtraAppId = -1 );
protected:
// implementation of CBaseFileSystem virtual functions
virtual FILE *FS_fopen( const char *filename, const char *options, unsigned flags, int64 *size, CFileLoadInfo *pInfo );
virtual void FS_setbufsize( FILE *fp, unsigned nBytes );
virtual void FS_fclose( FILE *fp );
virtual void FS_fseek( FILE *fp, int64 pos, int seekType );
virtual long FS_ftell( FILE *fp );
virtual int FS_feof( FILE *fp );
virtual size_t FS_fread( void *dest, size_t destSize, size_t size, FILE *fp );
virtual size_t FS_fwrite( const void *src, size_t size, FILE *fp );
virtual size_t FS_vfprintf( FILE *fp, const char *fmt, va_list list );
virtual int FS_ferror( FILE *fp );
virtual int FS_fflush( FILE *fp );
virtual char *FS_fgets( char *dest, int destSize, FILE *fp );
virtual int FS_stat( const char *path, struct _stat *buf, bool *pbLoadedFromSteamCache=NULL );
virtual int FS_chmod( const char *path, int pmode );
virtual HANDLE FS_FindFirstFile(const char *findname, WIN32_FIND_DATA *dat);
virtual bool FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat);
virtual bool FS_FindClose(HANDLE handle);
private:
bool IsFileInSteamCache( const char *file );
bool IsFileInSteamCache2( const char *file );
void ViewSteamCache( const char* szDir, bool bRecurse );
bool m_bSteamInitialized;
bool m_bCurrentlyLoading;
bool m_bAssertFilesImmediatelyAvailable;
bool m_bCanAsync;
bool m_bSelfMounted;
bool m_bContentLoaded;
bool m_bSDKToolMode;
SteamCallHandle_t m_hWaitForResourcesCallHandle;
int m_iCurrentReturnedCallHandle;
HMODULE m_hSteamDLL;
void LoadAndStartSteam();
#ifdef POSIX
static CUtlMap< int, CInterlockedInt > m_LockedFDMap;
#endif
};
#ifdef POSIX
CUtlMap< int, CInterlockedInt> CFileSystem_Steam::m_LockedFDMap;
#endif
//-----------------------------------------------------------------------------
// singleton
//-----------------------------------------------------------------------------
static CFileSystem_Steam g_FileSystem_Steam;
#if defined(DEDICATED)
CBaseFileSystem *BaseFileSystem_Steam( void )
{
return &g_FileSystem_Steam;
}
#endif
#ifdef DEDICATED // "hack" to allow us to not export a stdio version of the FILESYSTEM_INTERFACE_VERSION anywhere
IFileSystem *g_pFileSystemSteam = &g_FileSystem_Steam;
IBaseFileSystem *g_pBaseFileSystemSteam = &g_FileSystem_Steam;
#else
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Steam, IFileSystem, FILESYSTEM_INTERFACE_VERSION, g_FileSystem_Steam );
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Steam, IBaseFileSystem, BASEFILESYSTEM_INTERFACE_VERSION, g_FileSystem_Steam );
#endif
//-----------------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------------
CFileSystem_Steam::CFileSystem_Steam()
{
m_bSteamInitialized = false;
m_bCurrentlyLoading = false;
m_bAssertFilesImmediatelyAvailable = false;
m_bCanAsync = true;
m_bContentLoaded = false;
m_hWaitForResourcesCallHandle = STEAM_INVALID_CALL_HANDLE;
m_iCurrentReturnedCallHandle = 1;
m_hSteamDLL = NULL;
m_bSDKToolMode = false;
#ifdef POSIX
SetDefLessFunc( m_LockedFDMap );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CFileSystem_Steam::~CFileSystem_Steam()
{
m_bSteamInitialized = false;
}
bool CFileSystem_Steam::IsFileInSteamCache2( const char *file )
{
if ( !m_bContentLoaded || m_bSDKToolMode)
{
return true;
}
// see if the file exists
TSteamElemInfo info;
TSteamError error;
SteamHandle_t h = steam->FindFirst( file, eSteamFindRemoteOnly, &info, &error );
if ( h == STEAM_INVALID_HANDLE )
{
return false;
}
else
{
steam->FindClose( h, &error );
}
return true;
}
void MountDependencies( int iAppId, CUtlVector<unsigned int> &depList )
{
TSteamError steamError;
// Setup the buffers for the TSteamApp structure.
char buffers[4][2048];
TSteamApp steamApp;
steamApp.szName = buffers[0];
steamApp.uMaxNameChars = sizeof( buffers[0] );
steamApp.szLatestVersionLabel = buffers[1];
steamApp.uMaxLatestVersionLabelChars = sizeof( buffers[1] );
steamApp.szCurrentVersionLabel = buffers[2];
steamApp.uMaxCurrentVersionLabelChars = sizeof( buffers[2] );
steamApp.szInstallDirName = buffers[3];
steamApp.uMaxInstallDirNameChars = sizeof( buffers[3] );
// Ask how many caches depend on this app ID.
steam->EnumerateApp( iAppId, &steamApp, &steamError );
if ( steamError.eSteamError != eSteamErrorNone )
Error( "EnumerateApp( %d ) failed: %s", iAppId, steamError.szDesc );
// Mount each cache.
for ( int i=0; i < (int)steamApp.uNumDependencies; i++ )
{
TSteamAppDependencyInfo appDependencyInfo;
steam->EnumerateAppDependency( iAppId, i, &appDependencyInfo, &steamError );
if ( steamError.eSteamError != eSteamErrorNone )
Error( "EnumerateAppDependency( %d, %d ) failed: %s", iAppId, i, steamError.szDesc );
if ( depList.Find( appDependencyInfo.uAppId ) == -1 )
{
depList.AddToTail( appDependencyInfo.uAppId );
// Make sure that the user owns the app before attempting to mount it
int isSubscribed = false, isPending = false;
steam->IsAppSubscribed( appDependencyInfo.uAppId, &isSubscribed, &isPending, &steamError );
if ( isSubscribed )
{
steam->MountFilesystem( appDependencyInfo.uAppId, "", &steamError );
if ( steamError.eSteamError != eSteamErrorNone && steamError.eSteamError != eSteamErrorNotSubscribed )
{
Error( "MountFilesystem( %d ) failed: %s", appDependencyInfo.uAppId, steamError.szDesc );
}
}
}
}
}
//-----------------------------------------------------------------------------
// QueryInterface:
//-----------------------------------------------------------------------------
void *CFileSystem_Steam::QueryInterface( const char *pInterfaceName )
{
// We also implement the IMatSystemSurface interface
if (!Q_strncmp( pInterfaceName, FILESYSTEM_INTERFACE_VERSION, Q_strlen(FILESYSTEM_INTERFACE_VERSION) + 1))
return (IFileSystem*)this;
return CBaseFileSystem::QueryInterface( pInterfaceName );
}
//-----------------------------------------------------------------------------
// Methods of IAppSystem
//-----------------------------------------------------------------------------
InitReturnVal_t CFileSystem_Steam::Init()
{
m_bSteamInitialized = true;
m_bSelfMounted = false;
LoadAndStartSteam();
return CBaseFileSystem::Init();
}
void CFileSystem_Steam::Shutdown()
{
Assert( m_bSteamInitialized );
if ( !steam )
return;
TSteamError steamError;
// If we're not running Steam in local mode, remove all mount points from the STEAM VFS.
if ( !CommandLine()->CheckParm("-steamlocal") && !m_bSelfMounted && !steam->UnmountAppFilesystem(&steamError) )
{
#ifdef WIN32
OutputDebugString(steamError.szDesc);
#endif
Assert(!("STEAM VFS failed to unmount"));
// just continue on as if nothing happened
// ::MessageBox(NULL, szErrorMsg, "Half-Life FileSystem_Steam Error", MB_OK);
// exit( -1 );
}
steam->Cleanup(&steamError);
if ( m_hSteamDLL )
{
Sys_UnloadModule( (CSysModule *)m_hSteamDLL );
m_hSteamDLL = NULL;
}
m_bSteamInitialized = false;
}
void CFileSystem_Steam::LoadAndStartSteam()
{
if ( !m_hSteamDLL )
{
const char *pchSteamInstallPath = SteamAPI_GetSteamInstallPath();
if ( pchSteamInstallPath )
{
char szSteamDLLPath[ MAX_PATH ];
#ifdef WIN32
V_ComposeFileName( pchSteamInstallPath, "steam" DLL_EXT_STRING, szSteamDLLPath, Q_ARRAYSIZE(szSteamDLLPath) );
#elif defined(POSIX)
V_ComposeFileName( pchSteamInstallPath, "libsteam" DLL_EXT_STRING, szSteamDLLPath, Q_ARRAYSIZE(szSteamDLLPath) );
#else
#error
#endif
// try to load the steam.dll from the running steam process first
m_hSteamDLL = (HMODULE)Sys_LoadModule( szSteamDLLPath );
}
if ( !m_hSteamDLL )
#ifdef WIN32
m_hSteamDLL = (HMODULE)Sys_LoadModule( "steam" DLL_EXT_STRING );
#elif defined(POSIX)
m_hSteamDLL = (HMODULE)Sys_LoadModule( "libsteam" DLL_EXT_STRING );
#else
#error
#endif
}
if ( m_hSteamDLL )
{
typedef void *(*PFSteamCreateInterface)( const char *pchSteam );
#ifdef WIN32
PFSteamCreateInterface pfnSteamCreateInterface = (PFSteamCreateInterface)GetProcAddress( m_hSteamDLL, "_f" );
#else
PFSteamCreateInterface pfnSteamCreateInterface = (PFSteamCreateInterface)dlsym( (void *)m_hSteamDLL, "_f" );
#endif
if ( pfnSteamCreateInterface )
steam = (ISteamInterface *)pfnSteamCreateInterface( STEAM_INTERFACE_VERSION );
}
if ( !steam )
{
Error("CFileSystem_Steam::Init() failed: failed to find steam interface\n");
#ifdef WIN32
::DestroyWindow( GetForegroundWindow() );
::MessageBox(NULL, "CFileSystem_Steam::Init() failed: failed to find steam interface", "Half-Life FileSystem_Steam Error", MB_OK);
#endif
_exit( -1 );
}
TSteamError steamError;
if (!steam->Startup(STEAM_USING_FILESYSTEM | STEAM_USING_LOGGING | STEAM_USING_USERID | STEAM_USING_ACCOUNT, &steamError))
{
Error("SteamStartup() failed: %s\n", steamError.szDesc);
#ifdef WIN32
::DestroyWindow( GetForegroundWindow() );
::MessageBox(NULL, steamError.szDesc, "Half-Life FileSystem_Steam Error", MB_OK);
#endif
_exit( -1 );
}
}
//-----------------------------------------------------------------------------
// Methods of IAppSystem
//-----------------------------------------------------------------------------
FilesystemMountRetval_t CFileSystem_Steam::MountSteamContent( int nExtraAppId )
{
m_bContentLoaded = true;
FilesystemMountRetval_t retval = FILESYSTEM_MOUNT_OK;
// MWD: This is here because of Hammer's funky startup sequence that requires MountSteamContent() be called in CHammerApp::PreInit(). Once that root problem is addressed this will be removed;
if ( NULL == steam )
{
LoadAndStartSteam();
}
// only mount if we're already logged in
// if we're not logged in, assume the app will login & mount the cache itself
// this enables both the game and the platform to use this same code, even though they mount caches at different times
int loggedIn = 0;
TSteamError steamError;
int result = steam->IsLoggedIn(&loggedIn, &steamError);
if (!result || loggedIn)
{
if ( nExtraAppId != -1 )
{
m_bSDKToolMode = true;
CUtlVector<unsigned int> depList;
if ( nExtraAppId < -1 )
{
// Special way to tell them to mount a specific App ID's depots.
MountDependencies( -nExtraAppId, depList );
return FILESYSTEM_MOUNT_OK;
}
else
{
const char *pMainAppId = NULL;
// If they specified extra app IDs they want to mount after the main one, then we mount
// the caches manually here.
#ifdef _WIN32
// Use GetEnvironmentVariable instead of getenv because getenv doesn't pick up changes
// to the process environment after the DLL was loaded.
char szMainAppId[128];
if ( GetEnvironmentVariable( "steamappid", szMainAppId, sizeof( szMainAppId ) ) != 0 )
{
pMainAppId = szMainAppId;
}
#else
// LINUX BUG: see above
pMainAppId = getenv( "SteamAppId" );
#endif // _WIN32
if ( !pMainAppId )
Error( "Extra App ID set to %d, but no SteamAppId.", nExtraAppId );
//swapping this mount order ensures the most current engine binaries are used by tools
MountDependencies( nExtraAppId, depList );
MountDependencies( atoi( pMainAppId ), depList );
return FILESYSTEM_MOUNT_OK;
}
}
else if (!steam->MountAppFilesystem(&steamError))
{
Error("MountAppFilesystem() failed: %s\n", steamError.szDesc);
#ifdef WIN32
::DestroyWindow( GetForegroundWindow() );
::MessageBox(NULL, steamError.szDesc, "Half-Life FileSystem_Steam Error", MB_OK);
#endif
_exit( -1 );
}
}
else
{
m_bSelfMounted = true;
}
return retval;
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
FILE *CFileSystem_Steam::FS_fopen( const char *filenameT, const char *options, unsigned flags, int64 *size, CFileLoadInfo *pInfo )
{
char filename[MAX_PATH];
FixUpPath ( filenameT, filename, sizeof( filename ) );
// make sure the file is immediately available
if (m_bAssertFilesImmediatelyAvailable && !m_bCurrentlyLoading)
{
if (!IsFileImmediatelyAvailable(filename))
{
Msg("Steam FS: '%s' not immediately available when not in loading dialog", filename);
}
}
if ( !steam )
{
AssertMsg( 0, "CFileSystem_Steam::FS_fopen used with null steam interface!" );
return NULL;
}
CFileLoadInfo dummyInfo;
if ( !pInfo )
{
dummyInfo.m_bSteamCacheOnly = false;
pInfo = &dummyInfo;
}
SteamHandle_t f = 0;
#ifdef POSIX
FILE *pFile = NULL;
bool bWriteable = false;
if ( strchr(options,'w') || strchr(options,'a') )
bWriteable = true;
if ( bWriteable )
{
pFile = fopen( filename, options );
if (pFile && size)
{
// todo: replace with filelength()?
struct _stat buf;
int rt = _stat( filename, &buf );
if (rt == 0)
{
*size = buf.st_size;
}
}
if ( pFile )
{
// Win32 has an undocumented feature that is serialized ALL writes to a file across threads (i.e only 1 thread can open a file at a time)
// so use flock here to mimic that behavior
ThreadId_t curThread = ThreadGetCurrentId();
{
CThreadFastMutex Locklock;
AUTO_LOCK( Locklock );
int fd = fileno_unlocked( pFile );
int iLockID = m_LockedFDMap.Find( fd );
int ret = flock( fd, LOCK_EX | LOCK_NB );
if ( ret < 0 )
{
if ( errno == EWOULDBLOCK )
{
if ( iLockID != m_LockedFDMap.InvalidIndex() &&
m_LockedFDMap[iLockID] != -1 &&
curThread != m_LockedFDMap[iLockID] )
{
ret = flock( fd, LOCK_EX );
if ( ret < 0 )
{
fclose( pFile );
return NULL;
}
}
}
else
{
fclose( pFile );
return NULL;
}
}
if ( iLockID != m_LockedFDMap.InvalidIndex() )
m_LockedFDMap[iLockID] = curThread;
else
m_LockedFDMap.Insert( fd, curThread );
}
rewind( pFile );
}
}
else
{
#endif
TSteamError steamError;
unsigned int fileSize;
int bLocal = 0;
f = steam->OpenFileEx(filename, options, pInfo->m_bSteamCacheOnly, &fileSize, &bLocal, &steamError);
pInfo->m_bLoadedFromSteamCache = (bLocal == 0);
if (size)
{
*size = fileSize;
}
CheckError( f, steamError );
#ifdef POSIX
}
if ( f || pFile )
{
CSteamFile *steamFile = new CSteamFile( pFile ? (SteamHandle_t)pFile : f, bWriteable, filename );
f = (SteamHandle_t)steamFile;
}
#endif
return (FILE *)f;
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
void CFileSystem_Steam::FS_setbufsize( FILE *fp, unsigned nBytes )
{
}
//-----------------------------------------------------------------------------
// Purpose: steam call, unnecessary in stdio
//-----------------------------------------------------------------------------
WaitForResourcesHandle_t CFileSystem_Steam::WaitForResources( const char *resourcelist )
{
char szResourceList[MAX_PATH];
Q_strncpy( szResourceList, resourcelist, sizeof(szResourceList) );
Q_DefaultExtension( szResourceList, ".lst", sizeof(szResourceList) );
// cancel any old call
TSteamError steamError;
m_hWaitForResourcesCallHandle = steam->WaitForResources(szResourceList, &steamError);
if (steamError.eSteamError == eSteamErrorNone)
{
// return a new call handle
return (WaitForResourcesHandle_t)(++m_iCurrentReturnedCallHandle);
}
Msg("SteamWaitForResources() failed: %s\n", steamError.szDesc);
return (WaitForResourcesHandle_t)FILESYSTEM_INVALID_HANDLE;
}
//-----------------------------------------------------------------------------
// Purpose: steam call, unnecessary in stdio
//-----------------------------------------------------------------------------
bool CFileSystem_Steam::GetWaitForResourcesProgress( WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */ )
{
// clear the input
*progress = 0.0f;
*complete = true;
// check to see if they're using an old handle
if (m_iCurrentReturnedCallHandle != handle)
return false;
if (m_hWaitForResourcesCallHandle == STEAM_INVALID_CALL_HANDLE)
return false;
// get the progress
TSteamError steamError;
TSteamProgress steamProgress;
int result = steam->ProcessCall(m_hWaitForResourcesCallHandle, &steamProgress, &steamError);
if (result && steamError.eSteamError == eSteamErrorNone)
{
// we've finished successfully
m_hWaitForResourcesCallHandle = STEAM_INVALID_CALL_HANDLE;
*complete = true;
*progress = 1.0f;
return true;
}
else if (steamError.eSteamError != eSteamErrorNotFinishedProcessing)
{
// we have an error, just call it done
m_hWaitForResourcesCallHandle = STEAM_INVALID_CALL_HANDLE;
Msg("SteamProcessCall(SteamWaitForResources()) failed: %s\n", steamError.szDesc);
return false;
}
// return the progress
if (steamProgress.bValid)
{
*progress = (float)steamProgress.uPercentDone / (100.0f * STEAM_PROGRESS_PERCENT_SCALE);
}
else
{
*progress = 0;
}
*complete = false;
return (steamProgress.bValid != false);
}
//-----------------------------------------------------------------------------
// Purpose: steam call, unnecessary in stdio
//-----------------------------------------------------------------------------
void CFileSystem_Steam::CancelWaitForResources( WaitForResourcesHandle_t handle )
{
// check to see if they're using an old handle
if (m_iCurrentReturnedCallHandle != handle)
return;
if (m_hWaitForResourcesCallHandle == STEAM_INVALID_CALL_HANDLE)
return;
TSteamError steamError;
steam->AbortCall(m_hWaitForResourcesCallHandle, &steamError);
m_hWaitForResourcesCallHandle = STEAM_INVALID_CALL_HANDLE;
}
//-----------------------------------------------------------------------------
// Purpose: helper for posix file handle wrapper
//-----------------------------------------------------------------------------
#ifdef POSIX
FILE *GetFileHandle( CSteamFile *steamFile )
{
if ( !steamFile )
return NULL;
return (FILE *)steamFile->Handle();
}
bool BWriteable( CSteamFile *steamFile )
{
return steamFile && steamFile->BWriteable();
}
#endif
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
void CFileSystem_Steam::FS_fclose( FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
int fd = fileno_unlocked( fp );
fflush( fp );
flock( fd, LOCK_UN );
int iLockID = m_LockedFDMap.Find( fd );
if ( iLockID != m_LockedFDMap.InvalidIndex() )
m_LockedFDMap[ iLockID ] = -1;
fclose( fp );
}
else
{
#endif
TSteamError steamError;
steam->CloseFile((SteamHandle_t)fp, &steamError);
CheckError( (SteamHandle_t)fp, steamError );
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
void CFileSystem_Steam::FS_fseek( FILE *fp, int64 pos, int seekType )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
fseek( fp, pos, seekType );
}
else
{
#endif
TSteamError steamError;
int result;
result = steam->SeekFile((SteamHandle_t)fp, (int32)pos, (ESteamSeekMethod)seekType, &steamError);
CheckError((SteamHandle_t)fp, steamError);
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
long CFileSystem_Steam::FS_ftell( FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return ftell(fp);
}
else
{
#endif
long steam_offset;
TSteamError steamError;
steam_offset = steam->TellFile((SteamHandle_t)fp, &steamError);
if ( steamError.eSteamError != eSteamErrorNone )
{
CheckError((SteamHandle_t)fp, steamError);
return -1L;
}
return steam_offset;
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
int CFileSystem_Steam::FS_feof( FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return feof(fp);
}
else
{
#endif
long orig_pos;
// Figure out where in the file we currently are...
orig_pos = FS_ftell(fp);
if ( (SteamHandle_t)fp == g_pLastErrorFile && g_tLastError.eSteamError == eSteamErrorEOF )
return 1;
if ( g_tLastError.eSteamError != eSteamErrorNone )
return 0;
// Jump to the end...
FS_fseek(fp, 0L, SEEK_END);
// If we were already at the end, return true
if ( orig_pos == FS_ftell(fp) )
return 1;
// Otherwise, go back to the original spot and return false.
FS_fseek(fp, orig_pos, SEEK_SET);
return 0;
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
size_t CFileSystem_Steam::FS_fread( void *dest, size_t destSize, size_t size, FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return fread( dest, 1, size, fp );
}
else
{
#endif
TSteamError steamError;
int blocksRead = steam->ReadFile(dest, 1, size, (SteamHandle_t)fp, &steamError);
CheckError((SteamHandle_t)fp, steamError);
return blocksRead; // steam reads in atomic blocks of "size" bytes
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
size_t CFileSystem_Steam::FS_fwrite( const void *src, size_t size, FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
#define WRITE_CHUNK (256 * 1024)
if ( size > WRITE_CHUNK )
{
size_t remaining = size;
const byte* current = (const byte *) src;
size_t total = 0;
while ( remaining > 0 )
{
size_t bytesToCopy = min(remaining, WRITE_CHUNK);
total += fwrite(current, 1, bytesToCopy, fp);
remaining -= bytesToCopy;
current += bytesToCopy;
}
Assert( total == size );
return total;
}
return fwrite(src, 1, size, fp);// return number of bytes written (because we have size = 1, count = bytes, so it return bytes)
}
else
{
#endif
TSteamError steamError;
int result = steam->WriteFile(src, 1, size, (SteamHandle_t)fp, &steamError);
CheckError((SteamHandle_t)fp, steamError);
return result;
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
size_t CFileSystem_Steam::FS_vfprintf( FILE *fp, const char *fmt, va_list list )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return vfprintf(fp, fmt, list);
}
else
{
#endif
int blen, plen;
char *buf;
if ( !fp || !fmt )
return 0;
// Open the null device...used by vfprintf to determine the length of
// the formatted string.
FILE *nullDeviceFP = fopen("nul:", "w");
if ( !nullDeviceFP )
return 0;
// Figure out how long the formatted string will be...dump formatted
// string to the bit bucket.
blen = vfprintf(nullDeviceFP, fmt, list);
fclose(nullDeviceFP);
if ( !blen )
{
return 0;
}
// Get buffer in which to build the formatted string.
buf = (char *)malloc(blen+1);
if ( !buf )
{
return 0;
}
// Build the formatted string.
plen = _vsnprintf(buf, blen, fmt, list);
va_end(list);
if ( plen != blen )
{
free(buf);
return 0;
}
buf[ blen ] = 0;
// Write out the formatted string.
if ( plen != (int)FS_fwrite(buf, plen, fp) )
{
free(buf);
return 0;
}
free(buf);
return plen;
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
int CFileSystem_Steam::FS_ferror( FILE *fp )
{
if (fp)
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return ferror(fp);
}
else
{
#endif
if ((SteamHandle_t)fp != g_pLastErrorFile)
{
// it's asking for an error for a previous file, return no error
return 0;
}
return ( g_tLastError.eSteamError != eSteamErrorNone );
#ifdef POSIX
}
#endif
}
return g_tLastErrorNoFile.eSteamError != eSteamErrorNone;
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
int CFileSystem_Steam::FS_fflush( FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return fflush(fp);
}
else
{
#endif
TSteamError steamError;
int result = steam->FlushFile((SteamHandle_t)fp, &steamError);
CheckError((SteamHandle_t)fp, steamError);
return result;
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
char *CFileSystem_Steam::FS_fgets( char *dest, int destSize, FILE *fp )
{
#ifdef POSIX
CSteamFile *steamFile = (CSteamFile *)fp;
fp = GetFileHandle( steamFile );
if ( BWriteable( steamFile ) )
{
return fgets(dest, destSize, fp);
}
else
{
#endif
unsigned char c;
int numCharRead = 0;
// Read at most n chars from the file or until a newline
*dest = c = '\0';
while ( (numCharRead < destSize-1) && (c != '\n') )
{
// Read in the next char...
if ( FS_fread(&c, 1, 1, fp) != 1 )
{
if ( g_tLastError.eSteamError != eSteamErrorEOF || numCharRead == 0 )
{
return NULL; // If we hit an error, return NULL.
}
numCharRead = destSize; // Hit EOF, no more to read, all done...
}
else
{
*dest++ = c; // add the char to the string and point to the next pos
*dest = '\0'; // append NULL
numCharRead++; // count the char read
}
}
return dest; // Has a NULL termination...
#ifdef POSIX
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
int CFileSystem_Steam::FS_stat( const char *path, struct _stat *buf, bool *pbLoadedFromSteamCache )
{
TSteamElemInfo Info;
TSteamError steamError;
if ( pbLoadedFromSteamCache )
*pbLoadedFromSteamCache = false;
if ( !steam )
{
// The dedicated server gets here once at startup. When setting up the executable path before loading
// base modules like engine.dll, the filesystem looks for zipX.zip but we haven't mounted steam content
// yet so steam is null.
#if !defined( DEDICATED )
AssertMsg( 0, "CFileSystem_Steam::FS_stat used with null steam interface!" );
#endif
return -1;
}
memset(buf, 0, sizeof(struct _stat));
int returnVal= steam->Stat(path, &Info, &steamError);
if ( returnVal == 0 )
{
if (Info.bIsDir )
{
buf->st_mode |= _S_IFDIR;
buf->st_size = 0;
}
else
{
if ( pbLoadedFromSteamCache )
*pbLoadedFromSteamCache = ( Info.bIsLocal == 0 );
// Now we want to know if it's writable or not. First see if there is a local copy.
struct _stat testBuf;
int rt = _stat( path, &testBuf );
if ( rt == 0 )
{
// Ok, there's a local copy. Now check if the copy on our HD is writable.
if ( testBuf.st_mode & _S_IWRITE )
buf->st_mode |= _S_IWRITE;
}
buf->st_mode |= _S_IFREG;
buf->st_size = Info.uSizeOrCount;
}
buf->st_atime = Info.lLastAccessTime;
buf->st_mtime = Info.lLastModificationTime;
buf->st_ctime = Info.lCreationTime;
}
CheckError(NULL, steamError);
return returnVal;
}
#ifdef _WIN32
#include <io.h>
#endif
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
int CFileSystem_Steam::FS_chmod( const char *path, int pmode )
{
return _chmod( path, pmode );
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
HANDLE CFileSystem_Steam::FS_FindFirstFile(const char *findname, WIN32_FIND_DATA *dat)
{
TSteamElemInfo steamFindInfo;
HANDLE hResult = INVALID_HANDLE_VALUE;
SteamHandle_t steamResult;
TSteamError steamError;
steamResult = steam->FindFirst(findname, eSteamFindAll, &steamFindInfo, &steamError);
CheckError(NULL, steamError);
if ( steamResult == STEAM_INVALID_HANDLE )
{
hResult = INVALID_HANDLE_VALUE;
}
else
{
hResult = (HANDLE)steamResult;
strcpy(dat->cFileName, steamFindInfo.cszName);
// NEED TO DEAL WITH THIS STUFF!!! FORTUNATELY HALF-LIFE DOESN'T USE ANY OF IT
// AND ARCANUM USES _findfirst() etc.
//
// findInfo->ftLastWriteTime = steamFindInfo.lLastModificationTime;
// findInfo->ftCreationTime = steamFindInfo.lCreationTime;
// findInfo->ftLastAccessTime = steamFindInfo.lLastAccessTime;
// findInfo->nFileSizeHigh = ;
// findInfo->nFileSizeLow = ;
// Determine if the found object is a directory...
if ( steamFindInfo.bIsDir )
dat->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
else
dat->dwFileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
// Determine if the found object was local or remote.
// ***NOTE*** we are hijacking the FILE_ATTRIBUTE_OFFLINE bit and using it in a different
// (but similar) manner than the WIN32 documentation indicates ***NOTE***
if ( steamFindInfo.bIsLocal )
dat->dwFileAttributes &= ~FILE_ATTRIBUTE_OFFLINE;
else
dat->dwFileAttributes |= FILE_ATTRIBUTE_OFFLINE;
}
return hResult;
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
bool CFileSystem_Steam::FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat)
{
TSteamElemInfo steamFindInfo;
bool result;
TSteamError steamError;
result = (steam->FindNext((SteamHandle_t)handle, &steamFindInfo, &steamError) == 0);
CheckError(NULL, steamError);
if ( result )
{
strcpy(dat->cFileName, steamFindInfo.cszName);
if ( steamFindInfo.bIsDir )
dat->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
else
dat->dwFileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
}
return result;
}
//-----------------------------------------------------------------------------
// Purpose: low-level filesystem wrapper
//-----------------------------------------------------------------------------
bool CFileSystem_Steam::FS_FindClose(HANDLE handle)
{
TSteamError steamError;
int result = (steam->FindClose((SteamHandle_t)handle, &steamError) == 0);
CheckError(NULL, steamError);
return result != 0;
}
//-----------------------------------------------------------------------------
// Purpose: files are always immediately available on disk
//-----------------------------------------------------------------------------
bool CFileSystem_Steam::IsFileImmediatelyAvailable(const char *pFileName)
{
TSteamError steamError;
return (steam->IsFileImmediatelyAvailable(pFileName, &steamError) != 0);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFileSystem_Steam::GetLocalCopy( const char *pFileName )
{
// Now try to find the dll under Steam so we can do a GetLocalCopy() on it
TSteamError steamError;
/*
#ifdef WIN32
struct _stat StatBuf;
if ( FS_stat(pFileName, &StatBuf) == -1 )
{
// Use the environment search path to try and find it
char* pPath = getenv("PATH");
// Use the .EXE name to determine the root directory
char srchPath[ MAX_PATH ];
#ifdef WIN32
HINSTANCE hInstance = ( HINSTANCE )GetModuleHandle( 0 );
if ( !GetModuleFileName( hInstance, srchPath, MAX_PATH ) )
{
::MessageBox( 0, "Failed calling GetModuleFileName", "Half-Life Steam Filesystem Error", MB_OK );
return;
}
#else
srchPath[0] = '.';
srchPath[1] = '\0';
#endif
// Get the length of the root directory the .exe is in
char* pSeperator = strrchr( srchPath, CORRECT_PATH_SEPARATOR );
int nBaseLen = 0;
if ( pSeperator )
{
nBaseLen = pSeperator - srchPath;
}
// Extract each section of the path
char* pStart = pPath;
char* pEnd = 0;
bool bSearch = true;
while ( bSearch )
{
#ifdef WIN32
#define PATH_SEP ";"
#else
#define PATH_SEP ":"
#endif
pEnd = strstr( pStart, PATH_SEP );
int nSize = pEnd - pStart;
if ( !pEnd )
{
bSearch = false;
// If pEnd is NULL then nSize will be rubbish, so calculate
// it sensibly.
nSize = strlen( pStart );
}
// Is this path even potentially in the base directory?
if ( nSize > nBaseLen )
{
// Create a new path (relative to the base directory) by stripping off
// nBaseLen characters and therefore doing FS_stat relative to the current
// directory, which should be the base directory.
Assert( sizeof(srchPath) > nBaseLen + strlen(pFileName) + 2 );
nSize -= nBaseLen+1;
memcpy( srchPath, pStart+nBaseLen+1, nSize );
memcpy( srchPath+nSize, pFileName, strlen(pFileName)+1 );
// If the path starts with a directory separator then we won't get a
// relative path, so skip the check.
if ( srchPath[0] != CORRECT_PATH_SEPARATOR )
{
if ( FS_stat(srchPath, &StatBuf) == 0 )
{
steam->GetLocalFileCopy(srchPath, &steamError);
break;
}
}
}
pStart = pEnd+1;
}
}
else
#endif
*/
{
// Convert _srv.so to .so...
const char *pDllStringExtension = V_GetFileExtension( DLL_EXT_STRING );
const char *pModuleExtension = pDllStringExtension ? ( pDllStringExtension - 1 ) : DLL_EXT_STRING;
// If we got an extension, and this filename has it, then check if it's loaded.
if( pModuleExtension && V_stristr( pFileName, pModuleExtension ) )
{
// We can't be copying files over the top of .so files if they're already loaded
// in memory. mmap2( ... MAP_PRIVATE ... ) says "it is unspecified whether changes
// made to the file after the mmap() call are visible in the mapped region." Testing
// and lots of debugging (thanks Pierre-Loup!) has shown that they are, in fact,
// blasted right over your nicely loaded and fixed up object.
CSysModule *module = Sys_LoadModule( pFileName, SYS_NOLOAD );
if( module )
{
// Sys_LoadModule( SYS_NOLOAD ) increments the refcount, so bump that back down.
Sys_UnloadModule( module );
return;
}
}
steam->GetLocalFileCopy(pFileName, &steamError);
}
}
//-----------------------------------------------------------------------------
// Purpose: Load a DLL
// Input : *path
//-----------------------------------------------------------------------------
CSysModule * CFileSystem_Steam::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly )
{
char szNewPath[ MAX_PATH ];
CBaseFileSystem::ParsePathID( pFileName, pPathID, szNewPath );
// File must end in .dll
char szExtension[] = DLL_EXT_STRING;
Assert( Q_strlen(pFileName) < sizeof(szNewPath) );
Q_strncpy( szNewPath, pFileName, sizeof( szNewPath ) );
if ( !Q_stristr(szNewPath, szExtension) )
{
Assert( strlen(pFileName) + sizeof(szExtension) < sizeof(szNewPath) );
Q_strncat( szNewPath, szExtension, sizeof( szNewPath ), COPY_ALL_CHARACTERS );
}
LogFileAccess( szNewPath );
if ( !pPathID )
{
pPathID = "EXECUTABLE_PATH"; // default to the bin dir
}
CUtlSymbol lookup = g_PathIDTable.AddString( pPathID );
// a pathID has been specified, find the first match in the path list
int c = m_SearchPaths.Count();
for (int i = 0; i < c; i++)
{
// pak files are not allowed to be written to...
if (m_SearchPaths[i].GetPackFile())
continue;
if ( m_SearchPaths[i].GetPathID() == lookup )
{
char newPathName[MAX_PATH];
Q_snprintf( newPathName, sizeof(newPathName), "%s%s", m_SearchPaths[i].GetPathString(), szNewPath ); // append the path to this dir.
// make sure the file exists, and is in the Steam cache
if ( bValidatedDllOnly && !IsFileInSteamCache(newPathName) )
continue;
// Get a local copy from Steam
bool bGetLocalCopy = true;
if ( m_bSDKToolMode )
bGetLocalCopy = false;
#ifdef _WIN32
if ( IsDebuggerPresent() )
bGetLocalCopy = false;
#endif
if ( bGetLocalCopy )
GetLocalCopy( newPathName );
CSysModule *module = Sys_LoadModule( newPathName );
if ( module ) // we found the binary in one of our search paths
{
if ( bValidatedDllOnly && !IsFileInSteamCache2(newPathName) )
{
return NULL;
}
else
{
return module;
}
}
}
}
if ( bValidatedDllOnly && IsFileInSteamCache(szNewPath) )
{
// couldn't load it from any of the search paths, let LoadLibrary try
return Sys_LoadModule( szNewPath );
}
return NULL;
}
void CFileSystem_Steam::ViewSteamCache(const char* szDir, bool bRecurse)
{
TSteamElemInfo info;
TSteamError error;
char szPath[MAX_PATH];
V_snprintf( szPath, sizeof(szPath),"%s%c*.*", szDir, CORRECT_PATH_SEPARATOR );
SteamHandle_t h = steam->FindFirst( szPath, eSteamFindRemoteOnly, &info, &error );
int ret = 0;
if ( h != STEAM_INVALID_HANDLE )
{
do
{
Msg( "View Steam Cache: '%s%c%s' \n", szDir, CORRECT_PATH_SEPARATOR, info.cszName );
if ( bRecurse && info.bIsDir && (0 == V_stristr( info.cszName, "." ) ) )
{
V_snprintf( szPath, sizeof(szPath),"%s%c%s", szDir, CORRECT_PATH_SEPARATOR, info.cszName );
ViewSteamCache( szPath, true );
}
ret = steam->FindNext( h, &info, &error );
} while( 0 == ret );
steam->FindClose( h, &error );
}
}
// HACK HACK - to allow IsFileInSteamCache() to use the old C exported interface
extern "C" SteamHandle_t SteamFindFirst( const char *cszPattern, ESteamFindFilter eFilter, TSteamElemInfo *pFindInfo, TSteamError *pError );
extern "C" int SteamFindClose( SteamHandle_t hDirectory, TSteamError *pError );
//-----------------------------------------------------------------------------
// Purpose: returns true if the file exists and is in a mounted Steam cache
//-----------------------------------------------------------------------------
bool CFileSystem_Steam::IsFileInSteamCache( const char *file )
{
if ( !m_bContentLoaded || m_bSDKToolMode )
{
return true;
}
// see if the file exists
TSteamElemInfo info;
TSteamError error;
SteamHandle_t h = steam->FindFirst( file, eSteamFindRemoteOnly, &info, &error );
if ( h == STEAM_INVALID_HANDLE )
{
return false;
}
else
{
steam->FindClose( h, &error );
}
return true;
}
int CFileSystem_Steam::HintResourceNeed( const char *hintlist, int forgetEverything )
{
TSteamError steamError;
int result = steam->HintResourceNeed( hintlist, forgetEverything, &steamError );
CheckError(NULL, steamError);
return result;
}