diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /filesystem/filesystem_stdio.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'filesystem/filesystem_stdio.cpp')
| -rw-r--r-- | filesystem/filesystem_stdio.cpp | 1606 |
1 files changed, 1606 insertions, 0 deletions
diff --git a/filesystem/filesystem_stdio.cpp b/filesystem/filesystem_stdio.cpp new file mode 100644 index 0000000..43916fe --- /dev/null +++ b/filesystem/filesystem_stdio.cpp @@ -0,0 +1,1606 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifdef _WIN32 +#include <io.h> +#include <fcntl.h> +#endif + +#include "basefilesystem.h" +#include "packfile.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" +#ifdef _WIN32 +#include "tier0/tslist.h" +#elif defined(POSIX) +#include <fcntl.h> +#ifdef LINUX +#include <sys/file.h> +#endif +#endif +#include "tier1/convar.h" +#include "tier0/vcrmode.h" +#include "tier0/vprof.h" +#include "tier1/fmtstr.h" +#include "tier1/utlrbtree.h" +#include "vstdlib/osversion.h" + +#ifdef _X360 +#undef WaitForSingleObject +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ASSERT_INVARIANT( SEEK_CUR == FILESYSTEM_SEEK_CURRENT ); +ASSERT_INVARIANT( SEEK_SET == FILESYSTEM_SEEK_HEAD ); +ASSERT_INVARIANT( SEEK_END == FILESYSTEM_SEEK_TAIL ); + +//----------------------------------------------------------------------------- + +class CFileSystem_Stdio : public CBaseFileSystem +{ +public: + CFileSystem_Stdio(); + ~CFileSystem_Stdio(); + + // Used to get at older versions + 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 bool IsFileImmediatelyAvailable(const char *pFileName); + 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 false; } + virtual FilesystemMountRetval_t MountSteamContent( int nExtraAppId = -1 ) { return FILESYSTEM_MOUNT_OK; } + + bool GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign ); + void *AllocOptimalReadBuffer( FileHandle_t hFile, unsigned nSize, unsigned nOffset ); + void FreeOptimalReadBuffer( void *p ); + +protected: + // implementation of CBaseFileSystem virtual functions + virtual FILE *FS_fopen( const char *filename, const char *options, unsigned flags, int64 *size ); + 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 bool FS_setmode( FILE *fp, FileMode_t mode ); + 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); + virtual int FS_GetSectorSize( FILE * ); + +private: + bool CanAsync() const + { + return m_bCanAsync; + } + + bool m_bMounted; + bool m_bCanAsync; +}; + + +//----------------------------------------------------------------------------- +// Per-file worker classes +//----------------------------------------------------------------------------- +abstract_class CStdFilesystemFile +{ +public: + virtual ~CStdFilesystemFile() {} + virtual void FS_setbufsize( unsigned nBytes ) = 0; + virtual void FS_fclose() = 0; + virtual void FS_fseek( int64 pos, int seekType ) = 0; + virtual long FS_ftell() = 0; + virtual int FS_feof() = 0; + virtual size_t FS_fread( void *dest, size_t destSize, size_t size ) = 0; + virtual size_t FS_fwrite( const void *src, size_t size ) = 0; + virtual bool FS_setmode( FileMode_t mode ) = 0; + virtual size_t FS_vfprintf( const char *fmt, va_list list ) = 0; + virtual int FS_ferror() = 0; + virtual int FS_fflush() = 0; + virtual char *FS_fgets( char *dest, int destSize ) = 0; + virtual int FS_GetSectorSize() { return 1; } +}; + +//--------------------------------------------------------- + +class CStdioFile : public CStdFilesystemFile +{ +public: + static CStdioFile *FS_fopen( const char *filename, const char *options, int64 *size ); + + virtual void FS_setbufsize( unsigned nBytes ); + virtual void FS_fclose(); + virtual void FS_fseek( int64 pos, int seekType ); + virtual long FS_ftell(); + virtual int FS_feof(); + virtual size_t FS_fread( void *dest, size_t destSize, size_t size); + virtual size_t FS_fwrite( const void *src, size_t size ); + virtual bool FS_setmode( FileMode_t mode ); + virtual size_t FS_vfprintf( const char *fmt, va_list list ); + virtual int FS_ferror(); + virtual int FS_fflush(); + virtual char *FS_fgets( char *dest, int destSize ); + +#ifdef POSIX + static CUtlMap< ino_t, CThreadMutex * > m_LockedFDMap; + static CThreadMutex m_MutexLockedFD; +#endif +private: + CStdioFile( FILE *pFile, bool bWriteable ) + : m_pFile( pFile ), m_bWriteable( bWriteable ) + { + } + + FILE *m_pFile; + bool m_bWriteable; +}; + +#ifdef POSIX +CUtlMap< ino_t, CThreadMutex * > CStdioFile::m_LockedFDMap; +CThreadMutex CStdioFile::m_MutexLockedFD; +#endif + +//----------------------------------------------------------------------------- + +#ifdef _WIN32 +class CWin32ReadOnlyFile : public CStdFilesystemFile +{ +public: + static bool CanOpen( const char *filename, const char *options ); + static CWin32ReadOnlyFile *FS_fopen( const char *filename, const char *options, int64 *size ); + + virtual void FS_setbufsize( unsigned nBytes ) {} + virtual void FS_fclose(); + virtual void FS_fseek( int64 pos, int seekType ); + virtual long FS_ftell(); + virtual int FS_feof(); + virtual size_t FS_fread( void *dest, size_t destSize, size_t size); + virtual size_t FS_fwrite( const void *src, size_t size ) { return 0; } + virtual bool FS_setmode( FileMode_t mode ) { Error( "Can't set mode, open a second file in right mode\n" ); return false; } + virtual size_t FS_vfprintf( const char *fmt, va_list list ) { return 0; } + virtual int FS_ferror() { return 0; } + virtual int FS_fflush() { return 0; } + virtual char *FS_fgets( char *dest, int destSize ); + virtual int FS_GetSectorSize() { return m_SectorSize; } + +private: + CWin32ReadOnlyFile( HANDLE hFileUnbuffered, HANDLE hFileBuffered, int sectorSize, int64 fileSize, bool bOverlapped ) + : m_hFileUnbuffered( hFileUnbuffered ), + m_hFileBuffered( hFileBuffered ), + m_ReadPos( 0 ), + m_Size( fileSize ), + m_SectorSize( sectorSize ), + m_bOverlapped( bOverlapped ) + { + } + + int64 m_ReadPos; + int64 m_Size; + HANDLE m_hFileUnbuffered; + HANDLE m_hFileBuffered; + CThreadFastMutex m_Mutex; + int m_SectorSize; + bool m_bOverlapped; +}; + +#endif + +//----------------------------------------------------------------------------- +// singleton +//----------------------------------------------------------------------------- +CFileSystem_Stdio g_FileSystem_Stdio; +#if defined(_WIN32) && defined(DEDICATED) +CBaseFileSystem *BaseFileSystem_Stdio( void ) +{ + return &g_FileSystem_Stdio; +} +#endif + +#ifdef DEDICATED // "hack" to allow us to not export a stdio version of the FILESYSTEM_INTERFACE_VERSION anywhere + +IFileSystem *g_pFileSystem = &g_FileSystem_Stdio; +IBaseFileSystem *g_pBaseFileSystem = &g_FileSystem_Stdio; + +#else + +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Stdio, IFileSystem, FILESYSTEM_INTERFACE_VERSION, g_FileSystem_Stdio ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Stdio, IBaseFileSystem, BASEFILESYSTEM_INTERFACE_VERSION, g_FileSystem_Stdio ); + +#endif + +//----------------------------------------------------------------------------- + +#ifndef _RETAIL +bool UseOptimalBufferAllocation() +{ + static bool bUseOptimalBufferAllocation = ( IsX360() || ( !IsLinux() && Q_stristr( Plat_GetCommandLine(), "-unbuffered_io" ) != NULL ) ); + return bUseOptimalBufferAllocation; +} +ConVar filesystem_unbuffered_io( "filesystem_unbuffered_io", "1", 0, "" ); +#define UseUnbufferedIO() ( UseOptimalBufferAllocation() && filesystem_unbuffered_io.GetBool() ) +#else +#define UseUnbufferedIO() true +#endif + +ConVar filesystem_native( "filesystem_native", "1", 0, "Use native FS or STDIO" ); +ConVar filesystem_max_stdio_read( "filesystem_max_stdio_read", IsX360() ? "64" : "16", 0, "" ); +ConVar filesystem_report_buffered_io( "filesystem_report_buffered_io", "0" ); + +//----------------------------------------------------------------------------- +// constructor +//----------------------------------------------------------------------------- +CFileSystem_Stdio::CFileSystem_Stdio() +{ + m_bMounted = false; + m_bCanAsync = true; +#ifdef POSIX + SetDefLessFunc( CStdioFile::m_LockedFDMap ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFileSystem_Stdio::~CFileSystem_Stdio() +{ +#ifdef POSIX + FOR_EACH_MAP_FAST( CStdioFile::m_LockedFDMap, i ) + { + Assert( CStdioFile::m_LockedFDMap[ i ] ); + delete CStdioFile::m_LockedFDMap[ i ]; + } + CStdioFile::m_LockedFDMap.RemoveAll(); +#endif + + Assert(!m_bMounted); +} + + +//----------------------------------------------------------------------------- +// QueryInterface: +//----------------------------------------------------------------------------- +void *CFileSystem_Stdio::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 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CFileSystem_Stdio::GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign ) +{ + unsigned sectorSize; + + if ( hFile && UseOptimalBufferAllocation() ) + { + CFileHandle *fh = ( CFileHandle *)hFile; + sectorSize = fh->GetSectorSize(); + + if ( !sectorSize || ( fh->m_pPackFileHandle && ( fh->m_pPackFileHandle->AbsoluteBaseOffset() % sectorSize ) ) ) + { + sectorSize = 1; + } + } + else + { + sectorSize = 1; + } + + if ( pOffsetAlign ) + { + *pOffsetAlign = sectorSize; + } + + if ( pSizeAlign ) + { + *pSizeAlign = sectorSize; + } + + if ( pBufferAlign ) + { + if ( IsX360() ) + { + *pBufferAlign = 4; + } + else + { + *pBufferAlign = sectorSize; + } + } + + return ( sectorSize > 1 ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void *CFileSystem_Stdio::AllocOptimalReadBuffer( FileHandle_t hFile, unsigned nSize, unsigned nOffset ) +{ + if ( !UseOptimalBufferAllocation() ) + { + return CBaseFileSystem::AllocOptimalReadBuffer( hFile, nSize, nOffset ); + } + + unsigned sectorSize; + if ( hFile != FILESYSTEM_INVALID_HANDLE ) + { + CFileHandle *fh = ( CFileHandle *)hFile; + sectorSize = fh->GetSectorSize(); + + if ( !nSize ) + { + nSize = fh->Size(); + } + + if ( fh->m_pPackFileHandle ) + { + nOffset += fh->m_pPackFileHandle->AbsoluteBaseOffset(); + } + } + else + { + // an invalid handle gets a fake "optimal" but valid buffer + // this path is for a caller that isn't doing i/o, + // but needs an "optimal" buffer that can end up passed to FreeOptimalReadBuffer() + sectorSize = 4; + } + + bool bOffsetIsAligned = ( nOffset % sectorSize == 0 ); + unsigned nAllocSize = ( bOffsetIsAligned ) ? AlignValue( nSize, sectorSize ) : nSize; + + if ( IsX360() ) + { + return malloc( nAllocSize ); + } + else + { + unsigned nAllocAlignment = ( bOffsetIsAligned ) ? sectorSize : 4; + return _aligned_malloc( nAllocSize, nAllocAlignment ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CFileSystem_Stdio::FreeOptimalReadBuffer( void *p ) +{ + if ( !UseOptimalBufferAllocation() ) + { + CBaseFileSystem::FreeOptimalReadBuffer( p ); + return; + } + + if ( p ) + { + if ( IsX360() ) + { + free( p ); + } + else + { + _aligned_free( p ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +FILE *CFileSystem_Stdio::FS_fopen( const char *filenameT, const char *options, unsigned flags, int64 *size ) +{ + CStdFilesystemFile *pFile = NULL; + + char filename[ MAX_PATH ]; + + CBaseFileSystem::FixUpPath ( filenameT, filename, sizeof( filename ) ); + +#ifdef _WIN32 + if ( CWin32ReadOnlyFile::CanOpen( filename, options ) ) + { + pFile = CWin32ReadOnlyFile::FS_fopen( filename, options, size ); + if ( pFile ) + { + return (FILE *)pFile; + } + } +#endif + + pFile = CStdioFile::FS_fopen( filename, options, size ); + + return (FILE *)pFile; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CFileSystem_Stdio::FS_setbufsize( FILE *fp, unsigned nBytes ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + pFile->FS_setbufsize( nBytes ); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CFileSystem_Stdio::FS_fclose( FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + + pFile->FS_fclose(); + delete pFile; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CFileSystem_Stdio::FS_fseek( FILE *fp, int64 pos, int seekType ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + + pFile->FS_fseek( pos, seekType ); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +long CFileSystem_Stdio::FS_ftell( FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_ftell(); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::FS_feof( FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_feof(); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +size_t CFileSystem_Stdio::FS_fread( void *dest, size_t destSize, size_t size, FILE *fp ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if( ThreadInMainThread() ) + { + tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesRead" ); + } + + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + size_t nBytesRead = pFile->FS_fread( dest, destSize, size); + + Trace_FRead( nBytesRead, fp ); + + return nBytesRead; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +size_t CFileSystem_Stdio::FS_fwrite( const void *src, size_t size, FILE *fp ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); + if( ThreadInMainThread() ) + { + tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesWrite" ); + } + + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + + size_t nBytesWritten = pFile->FS_fwrite(src, size); + + return nBytesWritten; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +bool CFileSystem_Stdio::FS_setmode( FILE *fp, FileMode_t mode ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_setmode( mode ); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +size_t CFileSystem_Stdio::FS_vfprintf( FILE *fp, const char *fmt, va_list list ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_vfprintf(fmt, list); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::FS_ferror( FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_ferror(); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::FS_fflush( FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_fflush(); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +char *CFileSystem_Stdio::FS_fgets( char *dest, int destSize, FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_fgets(dest, destSize); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// pmode - +// Output : int +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::FS_chmod( const char *pathT, int pmode ) +{ + if ( !pathT ) + return -1; + + char path[ MAX_PATH ]; + + CBaseFileSystem::FixUpPath ( pathT, path, sizeof( path ) ); + + int rt = _chmod( path, pmode ); +#if defined(LINUX) + if (rt==-1) + { + char caseFixedName[ MAX_PATH ]; + const bool found = findFileInDirCaseInsensitive_safe( path, caseFixedName ); + if ( found ) + { + rt=_chmod( caseFixedName, pmode ); + } + } +#endif + return rt; +} + + +//----------------------------------------------------------------------------- +// Purpose: A replacement for _stat() backed by GetFileAttributesEx for XP users +// +// Workaround for: +// https://connect.microsoft.com/VisualStudio/feedback/details/1600505/stat-not-working-on-windows-xp-using-v14-xp-platform-toolset-vs2015 +// +// This is not well tested or meant to be a proper implementation of stat(), but rather a band-aid for XP users only +// until microsoft pushes a runtime update to fix above issue :-/ +//----------------------------------------------------------------------------- +#if defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) +static int WindowsXPStatShim( const char *pathT, struct _stat *buf ) +{ + WIN32_FILE_ATTRIBUTE_DATA fileAttributes; + if ( !GetFileAttributesEx(pathT, GetFileExInfoStandard, &fileAttributes) ) + { + *_errno() = ENOENT; + return -1; + } + + memset( buf, 0, sizeof(struct _stat) ); + + // Mode + unsigned short permBits = _S_IREAD; // If GetFileAttributes let us see it, we can read it. I think. + if ( fileAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + buf->st_mode |= _S_IFDIR; + permBits |= _S_IEXEC; + } + else + { + buf->st_mode |= _S_IFREG; + + const char *pExt = V_GetFileExtension( pathT ); + if ( V_strcasecmp( pExt, "exe" ) == 0 || + V_strcasecmp( pExt, "bat" ) == 0 || + V_strcasecmp( pExt, "com" ) == 0 || + V_strcasecmp( pExt, "cmd" ) == 0 ) + { + // Windows stat seems to set this flag for these extensions + permBits |= _S_IEXEC; + } + } + + if ( fileAttributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY ) + { + permBits |= S_IWRITE; + } + + // Duplicate permission bits to user/group/world (windows stat doesn't care) + buf->st_mode |= permBits | permBits >> 3 | permBits >> 6; + + // Device is just drive-letter-index according to msdn + char driveLetter = tolower( pathT[ 0 ] ); + if ( driveLetter >= 'a' && driveLetter <= 'z' && pathT[1] == ':' ) + { + unsigned char driveIdx = driveLetter - 'a'; + buf->st_dev = driveIdx; + buf->st_rdev = driveIdx; + } + else + { + buf->st_dev = _getdrive(); + buf->st_rdev = _getdrive(); + } + + buf->st_nlink = 1; + buf->st_size = (uint64_t)fileAttributes.nFileSizeHigh << 32 | fileAttributes.nFileSizeLow; + // The 90s was a hell of a time I guess. + uint64_t actualAccessTime = (uint64_t)fileAttributes.ftLastAccessTime.dwHighDateTime << 32 | (uint64_t)fileAttributes.ftLastAccessTime.dwLowDateTime; + uint64_t actualModTime = (uint64_t)fileAttributes.ftLastWriteTime.dwHighDateTime << 32 | (uint64_t)fileAttributes.ftLastWriteTime.dwLowDateTime; + uint64_t actualCreationTime = (uint64_t)fileAttributes.ftCreationTime.dwHighDateTime << 32 | (uint64_t)fileAttributes.ftCreationTime.dwLowDateTime; + uint64_t ullMSUniverseToEveryoneElseUniverse = (369 * 365 + 89) * 86400ull; // okay + buf->st_atime = actualAccessTime / 10000000ull - ullMSUniverseToEveryoneElseUniverse; + buf->st_mtime = actualModTime / 10000000ull - ullMSUniverseToEveryoneElseUniverse; + buf->st_ctime = actualCreationTime / 10000000ull - ullMSUniverseToEveryoneElseUniverse; + + // st_uid/st_gid always 0 on windows + + return 0; +} +#endif // defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::FS_stat( const char *pathT, struct _stat *buf, bool *pbLoadedFromSteamCache ) +{ + if ( pbLoadedFromSteamCache ) + *pbLoadedFromSteamCache = false; + + if ( !pathT ) + { + return -1; + } + + char path[ MAX_PATH ]; + + CBaseFileSystem::FixUpPath ( pathT, path, sizeof( path ) ); + + int rt = _stat( path, buf ); + + // Workaround bug wherein stat() randomly fails on Windows XP. See comment on function. +#if defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) + if ( rt == -1 ) + { + EOSType eOSType = GetOSType(); + if ( eOSType == k_eWin2000 || eOSType == k_eWinXP || eOSType == k_eWin2003 ) + { + rt = WindowsXPStatShim( path, buf ); + } + } +#endif // defined(_WIN32) && defined(FILESYSTEM_MSVC2015_STAT_BUG_WORKAROUND) + +#if defined(LINUX) + if ( rt == -1 ) + { + char caseFixedName[ MAX_PATH ]; + bool found = findFileInDirCaseInsensitive_safe( path, caseFixedName ); + if ( found ) + { + rt = _stat( caseFixedName, buf ); + } + } +#endif + return rt; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +HANDLE CFileSystem_Stdio::FS_FindFirstFile(const char *findnameT, WIN32_FIND_DATA *dat) +{ + char findname[ MAX_PATH ]; + + CBaseFileSystem::FixUpPath ( findnameT, findname, sizeof( findname ) ); + + return ::FindFirstFile(findname, dat); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +bool CFileSystem_Stdio::FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat) +{ + + if (INVALID_HANDLE_VALUE == handle) // invalid handle should return false + return false; + + return (::FindNextFile(handle, dat) != 0); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +bool CFileSystem_Stdio::FS_FindClose(HANDLE handle) +{ + return (::FindClose(handle) != 0); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::FS_GetSectorSize( FILE *fp ) +{ + CStdFilesystemFile *pFile = ((CStdFilesystemFile *)fp); + return pFile->FS_GetSectorSize(); +} + +//----------------------------------------------------------------------------- +// Purpose: files are always immediately available on disk +//----------------------------------------------------------------------------- +bool CFileSystem_Stdio::IsFileImmediatelyAvailable(const char *pFileName) +{ + return true; +} + +// enable this if you want the stdio filesystem to pretend it's steam, and make people wait for resources +//#define DEBUG_WAIT_FOR_RESOURCES_API + +#if defined(DEBUG_WAIT_FOR_RESOURCES_API) +static float g_flDebugProgress = 0.0f; +#endif + +//----------------------------------------------------------------------------- +// Purpose: steam call, unnecessary in stdio +//----------------------------------------------------------------------------- +WaitForResourcesHandle_t CFileSystem_Stdio::WaitForResources( const char *resourcelist ) +{ +#if defined(DEBUG_WAIT_FOR_RESOURCES_API) + g_flDebugProgress = 0.0f; +#endif + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: steam call, unnecessary in stdio +//----------------------------------------------------------------------------- +bool CFileSystem_Stdio::GetWaitForResourcesProgress( WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */ ) +{ +#if defined(DEBUG_WAIT_FOR_RESOURCES_API) + g_flDebugProgress += 0.002f; + if (g_flDebugProgress < 1.0f) + { + *progress = g_flDebugProgress; + *complete = false; + return true; + } +#endif + + // always return that we're complete + *progress = 0.0f; + *complete = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: steam call, unnecessary in stdio +//----------------------------------------------------------------------------- +void CFileSystem_Stdio::CancelWaitForResources( WaitForResourcesHandle_t handle ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFileSystem_Stdio::GetLocalCopy( const char *pFileName ) +{ + // do nothing. . everything is local. +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CFileSystem_Stdio::HintResourceNeed( const char *hintlist, int forgetEverything ) +{ + // do nothing. . everything is local. + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +CStdioFile *CStdioFile::FS_fopen( const char *filenameT, const char *options, int64 *size ) +{ + FILE *pFile = NULL; + char *p = NULL; + char filename[MAX_PATH]; + struct _stat buf; + + V_strncpy( filename, filenameT, sizeof(filename) ); + + // stop newline characters at end of filename + p = strchr( filename, '\n' ); + if ( p ) + *p = '\0'; + p = strchr( filename, '\r' ); + if ( p ) + *p = '\0'; + + + pFile = fopen(filename, options); + if (pFile && size) + { + // todo: replace with filelength()? + int rt = _stat( filename, &buf ); + if (rt == 0) + { + *size = buf.st_size; + } + } + +#if defined(LINUX) + if(!pFile && !strchr(options,'w') && !strchr(options,'+') ) // try opening the lower cased version + { + char caseFixedName[ MAX_PATH ]; + bool found = findFileInDirCaseInsensitive_safe( filename, caseFixedName ); + if ( found ) + { + pFile = fopen( caseFixedName, options ); + + if (pFile && size) + { + // todo: replace with filelength()? + struct _stat buf; + int rt = _stat( caseFixedName, &buf ); + if (rt == 0) + { + *size = buf.st_size; + } + } + } + } +#endif + + if ( pFile ) + { + bool bWriteable = false; + if ( strchr(options,'w') || strchr(options,'a') ) + bWriteable = true; + +#if defined POSIX + if ( bWriteable ) + { + CThreadMutex *pMutex = NULL; + + { + AUTO_LOCK( m_MutexLockedFD ); + // 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 add a lock here to mimic that behavior + + int iLockID = m_LockedFDMap.Find( buf.st_ino ); + if ( iLockID != m_LockedFDMap.InvalidIndex() ) + { + pMutex = m_LockedFDMap[iLockID]; + } + else + { + CThreadMutex *newMutex = new CThreadMutex; + pMutex = m_LockedFDMap[m_LockedFDMap.Insert( buf.st_ino, newMutex )]; + } + } + // grab the lock once we have UNLOCKED m_MutexLockedFD so we don't deadlock on a close + pMutex->Lock(); + + rewind( pFile ); + + // we need to get the file size again after the lock returns + if (pFile && size) + { + int rt = _stat( filename, &buf ); + if (rt == 0) + { + *size = buf.st_size; + } + } + + } +#endif + return new CStdioFile( pFile, bWriteable ); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CStdioFile::FS_setbufsize( unsigned nBytes ) +{ +#ifdef _WIN32 + if ( nBytes ) + { + setvbuf( m_pFile, NULL, _IOFBF, 32768 ); + } + else + { + setvbuf( m_pFile, NULL, _IONBF, 0 ); +#if defined(_MSC_VER) && ( _MSC_VER < 1900 ) + // hack to make microsoft stdio not always read one stray byte on odd sized files + m_pFile->_bufsiz = 1; +#endif + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CStdioFile::FS_fclose() +{ +#ifdef POSIX + if ( m_bWriteable ) + { + AUTO_LOCK( m_MutexLockedFD ); + + struct _stat buf; + int fd = fileno_unlocked( m_pFile ); + fstat( fd, &buf ); + + fflush( m_pFile ); + int iLockID = m_LockedFDMap.Find( buf.st_ino ); + if ( iLockID != m_LockedFDMap.InvalidIndex() ) + { + m_LockedFDMap[iLockID]->Unlock(); + } + } +#endif + fclose(m_pFile); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CStdioFile::FS_fseek( int64 pos, int seekType ) +{ + fseek( m_pFile, pos, seekType ); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +long CStdioFile::FS_ftell() +{ + return ftell(m_pFile); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CStdioFile::FS_feof() +{ + return feof(m_pFile); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +size_t CStdioFile::FS_fread( void *dest, size_t destSize, size_t size ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); + if( ThreadInMainThread() ) + { + tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesRead" ); + } + + // read (size) of bytes to ensure truncated reads returns bytes read and not 0 + return fread( dest, 1, size, m_pFile ); +} + + +#define WRITE_CHUNK (256 * 1024) + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +// +// This routine breaks data into chunks if the amount to be written is beyond WRITE_CHUNK (256kb) +// Windows can fail on monolithic writes of ~12MB or more, so we work around that here +//----------------------------------------------------------------------------- +size_t CStdioFile::FS_fwrite( const void *src, size_t size ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); + if( ThreadInMainThread() ) + { + tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesWrite" ); + } + + 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, (size_t)WRITE_CHUNK); + + total += fwrite(current, 1, bytesToCopy, m_pFile); + + remaining -= bytesToCopy; + current += bytesToCopy; + } + + Assert( total == size ); + return total; + } + + return fwrite(src, 1, size, m_pFile);// return number of bytes written (because we have size = 1, count = bytes, so it return bytes) +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +bool CStdioFile::FS_setmode( FileMode_t mode ) +{ +#ifdef _WIN32 + int fd = _fileno( m_pFile ); + int newMode = ( mode == FM_BINARY ) ? _O_BINARY : _O_TEXT; + return ( _setmode( fd, newMode) != -1 ); +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +size_t CStdioFile::FS_vfprintf( const char *fmt, va_list list ) +{ + return vfprintf(m_pFile, fmt, list); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CStdioFile::FS_ferror() +{ + return ferror(m_pFile); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CStdioFile::FS_fflush() +{ + return fflush(m_pFile); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +char *CStdioFile::FS_fgets( char *dest, int destSize ) +{ + return fgets(dest, destSize, m_pFile); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#ifdef _WIN32 + +ConVar filesystem_use_overlapped_io( "filesystem_use_overlapped_io", "1", 0, "" ); +#define UseOverlappedIO() filesystem_use_overlapped_io.GetBool() + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int GetSectorSize( const char *pszFilename ) +{ + if ( ( !pszFilename[0] || !pszFilename[1] ) || + ( pszFilename[0] == '\\' && pszFilename[1] == '\\' ) || + ( pszFilename[0] == '/' && pszFilename[1] == '/' ) ) + { + // Cannot determine sector size with a UNC path (need volume identifier) + return 0; + } + + if ( IsX360() ) + { + // purposely dvd centric, which is also the worst case + return XBOX_DVD_SECTORSIZE; + } + +#if defined( _WIN32 ) && !defined( FILESYSTEM_STEAM ) && !defined( _X360 ) + char szAbsoluteFilename[MAX_FILEPATH]; + if ( pszFilename[1] != ':' ) + { + Q_MakeAbsolutePath( szAbsoluteFilename, sizeof(szAbsoluteFilename), pszFilename ); + pszFilename = szAbsoluteFilename; + } + + DWORD sectorSize = 1; + + struct DriveSectorSize_t + { + char volume; + DWORD sectorSize; + }; + + static DriveSectorSize_t cachedSizes[4]; + + char volume = tolower( *pszFilename ); + + int i; + for ( i = 0; i < ARRAYSIZE(cachedSizes) && cachedSizes[i].volume; i++ ) + { + if ( cachedSizes[i].volume == volume ) + { + sectorSize = cachedSizes[i].sectorSize; + break; + } + } + + if ( sectorSize == 1 ) + { + char root[4] = "X:\\"; + root[0] = *pszFilename; + + DWORD ignored; + if ( !GetDiskFreeSpace( root, &ignored, §orSize, &ignored, &ignored ) ) + { + sectorSize = 0; + } + + if ( i < ARRAYSIZE(cachedSizes) ) + { + cachedSizes[i].volume = volume; + cachedSizes[i].sectorSize = sectorSize; + } + } + + return sectorSize; +#else + return 0; +#endif +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +class CThreadIOEventPool +{ +public: + ~CThreadIOEventPool() + { + CThreadEvent *pEvent; + + while ( m_Events.PopItem( &pEvent ) ) + { + delete pEvent; + } + } + + CThreadEvent *GetEvent() + { + CThreadEvent *pEvent; + + if ( m_Events.PopItem( &pEvent ) ) + { + return pEvent; + } + + return new CThreadEvent; + } + + void ReleaseEvent( CThreadEvent *pEvent ) + { + m_Events.PushItem( pEvent ); + } + +private: + CTSList<CThreadEvent *> m_Events; +}; + + +CThreadIOEventPool g_ThreadIOEvents; + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CWin32ReadOnlyFile::CanOpen( const char *filename, const char *options ) +{ + return ( options[0] == 'r' && options[1] == 'b' && options[2] == 0 && filesystem_native.GetBool() ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +static HANDLE OpenWin32File( const char *filename, bool bOverlapped, bool bUnbuffered, int64 *pFileSize ) +{ + HANDLE hFile; + + DWORD createFlags = FILE_ATTRIBUTE_NORMAL; + + if ( bOverlapped ) + { + createFlags |= FILE_FLAG_OVERLAPPED; + } + + if ( bUnbuffered ) + { + createFlags |= FILE_FLAG_NO_BUFFERING; + } + + hFile = ::CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, createFlags, NULL ); + if ( hFile != INVALID_HANDLE_VALUE && !*pFileSize ) + { + LARGE_INTEGER fileSize; + if ( !GetFileSizeEx( hFile, &fileSize ) ) + { + CloseHandle( hFile ); + hFile = INVALID_HANDLE_VALUE; + } + *pFileSize = fileSize.QuadPart; + } + return hFile; +} + +CWin32ReadOnlyFile *CWin32ReadOnlyFile::FS_fopen( const char *filename, const char *options, int64 *size ) +{ + Assert( CanOpen( filename, options ) ); + + int sectorSize = 0; + bool bTryUnbuffered = ( UseUnbufferedIO() && ( sectorSize = GetSectorSize( filename ) ) != 0 ); + bool bOverlapped = UseOverlappedIO(); + + HANDLE hFileUnbuffered = INVALID_HANDLE_VALUE; + int64 fileSize = 0; + + if ( bTryUnbuffered ) + { + hFileUnbuffered = OpenWin32File( filename, bOverlapped, true, &fileSize ); + if ( hFileUnbuffered == INVALID_HANDLE_VALUE ) + { + return NULL; + } + } + + HANDLE hFileBuffered = OpenWin32File( filename, bOverlapped, false, &fileSize ); + if ( hFileBuffered == INVALID_HANDLE_VALUE ) + { + if ( hFileUnbuffered != INVALID_HANDLE_VALUE ) + { + CloseHandle( hFileUnbuffered ); + } + return NULL; + } + + if ( size ) + { + *size = fileSize; + } + + return new CWin32ReadOnlyFile( hFileUnbuffered, hFileBuffered, ( sectorSize ) ? sectorSize : 1, fileSize, bOverlapped ); +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CWin32ReadOnlyFile::FS_fclose() +{ + if ( m_hFileUnbuffered != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hFileUnbuffered ); + } + + if ( m_hFileBuffered != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hFileBuffered ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +void CWin32ReadOnlyFile::FS_fseek( int64 pos, int seekType ) +{ + switch ( seekType ) + { + case SEEK_SET: + m_ReadPos = pos; + break; + + case SEEK_CUR: + m_ReadPos += pos; + break; + + case SEEK_END: + m_ReadPos = m_Size - pos; + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +long CWin32ReadOnlyFile::FS_ftell() +{ + return m_ReadPos; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +int CWin32ReadOnlyFile::FS_feof() +{ + return ( m_ReadPos >= m_Size ); +} + +// ends up on a thread's stack, don't blindly increase without awareness of that implication +// 360 threads have small stacks, using small buffer of the worst case quantum sector size +#if !defined( _X360 ) +#define READ_TEMP_BUFFER ( 32*1024 ) +#else +#define READ_TEMP_BUFFER ( 2*XBOX_DVD_SECTORSIZE ) +#endif + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +size_t CWin32ReadOnlyFile::FS_fread( void *dest, size_t destSize, size_t size ) +{ + VPROF_BUDGET( "CWin32ReadOnlyFile::FS_fread", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %t", __FUNCTION__, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); + if( ThreadInMainThread() ) + { + tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, size, "FileBytesRead" ); + } + + if ( !size || ( m_hFileUnbuffered == INVALID_HANDLE_VALUE && m_hFileBuffered == INVALID_HANDLE_VALUE ) ) + { + return 0; + } + + CThreadEvent *pEvent = NULL; + + if ( destSize == (size_t)-1 ) + { + destSize = size; + } + + byte tempBuffer[READ_TEMP_BUFFER]; + HANDLE hReadFile = m_hFileBuffered; + int nBytesToRead = size; + byte *pDest = (byte *)dest; + int64 offset = m_ReadPos; + + if ( m_hFileUnbuffered != INVALID_HANDLE_VALUE ) + { + const int destBaseAlign = ( IsX360() ) ? 4 : m_SectorSize; + bool bDestBaseIsAligned = ( (DWORD)dest % destBaseAlign == 0 ); + bool bCanReadUnbufferedDirect = ( bDestBaseIsAligned && ( destSize % m_SectorSize == 0 ) && ( m_ReadPos % m_SectorSize == 0 ) ); + + if ( bCanReadUnbufferedDirect ) + { + // fastest path, unbuffered + nBytesToRead = AlignValue( size, m_SectorSize ); + hReadFile = m_hFileUnbuffered; + } + else + { + // not properly aligned, snap to alignments + // attempt to perform single unbuffered operation using stack buffer + int64 alignedOffset = AlignValue( ( m_ReadPos - m_SectorSize ) + 1, m_SectorSize ); + unsigned int alignedBytesToRead = AlignValue( ( m_ReadPos - alignedOffset ) + size, m_SectorSize ); + if ( alignedBytesToRead <= sizeof( tempBuffer ) - destBaseAlign ) + { + // read operation can be performed as unbuffered follwed by a post fixup + nBytesToRead = alignedBytesToRead; + offset = alignedOffset; + pDest = AlignValue( tempBuffer, destBaseAlign ); + hReadFile = m_hFileUnbuffered; + } + } + } + + OVERLAPPED overlapped = { 0 }; + if ( m_bOverlapped ) + { + pEvent = g_ThreadIOEvents.GetEvent(); + overlapped.hEvent = *pEvent; + } + +#ifdef REPORT_BUFFERED_IO + if ( hReadFile == m_hFileBuffered && filesystem_report_buffered_io.GetBool() ) + { + Msg( "Buffered Operation :(\n" ); + } +#endif + + // some disk drivers will fail if read is too large + static int MAX_READ = filesystem_max_stdio_read.GetInt()*1024*1024; + const int MIN_READ = 64*1024; + bool bReadOk = true; + DWORD nBytesRead = 0; + size_t result = 0; + int64 currentOffset = offset; + + while ( bReadOk && nBytesToRead > 0 ) + { + int nCurBytesToRead = min( nBytesToRead, MAX_READ ); + DWORD nCurBytesRead = 0; + + overlapped.Offset = currentOffset & 0xFFFFFFFF; + overlapped.OffsetHigh = ( currentOffset >> 32 ) & 0xFFFFFFFF; + + bReadOk = ( ::ReadFile( hReadFile, pDest + nBytesRead, nCurBytesToRead, &nCurBytesRead, &overlapped ) != 0 ); + if ( !bReadOk ) + { + if ( m_bOverlapped && GetLastError() == ERROR_IO_PENDING ) + { + bReadOk = true; + } + } + + if ( bReadOk ) + { + if ( !m_bOverlapped || GetOverlappedResult( hReadFile, &overlapped, &nCurBytesRead, true ) ) + { + nBytesRead += nCurBytesRead; + nBytesToRead -= nCurBytesRead; + currentOffset += nCurBytesRead; + } + else + { + if ( m_bOverlapped ) + { + if ( GetLastError() == ERROR_HANDLE_EOF ) + { + nBytesToRead = 0; // we have hit the end of the file + } + else + { + bReadOk = false; + } + } + else + { + bReadOk = false; + } + } + + if ( !m_bOverlapped && nCurBytesRead == 0 ) + { + nBytesToRead = 0; // we have hit the end of the file + } + + } + + if ( nBytesToRead > 0 && nCurBytesRead == 0 ) // if you failed to ready anything this time then bail the loop + { + DevMsg( "Got zero length read" ); + bReadOk = false; + } + else if ( !bReadOk ) + { + DWORD dwError = GetLastError(); + + if ( IsX360() ) + { + if ( dwError == ERROR_DISK_CORRUPT || dwError == ERROR_FILE_CORRUPT ) + { + FSDirtyDiskReportFunc_t func = g_FileSystem_Stdio.GetDirtyDiskReportFunc(); + if ( func ) + { + func(); + result = 0; + } + } + } + + if ( dwError == ERROR_NO_SYSTEM_RESOURCES && MAX_READ > MIN_READ ) + { + MAX_READ /= 2; + bReadOk = true; + DevMsg( "ERROR_NO_SYSTEM_RESOURCES: Reducing max read to %d bytes\n", MAX_READ ); + } + else + { + DevMsg( "Unknown read error %d\n", dwError ); + } + } + } + + if ( bReadOk ) + { + if ( nBytesRead && hReadFile == m_hFileUnbuffered && pDest != dest ) + { + int nBytesExtra = ( m_ReadPos - offset ); + nBytesRead -= nBytesExtra; + if ( nBytesRead ) + { + memcpy( dest, (byte *)pDest + nBytesExtra, size ); + } + } + + result = min( (size_t)nBytesRead, size ); + } + + if ( m_bOverlapped ) + { + pEvent->Reset(); + g_ThreadIOEvents.ReleaseEvent( pEvent ); + } + + m_ReadPos += result; + + return result; +} + +//----------------------------------------------------------------------------- +// Purpose: low-level filesystem wrapper +//----------------------------------------------------------------------------- +char *CWin32ReadOnlyFile::FS_fgets( char *dest, int destSize ) +{ + if ( FS_feof() ) + { + return NULL; + } + int nStartPos = m_ReadPos; + int nBytesRead = FS_fread( dest, destSize, destSize ); + if ( !nBytesRead ) + { + return NULL; + } + + dest[min( nBytesRead, destSize - 1)] = 0; + char *pNewline = strchr( dest, '\n' ); + if ( pNewline ) + { + // advance past, leave \n + pNewline++; + *pNewline = 0; + } + else + { + pNewline = &dest[min( nBytesRead, destSize - 1)]; + } + m_ReadPos = nStartPos + ( pNewline - dest ) + 1; + + return dest; +} + + +#endif // _WIN32 |