|
|
//====== Copyright 1996-2005, 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"
#include "tier0/vprof.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
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
#ifdef _WIN32
DECLSPEC_IMPORT BOOL WINAPI IsDebuggerPresent(void); #else
static bool IsDebuggerPresent( void ) { return false; } #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 ); 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", szSteamDLLPath, Q_ARRAYSIZE(szSteamDLLPath) ); #elif defined(OSX)
V_ComposeFileName( pchSteamInstallPath, "libsteam.dylib", szSteamDLLPath, Q_ARRAYSIZE(szSteamDLLPath) ); #elif defined(LINUX)
V_ComposeFileName( pchSteamInstallPath, "libsteam.so", 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" ); #elif defined(OSX)
m_hSteamDLL = (HMODULE)Sys_LoadModule( "libsteam.dylib" ); #elif defined(LINUX)
m_hSteamDLL = (HMODULE)Sys_LoadModule( "libsteam.so" ); #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 *filename, const char *options, unsigned flags, int64 *size, CFileLoadInfo *pInfo ) { // 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 */ ) { VPROF_BUDGET( "GetWaitForResourcesProgress (steam)", VPROF_BUDGETGROUP_STEAM );
// 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 ) { TSteamElemInfo Info; TSteamError steamError;
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 { // 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 ( pStart && bSearch ) { #ifdef WIN32
#define PATH_SEP ";"
#else
#define PATH_SEP ":"
#endif
pEnd = strstr( pStart, PATH_SEP ); if ( !pEnd ) bSearch = false;
int nSize = pEnd - pStart; // Is this path even in the base directory?
if ( nSize > nBaseLen ) { // Create a new path (relative to 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 ( FS_stat(srchPath, &StatBuf) == 0 ) { steam->GetLocalFileCopy(srchPath, &steamError); break; } } pStart = pEnd+1; } } else #endif
{ 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; if ( IsDebuggerPresent() ) bGetLocalCopy = false; 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; }
|