//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #ifndef UTLCACHEDFILEDATA_H #define UTLCACHEDFILEDATA_H #if defined( WIN32 ) #pragma once #endif #include "filesystem.h" // FileNameHandle_t #include "utlrbtree.h" #include "utlbuffer.h" #include "UtlSortVector.h" #include "tier1/strtools.h" #include "tier0/memdbgon.h" // If you change to serialization protocols, this must be bumped... #define UTL_CACHE_SYSTEM_VERSION 2 #define UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO (long)-2 // Cacheable types must derive from this and implement the appropriate methods... abstract_class IBaseCacheInfo { public: virtual void Save( CUtlBuffer& buf ) = 0; virtual void Restore( CUtlBuffer& buf ) = 0; virtual void Rebuild( char const *filename ) = 0; }; typedef unsigned int (*PFNCOMPUTECACHEMETACHECKSUM)( void ); typedef enum { UTL_CACHED_FILE_USE_TIMESTAMP = 0, UTL_CACHED_FILE_USE_FILESIZE, } UtlCachedFileDataType_t; template class CUtlCachedFileData { public: CUtlCachedFileData ( char const *repositoryFileName, int version, PFNCOMPUTECACHEMETACHECKSUM checksumfunc = NULL, UtlCachedFileDataType_t fileCheckType = UTL_CACHED_FILE_USE_TIMESTAMP, bool nevercheckdisk = false, bool readonly = false, bool savemanifest = false ) : m_Elements( 0, 0, FileNameHandleLessFunc ), m_sRepositoryFileName( repositoryFileName ), m_nVersion( version ), m_pfnMetaChecksum( checksumfunc ), m_bDirty( false ), m_bInitialized( false ), m_uCurrentMetaChecksum( 0u ), m_fileCheckType( fileCheckType ), m_bNeverCheckDisk( nevercheckdisk ), m_bReadOnly( readonly ), m_bSaveManifest( savemanifest ) { Assert( !m_sRepositoryFileName.IsEmpty() ); } virtual ~CUtlCachedFileData() { m_Elements.RemoveAll(); int c = m_Data.Count(); for ( int i = 0; i < c ; ++i ) { delete m_Data[ i ]; } m_Data.RemoveAll(); } // If bExpectMissing is set, don't complain if this causes synchronous disk I/O to build the cache. T* Get( char const *filename ); const T* Get( char const *filename ) const; T* operator[]( int i ); const T* operator[]( int i ) const; int Count() const; void GetElementName( int i, char *buf, int buflen ) { buf[ 0 ] = 0; if ( !m_Elements.IsValidIndex( i ) ) return; g_pFullFileSystem->String( m_Elements[ i ].handle, buf, buflen ); } bool EntryExists( char const *filename ) const { ElementType_t element; element.handle = g_pFullFileSystem->FindOrAddFileName( filename ); int idx = m_Elements.Find( element ); return idx != m_Elements.InvalidIndex() ? true : false; } void SetElement( char const *name, long fileinfo, T* src ) { SetDirty( true ); int idx = GetIndex( name ); Assert( idx != m_Elements.InvalidIndex() ); ElementType_t& e = m_Elements[ idx ]; CUtlBuffer buf( 0, 0, 0 ); Assert( e.dataIndex != m_Data.InvalidIndex() ); T *dest = m_Data[ e.dataIndex ]; Assert( dest ); // I suppose we could do an assignment operator, but this should save/restore the data element just fine for // tool purposes ((IBaseCacheInfo *)src)->Save( buf ); ((IBaseCacheInfo *)dest)->Restore( buf ); e.fileinfo = fileinfo; if ( ( e.fileinfo == -1 ) && ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) ) { e.fileinfo = 0; } // Force recheck e.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO; } // If you create a cache and don't call init/shutdown, you can call this to do a quick check to see if the checksum/version // will cause a rebuild... bool IsUpToDate(); void Shutdown(); bool Init(); void Save(); void Reload(); void ForceRecheckDiskInfo(); // Iterates all entries and gets filesystem info and optionally causes rebuild on any existing items which are out of date void CheckDiskInfo( bool force_rebuild, long cacheFileTime = 0L ); void SaveManifest(); bool ManifestExists(); const char *GetRepositoryFileName() const { return m_sRepositoryFileName; } long GetFileInfo( char const *filename ) { ElementType_t element; element.handle = g_pFullFileSystem->FindOrAddFileName( filename ); int idx = m_Elements.Find( element ); if ( idx == m_Elements.InvalidIndex() ) { return 0L; } return m_Elements[ idx ].fileinfo; } int GetNumElements() { return m_Elements.Count(); } bool IsDirty() const { return m_bDirty; } T *RebuildItem( const char *filename ); private: void InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile ); void InitLargeBuffer( FileHandle_t& fh, bool& deleteFile ); int GetIndex( const char *filename ) { ElementType_t element; element.handle = g_pFullFileSystem->FindOrAddFileName( filename ); int idx = m_Elements.Find( element ); if ( idx == m_Elements.InvalidIndex() ) { T *data = new T(); int dataIndex = m_Data.AddToTail( data ); idx = m_Elements.Insert( element ); m_Elements[ idx ].dataIndex = dataIndex; } return idx; } void CheckInit(); void SetDirty( bool dirty ) { m_bDirty = dirty; } void RebuildCache( char const *filename, T *data ); struct ElementType_t { ElementType_t() : handle( 0 ), fileinfo( 0 ), diskfileinfo( UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ), dataIndex( -1 ) { } FileNameHandle_t handle; long fileinfo; long diskfileinfo; int dataIndex; }; static bool FileNameHandleLessFunc( ElementType_t const &lhs, ElementType_t const &rhs ) { return lhs.handle < rhs.handle; } CUtlRBTree< ElementType_t > m_Elements; CUtlVector< T * > m_Data; CUtlString m_sRepositoryFileName; int m_nVersion; PFNCOMPUTECACHEMETACHECKSUM m_pfnMetaChecksum; unsigned int m_uCurrentMetaChecksum; UtlCachedFileDataType_t m_fileCheckType; bool m_bNeverCheckDisk : 1; bool m_bReadOnly : 1; bool m_bSaveManifest : 1; bool m_bDirty : 1; bool m_bInitialized : 1; }; template T* CUtlCachedFileData::Get( char const *filename ) { int idx = GetIndex( filename ); ElementType_t& e = m_Elements[ idx ]; if ( e.fileinfo == -1 && m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) { e.fileinfo = 0; } long cachefileinfo = e.fileinfo; // Set the disk fileinfo the first time we encounter the filename if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ) { if ( m_bNeverCheckDisk ) { e.diskfileinfo = cachefileinfo; } else { if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) { e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" ); // Missing files get a disk file size of 0 if ( e.diskfileinfo == -1 ) { e.diskfileinfo = 0; } } else { e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" ); } } } Assert( e.dataIndex != m_Data.InvalidIndex() ); T *data = m_Data[ e.dataIndex ]; Assert( data ); // Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct... if ( cachefileinfo != e.diskfileinfo ) { if ( !m_bReadOnly ) { RebuildCache( filename, data ); } e.fileinfo = e.diskfileinfo; } return data; } template const T* CUtlCachedFileData::Get( char const *filename ) const { return const_cast< CUtlCachedFileData * >(this)->Get( filename ); } template T* CUtlCachedFileData::operator[]( int i ) { return m_Data[ m_Elements[ i ].dataIndex ]; } template const T* CUtlCachedFileData::operator[]( int i ) const { return m_Data[ m_Elements[ i ].dataIndex ]; } template int CUtlCachedFileData::Count() const { return m_Elements.Count(); } template void CUtlCachedFileData::Reload() { Shutdown(); Init(); } template bool CUtlCachedFileData::IsUpToDate() { // Don't call Init/Shutdown if using this method!!! Assert( !m_bInitialized ); if ( m_sRepositoryFileName.IsEmpty() ) { Error( "CUtlCachedFileData: Can't IsUpToDate, no repository file specified." ); return false; } // Always compute meta checksum m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0; FileHandle_t fh; fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" ); if ( fh == FILESYSTEM_INVALID_HANDLE ) { return false; } // Version data is in first 12 bytes of file byte header[ 12 ]; g_pFullFileSystem->Read( header, sizeof( header ), fh ); g_pFullFileSystem->Close( fh ); int cacheversion = *( int *)&header[ 0 ]; if ( UTL_CACHE_SYSTEM_VERSION != cacheversion ) { DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() ); Assert( !m_bReadOnly ); if ( !m_bReadOnly ) { g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" ); } return false; } // Now parse data from the buffer int version = *( int *)&header[ 4 ]; if ( version != m_nVersion ) { DevMsg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() ); Assert( !m_bReadOnly ); if ( !m_bReadOnly ) { g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" ); } return false; } // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in // meta data function unsigned int cache_meta_checksum = (unsigned int)*( int *)&header[ 8 ]; if ( cache_meta_checksum != m_uCurrentMetaChecksum ) { DevMsg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() ); Assert( !m_bReadOnly ); if ( !m_bReadOnly ) { g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" ); } return false; } // Looks valid return true; } template void CUtlCachedFileData::InitSmallBuffer( FileHandle_t& fh, int fileSize, bool& deleteFile ) { deleteFile = false; CUtlBuffer loadBuf; g_pFullFileSystem->ReadToBuffer( fh, loadBuf ); g_pFullFileSystem->Close( fh ); int cacheversion = 0; loadBuf.Get( &cacheversion, sizeof( cacheversion ) ); if ( UTL_CACHE_SYSTEM_VERSION == cacheversion ) { // Now parse data from the buffer int version = loadBuf.GetInt(); if ( version == m_nVersion ) { // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in // meta data function unsigned int cache_meta_checksum = loadBuf.GetInt(); if ( cache_meta_checksum == m_uCurrentMetaChecksum ) { int count = loadBuf.GetInt(); Assert( count < 2000000 ); CUtlBuffer buf( 0, 0, 0 ); for ( int i = 0 ; i < count; ++i ) { int bufsize = loadBuf.GetInt(); Assert( bufsize < 1000000 ); buf.Clear(); buf.EnsureCapacity( bufsize ); loadBuf.Get( buf.Base(), bufsize ); buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); buf.SeekPut( CUtlBuffer::SEEK_HEAD, bufsize ); // Read the element name char elementFileName[ 512 ]; buf.GetString( elementFileName ); // Now read the element int slot = GetIndex( elementFileName ); Assert( slot != m_Elements.InvalidIndex() ); ElementType_t& element = m_Elements[ slot ]; element.fileinfo = buf.GetInt(); if ( ( element.fileinfo == -1 ) && ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) ) { element.fileinfo = 0; } Assert( element.dataIndex != m_Data.InvalidIndex() ); T *data = m_Data[ element.dataIndex ]; Assert( data ); ((IBaseCacheInfo *)data)->Restore( buf ); } } else { Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() ); deleteFile = true; } } else { Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() ); deleteFile = true; } } else { DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() ); deleteFile = true; } } template void CUtlCachedFileData::InitLargeBuffer( FileHandle_t& fh, bool& deleteFile ) { deleteFile = false; int cacheversion = 0; g_pFullFileSystem->Read( &cacheversion, sizeof( cacheversion ), fh ); if ( UTL_CACHE_SYSTEM_VERSION == cacheversion ) { // Now parse data from the buffer int version = 0; g_pFullFileSystem->Read( &version, sizeof( version ), fh ); if ( version == m_nVersion ) { // This is a checksum based on any meta data files which the cache depends on (supplied by a passed in // meta data function unsigned int cache_meta_checksum = 0; g_pFullFileSystem->Read( &cache_meta_checksum, sizeof( cache_meta_checksum ), fh ); if ( cache_meta_checksum == m_uCurrentMetaChecksum ) { int count = 0; g_pFullFileSystem->Read( &count, sizeof( count ), fh ); Assert( count < 2000000 ); CUtlBuffer buf( 0, 0, 0 ); for ( int i = 0 ; i < count; ++i ) { int bufsize = 0; g_pFullFileSystem->Read( &bufsize, sizeof( bufsize ), fh ); Assert( bufsize < 1000000 ); if ( bufsize > 1000000 ) { Msg( "Discarding repository '%s' due to corruption\n", m_sRepositoryFileName.String() ); deleteFile = true; break; } buf.Clear(); buf.EnsureCapacity( bufsize ); int nBytesRead = g_pFullFileSystem->Read( buf.Base(), bufsize, fh ); buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); // Read the element name char elementFileName[ 512 ]; buf.GetString( elementFileName ); // Now read the element int slot = GetIndex( elementFileName ); Assert( slot != m_Elements.InvalidIndex() ); ElementType_t& element = m_Elements[ slot ]; element.fileinfo = buf.GetInt(); if ( ( element.fileinfo == -1 ) && ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) ) { element.fileinfo = 0; } Assert( element.dataIndex != m_Data.InvalidIndex() ); T *data = m_Data[ element.dataIndex ]; Assert( data ); ((IBaseCacheInfo *)data)->Restore( buf ); } } else { Msg( "Discarding repository '%s' due to meta checksum change\n", m_sRepositoryFileName.String() ); deleteFile = true; } } else { Msg( "Discarding repository '%s' due to version change\n", m_sRepositoryFileName.String() ); deleteFile = true; } } else { DevMsg( "Discarding repository '%s' due to cache system version change\n", m_sRepositoryFileName.String() ); deleteFile = true; } g_pFullFileSystem->Close( fh ); } template bool CUtlCachedFileData::Init() { if ( m_bInitialized ) { return true; } m_bInitialized = true; if ( m_sRepositoryFileName.IsEmpty() ) { Error( "CUtlCachedFileData: Can't Init, no repository file specified." ); return false; } // Always compute meta checksum m_uCurrentMetaChecksum = m_pfnMetaChecksum ? (*m_pfnMetaChecksum)() : 0; FileHandle_t fh; fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "rb", "MOD" ); if ( fh == FILESYSTEM_INVALID_HANDLE ) { // Nothing on disk, we'll recreate everything from scratch... SetDirty( true ); return true; } long fileTime = g_pFullFileSystem->GetFileTime( m_sRepositoryFileName, "MOD" ); int size = g_pFullFileSystem->Size( fh ); bool deletefile = false; if ( size > 1024 * 1024 ) { InitLargeBuffer( fh, deletefile ); } else { InitSmallBuffer( fh, size, deletefile ); } if ( deletefile ) { Assert( !m_bReadOnly ); if ( !m_bReadOnly ) { g_pFullFileSystem->RemoveFile( m_sRepositoryFileName, "MOD" ); } SetDirty( true ); } CheckDiskInfo( false, fileTime ); return true; } template void CUtlCachedFileData::Save() { char path[ 512 ]; Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) ); Q_StripFilename( path ); g_pFullFileSystem->CreateDirHierarchy( path, "MOD" ); if ( g_pFullFileSystem->FileExists( m_sRepositoryFileName, "MOD" ) && !g_pFullFileSystem->IsFileWritable( m_sRepositoryFileName, "MOD" ) ) { g_pFullFileSystem->SetFileWritable( m_sRepositoryFileName, true, "MOD" ); } // Now write to file FileHandle_t fh; fh = g_pFullFileSystem->Open( m_sRepositoryFileName, "wb" ); if ( FILESYSTEM_INVALID_HANDLE == fh ) { ExecuteNTimes( 25, Warning( "Unable to persist cache '%s', check file permissions\n", m_sRepositoryFileName.String() ) ); } else { SetDirty( false ); int v = UTL_CACHE_SYSTEM_VERSION; g_pFullFileSystem->Write( &v, sizeof( v ), fh ); v = m_nVersion; g_pFullFileSystem->Write( &v, sizeof( v ), fh ); v = (int)m_uCurrentMetaChecksum; g_pFullFileSystem->Write( &v, sizeof( v ), fh ); // Element count int c = Count(); g_pFullFileSystem->Write( &c, sizeof( c ), fh ); // Save repository back out to disk... CUtlBuffer buf( 0, 0, 0 ); for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) ) { buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); ElementType_t& element = m_Elements[ i ]; char fn[ 512 ]; g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) ); buf.PutString( fn ); buf.PutInt( element.fileinfo ); Assert( element.dataIndex != m_Data.InvalidIndex() ); T *data = m_Data[ element.dataIndex ]; Assert( data ); ((IBaseCacheInfo *)data)->Save( buf ); int bufsize = buf.TellPut(); g_pFullFileSystem->Write( &bufsize, sizeof( bufsize ), fh ); g_pFullFileSystem->Write( buf.Base(), bufsize, fh ); } g_pFullFileSystem->Close( fh ); } if ( m_bSaveManifest ) { SaveManifest(); } } template void CUtlCachedFileData::Shutdown() { if ( !m_bInitialized ) return; m_bInitialized = false; if ( IsDirty() ) { Save(); } // No matter what, create the manifest if it doesn't exist on the HD yet else if ( m_bSaveManifest && !ManifestExists() ) { SaveManifest(); } m_Elements.RemoveAll(); } template bool CUtlCachedFileData::ManifestExists() { char manifest_name[ 512 ]; Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) ); Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) ); return g_pFullFileSystem->FileExists( manifest_name, "MOD" ); } template void CUtlCachedFileData::SaveManifest() { // Save manifest out to disk... CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) ) { ElementType_t& element = m_Elements[ i ]; char fn[ 512 ]; g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) ); buf.Printf( "\"%s\"\r\n", fn ); } char path[ 512 ]; Q_strncpy( path, m_sRepositoryFileName, sizeof( path ) ); Q_StripFilename( path ); g_pFullFileSystem->CreateDirHierarchy( path, "MOD" ); char manifest_name[ 512 ]; Q_strncpy( manifest_name, m_sRepositoryFileName, sizeof( manifest_name ) ); Q_SetExtension( manifest_name, ".manifest", sizeof( manifest_name ) ); if ( g_pFullFileSystem->FileExists( manifest_name, "MOD" ) && !g_pFullFileSystem->IsFileWritable( manifest_name, "MOD" ) ) { g_pFullFileSystem->SetFileWritable( manifest_name, true, "MOD" ); } // Now write to file FileHandle_t fh; fh = g_pFullFileSystem->Open( manifest_name, "wb" ); if ( FILESYSTEM_INVALID_HANDLE != fh ) { g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh ); g_pFullFileSystem->Close( fh ); // DevMsg( "Persisting cache manifest '%s' (%d entries)\n", manifest_name, c ); } else { Warning( "Unable to persist cache manifest '%s', check file permissions\n", manifest_name ); } } template T *CUtlCachedFileData::RebuildItem( const char *filename ) { int idx = GetIndex( filename ); ElementType_t& e = m_Elements[ idx ]; ForceRecheckDiskInfo(); long cachefileinfo = e.fileinfo; // Set the disk fileinfo the first time we encounter the filename if ( e.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ) { if ( m_bNeverCheckDisk ) { e.diskfileinfo = cachefileinfo; } else { if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) { e.diskfileinfo = g_pFullFileSystem->Size( filename, "GAME" ); // Missing files get a disk file size of 0 if ( e.diskfileinfo == -1 ) { e.diskfileinfo = 0; } } else { e.diskfileinfo = g_pFullFileSystem->GetFileTime( filename, "GAME" ); } } } Assert( e.dataIndex != m_Data.InvalidIndex() ); T *data = m_Data[ e.dataIndex ]; Assert( data ); // Compare fileinfo to disk fileinfo and rebuild cache if out of date or not correct... if ( !m_bReadOnly ) { RebuildCache( filename, data ); } e.fileinfo = e.diskfileinfo; return data; } template void CUtlCachedFileData::RebuildCache( char const *filename, T *data ) { Assert( !m_bReadOnly ); // Recache item, mark self as dirty SetDirty( true ); ((IBaseCacheInfo *)data)->Rebuild( filename ); } template void CUtlCachedFileData::ForceRecheckDiskInfo() { for ( int i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) ) { ElementType_t& element = m_Elements[ i ]; element.diskfileinfo = UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO; } } class CSortedCacheFile { public: FileNameHandle_t handle; int index; bool Less( const CSortedCacheFile &file0, const CSortedCacheFile &file1, void * ) { char name0[ 512 ]; char name1[ 512 ]; g_pFullFileSystem->String( file0.handle, name0, sizeof( name0 ) ); g_pFullFileSystem->String( file1.handle, name1, sizeof( name1 ) ); return Q_stricmp( name0, name1 ) < 0 ? true : false; } }; // Iterates all entries and causes rebuild on any existing items which are out of date template void CUtlCachedFileData::CheckDiskInfo( bool forcerebuild, long cacheFileTime ) { char fn[ 512 ]; int i; if ( forcerebuild ) { for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) ) { ElementType_t& element = m_Elements[ i ]; g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) ); Get( fn ); } return; } CUtlSortVector list; for ( i = m_Elements.FirstInorder(); i != m_Elements.InvalidIndex(); i = m_Elements.NextInorder( i ) ) { ElementType_t& element = m_Elements[ i ]; CSortedCacheFile insert; insert.handle = element.handle; insert.index = i; list.InsertNoSort( insert ); } list.RedoSort(); if ( !list.Count() ) return; for ( int listStart = 0, listEnd = 0; listStart < list.Count(); listStart = listEnd+1 ) { int pathIndex = g_pFullFileSystem->GetPathIndex( m_Elements[list[listStart].index].handle ); for ( listEnd = listStart; listEnd < list.Count(); listEnd++ ) { ElementType_t& element = m_Elements[ list[listEnd].index ]; int pathTest = g_pFullFileSystem->GetPathIndex( element.handle ); if ( pathTest != pathIndex ) break; } g_pFullFileSystem->String( m_Elements[list[listStart].index].handle, fn, sizeof( fn ) ); Q_StripFilename( fn ); bool bCheck = true; if ( m_bNeverCheckDisk ) { bCheck = false; } else { long pathTime = g_pFullFileSystem->GetPathTime( fn, "GAME" ); bCheck = (pathTime > cacheFileTime) ? true : false; } for ( i = listStart; i < listEnd; i++ ) { ElementType_t& element = m_Elements[ list[i].index ]; if ( element.diskfileinfo == UTL_CACHED_FILE_DATA_UNDEFINED_DISKINFO ) { if ( !bCheck ) { element.diskfileinfo = element.fileinfo; } else { g_pFullFileSystem->String( element.handle, fn, sizeof( fn ) ); if ( m_fileCheckType == UTL_CACHED_FILE_USE_FILESIZE ) { element.diskfileinfo = g_pFullFileSystem->Size( fn, "GAME" ); // Missing files get a disk file size of 0 if ( element.diskfileinfo == -1 ) { element.diskfileinfo = 0; } } else { element.diskfileinfo = g_pFullFileSystem->GetFileTime( fn, "GAME" ); } } } } } } #include "tier0/memdbgoff.h" #endif // UTLCACHEDFILEDATA_H