//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #ifndef UTLCACHEDFILEDATA_H #define UTLCACHEDFILEDATA_H #if defined(WIN32) #pragma once #endif #include "UtlSortVector.h" #include "filesystem.h" // FileNameHandle_t #include "tier1/strtools.h" #include "utlbuffer.h" #include "utlrbtree.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(); } 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 m_Elements; CUtlVector 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 *>(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