summaryrefslogtreecommitdiff
path: root/filesystem/packfile.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /filesystem/packfile.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'filesystem/packfile.cpp')
-rw-r--r--filesystem/packfile.cpp1127
1 files changed, 1127 insertions, 0 deletions
diff --git a/filesystem/packfile.cpp b/filesystem/packfile.cpp
new file mode 100644
index 0000000..5190959
--- /dev/null
+++ b/filesystem/packfile.cpp
@@ -0,0 +1,1127 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#include "packfile.h"
+#include "zip_utils.h"
+#include "tier0/basetypes.h"
+#include "tier1/convar.h"
+#include "tier1/lzmaDecoder.h"
+#include "tier1/utlbuffer.h"
+#include "tier1/generichash.h"
+
+ConVar fs_monitor_read_from_pack( "fs_monitor_read_from_pack", "0", 0, "0:Off, 1:Any, 2:Sync only" );
+
+// How many bytes we should decode at a time when doing pseudo-reads to seek forward in a compressed file handle,
+// (affects maximum stack allocation by a forward seek)
+#define COMPRESSED_SEEK_READ_CHUNK 1024
+
+CPackFile::CPackFile()
+{
+ m_FileLength = 0;
+ m_hPackFileHandleFS = NULL;
+ m_fs = NULL;
+ m_nBaseOffset = 0;
+ m_bIsMapPath = false;
+ m_lPackFileTime = 0L;
+ m_refCount = 0;
+ m_nOpenFiles = 0;
+ m_PackFileID = 0;
+}
+
+CPackFile::~CPackFile()
+{
+ if ( m_nOpenFiles )
+ {
+ Error( "Closing pack file with %d open files!\n", m_nOpenFiles );
+ }
+
+ if ( m_hPackFileHandleFS )
+ {
+ m_fs->FS_fclose( m_hPackFileHandleFS );
+ m_hPackFileHandleFS = NULL;
+ }
+
+ m_fs->m_ZipFiles.FindAndRemove( this );
+}
+
+int CPackFile::GetSectorSize()
+{
+ if ( m_hPackFileHandleFS )
+ {
+ return m_fs->FS_GetSectorSize( m_hPackFileHandleFS );
+ }
+#if defined( SUPPORT_PACKED_STORE )
+ else if ( m_hPackFileHandleVPK )
+ {
+ return 2048;
+ }
+#endif
+ else
+ {
+ return -1;
+ }
+}
+
+// Read a bit of the file from the pack file:
+int CZipPackFileHandle::Read( void* pBuffer, int nDestSize, int nBytes )
+{
+ // Clamp nBytes to not go past the end of the file (async is still possible due to nDestSize)
+ if ( nBytes + m_nFilePointer > m_nLength )
+ {
+ nBytes = m_nLength - m_nFilePointer;
+ }
+
+ // Seek to the given file pointer and read
+ int nBytesRead = m_pOwner->ReadFromPack( m_nIndex, pBuffer, nDestSize, nBytes, m_nBase + m_nFilePointer );
+
+ m_nFilePointer += nBytesRead;
+
+ return nBytesRead;
+}
+
+// Seek around inside the pack:
+int CZipPackFileHandle::Seek( int nOffset, int nWhence )
+{
+ if ( nWhence == SEEK_SET )
+ {
+ m_nFilePointer = nOffset;
+ }
+ else if ( nWhence == SEEK_CUR )
+ {
+ m_nFilePointer += nOffset;
+ }
+ else if ( nWhence == SEEK_END )
+ {
+ m_nFilePointer = m_nLength + nOffset;
+ }
+
+ // Clamp the file pointer to the actual bounds of the file:
+ if ( m_nFilePointer > m_nLength )
+ {
+ m_nFilePointer = m_nLength;
+ }
+
+ return m_nFilePointer;
+}
+
+//-----------------------------------------------------------------------------
+// Open a file inside of a pack file.
+//-----------------------------------------------------------------------------
+CFileHandle *CZipPackFile::OpenFile( const char *pFileName, const char *pOptions )
+{
+ int nIndex, nOriginalSize, nCompressedSize;
+ int64 nPosition;
+ unsigned short nCompressionMethod;
+
+ // find the file's location in the pack
+ if ( GetFileInfo( pFileName, nIndex, nPosition, nOriginalSize, nCompressedSize, nCompressionMethod ) )
+ {
+ m_mutex.Lock();
+#if defined( SUPPORT_PACKED_STORE )
+ if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL && !m_hPackFileHandleVPK )
+#else
+ if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL )
+#endif
+ {
+ // Try to open it as a regular file first
+ m_hPackFileHandleFS = m_fs->Trace_FOpen( m_ZipName, "rb", 0, NULL );
+
+ // !NOTE! Pack files inside of VPK not supported
+ }
+ m_nOpenFiles++;
+ m_mutex.Unlock();
+ CPackFileHandle* ph = NULL;
+ if ( nCompressionMethod == ZIP_COMPRESSION_LZMA )
+ {
+ ph = new CLZMAZipPackFileHandle( this, nPosition, nOriginalSize, nCompressedSize, nIndex );
+ }
+ else
+ {
+ AssertMsg( nCompressionMethod == ZIP_COMPRESSION_NONE, "Unsupported compression type in zip pack file" );
+ ph = new CZipPackFileHandle( this, nPosition, nOriginalSize, nIndex );
+ }
+ CFileHandle *fh = new CFileHandle( m_fs );
+ fh->m_pPackFileHandle = ph;
+ fh->m_nLength = nOriginalSize;
+
+ // The default mode for fopen is text, so require 'b' for binary
+ if ( strstr( pOptions, "b" ) == NULL )
+ {
+ fh->m_type = FT_PACK_TEXT;
+ }
+ else
+ {
+ fh->m_type = FT_PACK_BINARY;
+ }
+
+#if !defined( _RETAIL )
+ fh->SetName( pFileName );
+#endif
+ return fh;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Get a directory entry from a pack's preload section
+//-----------------------------------------------------------------------------
+ZIP_PreloadDirectoryEntry* CZipPackFile::GetPreloadEntry( int nEntryIndex )
+{
+ if ( !m_pPreloadHeader )
+ {
+ return NULL;
+ }
+
+ // If this entry doesn't have a corresponding preload entry, fail.
+ if ( m_PackFiles[nEntryIndex].m_nPreloadIdx == INVALID_PRELOAD_ENTRY )
+ {
+ return NULL;
+ }
+
+ return &m_pPreloadDirectory[m_PackFiles[nEntryIndex].m_nPreloadIdx];
+}
+
+//-----------------------------------------------------------------------------
+// Read a file from the pack
+//-----------------------------------------------------------------------------
+int CZipPackFile::ReadFromPack( int nEntryIndex, void* pBuffer, int nDestBytes, int nBytes, int64 nOffset )
+{
+ if ( nEntryIndex >= 0 )
+ {
+ if ( nBytes <= 0 )
+ {
+ return 0;
+ }
+
+ // X360TBD: This is screwy, it works because m_nBaseOffset is 0 for preload capable zips
+ // It comes into play for files out of the embedded bsp zip,
+ // this hackery is a pre-bias expecting ReadFromPack() do a symmetric post bias, yuck.
+
+ // Attempt to satisfy request from possible preload section, otherwise fall through
+ // A preload entry may be compressed
+ ZIP_PreloadDirectoryEntry *pPreloadEntry = GetPreloadEntry( nEntryIndex );
+ if ( pPreloadEntry )
+ {
+ // convert the absolute pack file position to a local file position
+ int nLocalOffset = nOffset - m_PackFiles[nEntryIndex].m_nPosition;
+ byte *pPreloadData = (byte*)m_pPreloadData + pPreloadEntry->DataOffset;
+
+ if ( CLZMA::IsCompressed( pPreloadData ) )
+ {
+ unsigned int actualSize = CLZMA::GetActualSize( pPreloadData );
+ if ( nLocalOffset + nBytes <= (int)actualSize )
+ {
+ // satisfy from compressed preload
+ if ( fs_monitor_read_from_pack.GetInt() == 1 )
+ {
+ char szName[MAX_PATH];
+ IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
+ Msg( "Read From Pack: [Preload] Requested:%d, Compressed:%d, %s\n", nBytes, pPreloadEntry->Length, szName );
+ }
+
+ if ( nLocalOffset == 0 && nDestBytes >= (int)actualSize && nBytes == (int)actualSize )
+ {
+ // uncompress directly into caller's buffer
+ CLZMA::Uncompress( (unsigned char *)pPreloadData, (unsigned char *)pBuffer );
+ return nBytes;
+ }
+
+ // uncompress into temporary memory
+ CUtlMemory< byte > tempMemory;
+ tempMemory.EnsureCapacity( actualSize );
+ CLZMA::Uncompress( pPreloadData, tempMemory.Base() );
+ // copy only what caller expects
+ V_memcpy( pBuffer, (byte*)tempMemory.Base() + nLocalOffset, nBytes );
+ return nBytes;
+ }
+ }
+ else if ( nLocalOffset + nBytes <= (int)pPreloadEntry->Length )
+ {
+ // satisfy from uncompressed preload
+ if ( fs_monitor_read_from_pack.GetInt() == 1 )
+ {
+ char szName[MAX_PATH];
+ IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
+ Msg( "Read From Pack: [Preload] Requested:%d, Total:%d, %s\n", nBytes, pPreloadEntry->Length, szName );
+ }
+
+ V_memcpy( pBuffer, pPreloadData + nLocalOffset, nBytes );
+ return nBytes;
+ }
+ }
+ }
+
+#if defined ( _X360 )
+ // fell through as a direct request from within the pack
+ // intercept to possible embedded section
+ if ( m_pSection )
+ {
+ // a section is a special update zip that has no files, only preload
+ // it has to be in the section
+ V_memcpy( pBuffer, (byte*)m_pSection + nOffset, nBytes );
+ return nBytes;
+ }
+#endif
+
+ // Otherwise, do the read from the pack
+ m_mutex.Lock();
+
+ if ( fs_monitor_read_from_pack.GetInt() == 1 || ( fs_monitor_read_from_pack.GetInt() == 2 && ThreadInMainThread() ) )
+ {
+ // spew info about real i/o request
+ char szName[MAX_PATH];
+ IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
+ Msg( "Read From Pack: Sync I/O: Requested:%7d, Offset:0x%16.16llx, %s\n", nBytes, m_nBaseOffset + nOffset, szName );
+ }
+
+ int nBytesRead = 0;
+ // Seek to the start of the read area and perform the read: TODO: CHANGE THIS INTO A CFileHandle
+ if ( m_hPackFileHandleFS )
+ {
+ m_fs->FS_fseek( m_hPackFileHandleFS, m_nBaseOffset + nOffset, SEEK_SET );
+ nBytesRead = m_fs->FS_fread( pBuffer, nDestBytes, nBytes, m_hPackFileHandleFS );
+ }
+#if defined( SUPPORT_PACKED_STORE )
+ else
+ {
+ // We're a packfile embedded in a VPK
+ m_hPackFileHandleVPK.Seek( m_nBaseOffset + nOffset, FILESYSTEM_SEEK_HEAD );
+ nBytesRead = m_hPackFileHandleVPK.Read( pBuffer, nBytes );
+ }
+#endif
+ m_mutex.Unlock();
+
+ return nBytesRead;
+}
+
+//-----------------------------------------------------------------------------
+// Gets size, position, and index for a file in the pack.
+//-----------------------------------------------------------------------------
+bool CZipPackFile::GetFileInfo( const char *pFileName, int &nBaseIndex, int64 &nFileOffset, int &nOriginalSize, int &nCompressedSize, unsigned short &nCompressionMethod )
+{
+ char szCleanName[MAX_FILEPATH];
+ Q_strncpy( szCleanName, pFileName, sizeof( szCleanName ) );
+#ifdef _WIN32
+ Q_strlower( szCleanName );
+#endif
+ Q_FixSlashes( szCleanName );
+
+ if ( !Q_RemoveDotSlashes( szCleanName, CORRECT_PATH_SEPARATOR, false ) )
+ {
+ return false;
+ }
+
+ CZipPackFile::CPackFileEntry lookup;
+
+ // We may get passed non-canonicalized filenames, so we need to remove the ../ from the path
+ char szFixedName[MAX_PATH] = {0};
+ V_strcpy_safe( szFixedName, pFileName );
+ V_RemoveDotSlashes( szFixedName );
+
+ lookup.m_HashName = HashStringCaselessConventional( szFixedName );
+
+ int idx = m_PackFiles.Find( lookup );
+ if ( -1 != idx )
+ {
+ nFileOffset = m_PackFiles[idx].m_nPosition;
+ nOriginalSize = m_PackFiles[idx].m_nOriginalSize;
+ nCompressedSize = m_PackFiles[idx].m_nCompressedSize;
+ nBaseIndex = idx;
+ nCompressionMethod = m_PackFiles[idx].m_nCompressionMethod;
+ return true;
+ }
+
+ return false;
+}
+
+bool CZipPackFile::IndexToFilename( int nIndex, char *pBuffer, int nBufferSize )
+{
+ AssertMsg( nIndex >= 0 && nIndex < m_PackFiles.Count(), "Out of bounds vector access in IndexToFilename" );
+ if ( nIndex >= 0 )
+ {
+ m_fs->String( m_PackFiles[nIndex].m_hFileName, pBuffer, nBufferSize );
+ return true;
+ }
+
+ Q_strncpy( pBuffer, "unknown", nBufferSize );
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Find a file in the pack.
+//-----------------------------------------------------------------------------
+bool CZipPackFile::ContainsFile( const char *pFileName )
+{
+ int nIndex, nOriginalSize, nCompressedSize;
+ int64 nOffset;
+ unsigned short nCompressionMethod;
+ bool bFound = GetFileInfo( pFileName, nIndex, nOffset, nOriginalSize, nCompressedSize, nCompressionMethod );
+ return bFound;
+}
+
+//-----------------------------------------------------------------------------
+// Build a list of matching files and directories given a FindFirst() style wildcard
+//-----------------------------------------------------------------------------
+void CZipPackFile::GetFileAndDirLists( const char *pRawWildCard, CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput )
+{
+ // See also: VPKlib function with same name.
+
+ CUtlDict<int,int> AddedDirectories; // Used to remove duplicate paths
+
+ char szWildCard[MAX_PATH] = { 0 };
+ char szWildCardPath[MAX_PATH] = { 0 };
+ char szWildCardBase[MAX_PATH] = { 0 };
+ char szWildCardExt[MAX_PATH] = { 0 };
+
+ size_t nLenWildcardPath = 0;
+ size_t nLenWildcardBase = 0;
+
+ bool bBaseWildcard = true;
+ bool bExtWildcard = true;
+
+ //
+ // Parse the wildcard string into a base and extension used for string comparisons
+ //
+ V_strncpy( szWildCard, pRawWildCard, sizeof( szWildCard ) );
+ V_FixSlashes( szWildCard, '/' );
+ V_RemoveDotSlashes( szWildCard, '/', /* bRemoveDoubleSlashes */ true );
+
+ // Workaround edge case in crappy path code. ExtractFilePath extracts a/b/ from a/b/c/ but FileBase would return the empty string.
+ size_t nLenWildCard = V_strlen( szWildCard );
+ if ( nLenWildCard && szWildCard[ nLenWildCard - 1 ] == '/' )
+ {
+ V_strncpy( szWildCardPath, szWildCard, sizeof( szWildCardPath ) );
+ }
+ else
+ {
+ V_ExtractFilePath( szWildCard, szWildCardPath, sizeof( szWildCardPath ) );
+ }
+
+ V_FileBase( szWildCard, szWildCardBase, sizeof( szWildCardBase ) );
+ bool bWildcardHasExt = !!V_strrchr( szWildCard, '.' );
+ V_ExtractFileExtension( szWildCard, szWildCardExt, sizeof( szWildCardExt ) );
+
+ // From the pattern, we now have the directory path up to the file pattern, the filename base, and the filename
+ // extension.
+
+ // We don't support partial wildcards here (foo*bar.*). This support is massively inconsistent in our codebase and
+ // there's no one point where we implement it, so rather than trying to match one of our broken implementations
+ // (windows stdio is the only one I could find that was actually right), I'm going with "you shouldn't use this API
+ // for that".
+ bBaseWildcard = ( V_strcmp( szWildCardBase, "*" ) == 0 );
+ bExtWildcard = ( V_strcmp( szWildCardExt, "*" ) == 0 );
+
+ if ( !bWildcardHasExt && bBaseWildcard )
+ {
+ // For the special case of just '*' (and not, e.g., '*.') match '*.*'
+ bExtWildcard = true;
+ }
+
+ nLenWildcardPath = V_strlen( szWildCardPath );
+ nLenWildcardBase = V_strlen( szWildCardBase );
+
+ // Generate the list of directories and files that match the wildcard
+ //
+
+ // For each candidate we attempt to walk up its path and consider the directories it represents as well (the
+ // directories in a zip only exist in that files contain them, there are no empty directories)
+ FOR_EACH_VEC( m_PackFiles, filesIdx )
+ {
+ char szCandidateName[MAX_PATH] = { 0 };
+ IndexToFilename( filesIdx, szCandidateName, sizeof( szCandidateName ));
+
+ if ( !szCandidateName[0] )
+ {
+ continue;
+ }
+
+ // Check if this file starts with the wildcard selector's path.
+ // Note that we only ensure the prefix is the same. There are no specific entries for directories in a zip, they
+ // only exist in that files in the zip reference them, so handle subdirectory matches from filenames as well.
+ CUtlDict<int,int> ConsideredDirectories; // Will have duplicate directory matches when multiple files reside in them
+ if ( ( nLenWildcardPath && ( 0 == V_strnicmp( szCandidateName, szWildCardPath, nLenWildcardPath ) ) )
+ || ( !nLenWildcardPath && strchr( szCandidateName, '/' ) ) )
+ {
+ // Check if we matched because of a sub-directory, e.g. a/b/*.* would match /a/b/c/d/foo (in which case we
+ // want to add /a/b/c to the matched directories list, ignoring the actual specific file)
+ char szCandidateBaseName[MAX_PATH] = { 0 };
+ bool bIsDir = false;
+ size_t nSubDirLen = 0;
+ char *pSubDirSlash = strchr( szCandidateName + nLenWildcardPath, '/' );
+ if ( pSubDirSlash )
+ {
+ // This is a subdirectory match, drop everything after it and continue with it as the filename
+ nSubDirLen = (size_t)( (ptrdiff_t)pSubDirSlash - (ptrdiff_t)( szCandidateName + nLenWildcardPath ) );
+ V_strncpy( szCandidateBaseName, szCandidateName + nLenWildcardPath, nSubDirLen + 1 );
+ bIsDir = true;
+
+ // Early out if we already considered this exact directory from another file
+ if ( ConsideredDirectories.Find( szCandidateBaseName ) != ConsideredDirectories.InvalidIndex() )
+ {
+ continue;
+ }
+
+ ConsideredDirectories.Insert( szCandidateBaseName, 0 );
+ }
+ else
+ {
+ V_strncpy( szCandidateBaseName, szCandidateName + nLenWildcardPath, sizeof( szCandidateBaseName ) );
+ }
+
+ char *pExt = strchr( szCandidateBaseName, '.' );
+ if ( pExt )
+ {
+ // Null out the . and move to point to the extension
+ *pExt = '\0';
+ pExt++;
+ }
+
+ // Determine if this file matches the wildcart (*.*, *.ext, ext.*)
+ bool bBaseMatch = false;
+ bool bExtMatch = false;
+
+ // If we have a base dir name, and we have a szWildCardBase to match against
+ if ( bBaseWildcard )
+ bBaseMatch = true; // The base is the wildCard ("*"), so whatever we have as the base matches
+ else
+ bBaseMatch = ( 0 == V_stricmp( szCandidateBaseName, szWildCardBase ) );
+
+ // If we have an extension and we have a szWildCardExtension to mach against
+ if ( ( bExtWildcard && pExt ) || ( !pExt && !bWildcardHasExt ) )
+ bExtMatch = true;
+ else
+ bExtMatch = bWildcardHasExt && pExt && ( 0 == V_stricmp( pExt, szWildCardExt ) );
+
+ // If both parts match, then add it to the list
+ if ( bBaseMatch && bExtMatch )
+ {
+ if ( bIsDir )
+ {
+ // Pull up to the subdir we considered out of szCandidateName
+ size_t nMatchSize = nLenWildcardPath + nSubDirLen + 1;
+ char *pszFullMatch = new char[ nMatchSize ];
+ V_strncpy( pszFullMatch, szCandidateName, nMatchSize );
+ outDirnames.AddToTail( pszFullMatch );
+ }
+ else
+ {
+ size_t nMatchSize = V_strlen( szCandidateName ) + 1;
+ char *pszFullMatch = new char[ nMatchSize ];
+ V_strncpy( pszFullMatch, szCandidateName, nMatchSize );
+ outFilenames.AddToTail( pszFullMatch );
+ }
+ }
+ }
+ }
+
+ // Sort the output if requested
+ if ( bSortedOutput )
+ {
+ outDirnames.Sort( &CUtlStringList::SortFunc );
+ outFilenames.Sort( &CUtlStringList::SortFunc );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Set up the preload section
+//-----------------------------------------------------------------------------
+void CZipPackFile::SetupPreloadData()
+{
+ if ( m_pPreloadHeader || !m_nPreloadSectionSize )
+ {
+ // already loaded or not available
+ return;
+ }
+
+ MEM_ALLOC_CREDIT_( "xZip" );
+
+ void *pPreload;
+#if defined ( _X360 )
+ if ( m_pSection )
+ {
+ pPreload = (byte*)m_pSection + m_nPreloadSectionOffset;
+ }
+ else
+#endif
+ {
+ pPreload = malloc( m_nPreloadSectionSize );
+ if ( !pPreload )
+ {
+ return;
+ }
+
+ if ( IsX360() )
+ {
+ // 360 XZips are always dvd aligned
+ Assert( ( m_nPreloadSectionSize % XBOX_DVD_SECTORSIZE ) == 0 );
+ Assert( ( m_nPreloadSectionOffset % XBOX_DVD_SECTORSIZE ) == 0 );
+ }
+
+ // preload data is loaded as a single unbuffered i/o operation
+ ReadFromPack( -1, pPreload, -1, m_nPreloadSectionSize, m_nPreloadSectionOffset );
+ }
+
+ // setup the header
+ m_pPreloadHeader = (ZIP_PreloadHeader *)pPreload;
+
+ // setup the preload directory
+ m_pPreloadDirectory = (ZIP_PreloadDirectoryEntry *)((byte *)m_pPreloadHeader + sizeof( ZIP_PreloadHeader ) );
+
+ // setup the remap table
+ m_pPreloadRemapTable = (unsigned short *)((byte *)m_pPreloadDirectory + m_pPreloadHeader->PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) );
+
+ // set the preload data base
+ m_pPreloadData = (byte *)m_pPreloadRemapTable + m_pPreloadHeader->DirectoryEntries * sizeof( unsigned short );
+}
+
+void CZipPackFile::DiscardPreloadData()
+{
+ if ( !m_pPreloadHeader )
+ {
+ // already discarded
+ return;
+ }
+
+#if defined ( _X360 )
+ // a section is an alias, the header becomes an alias, not owned memory
+ if ( !m_pSection )
+ {
+ free( m_pPreloadHeader );
+ }
+#else
+ free( m_pPreloadHeader );
+#endif
+ m_pPreloadHeader = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Parse the zip file to build the file directory and preload section
+//-----------------------------------------------------------------------------
+bool CZipPackFile::Prepare( int64 fileLen, int64 nFileOfs )
+{
+ if ( !fileLen || fileLen < sizeof( ZIP_EndOfCentralDirRecord ) )
+ {
+ // nonsense zip
+ return false;
+ }
+
+ // Pack files are always little-endian
+ m_swap.ActivateByteSwapping( IsX360() );
+
+ m_FileLength = fileLen;
+ m_nBaseOffset = nFileOfs;
+
+ ZIP_EndOfCentralDirRecord rec = { 0 };
+
+ // Find and read the central header directory from its expected position at end of the file
+ bool bCentralDirRecord = false;
+ int64 offset = fileLen - sizeof( ZIP_EndOfCentralDirRecord );
+
+ // 360 can have an incompatible format
+ bool bCompatibleFormat = true;
+ if ( IsX360() )
+ {
+ // 360 has dependable exact zips, backup to handle possible xzip format
+ if ( offset - XZIP_COMMENT_LENGTH >= 0 )
+ {
+ offset -= XZIP_COMMENT_LENGTH;
+ }
+
+ // single i/o operation, scanning forward
+ char *pTemp = (char *)_alloca( fileLen - offset );
+ ReadFromPack( -1, pTemp, -1, fileLen - offset, offset );
+ while ( offset <= (int64)(fileLen - sizeof( ZIP_EndOfCentralDirRecord )) )
+ {
+ memcpy( &rec, pTemp, sizeof( ZIP_EndOfCentralDirRecord ) );
+ m_swap.SwapFieldsToTargetEndian( &rec );
+ if ( rec.signature == PKID( 5, 6 ) )
+ {
+ bCentralDirRecord = true;
+ if ( rec.commentLength >= 4 )
+ {
+ char *pComment = pTemp + sizeof( ZIP_EndOfCentralDirRecord );
+ if ( !V_strnicmp( pComment, "XZP2", 4 ) )
+ {
+ bCompatibleFormat = false;
+ }
+ }
+ break;
+ }
+ offset++;
+ pTemp++;
+ }
+ }
+ else
+ {
+ // scan entire file from expected location for central dir
+ for ( ; offset >= 0; offset-- )
+ {
+ ReadFromPack( -1, (void*)&rec, -1, sizeof( rec ), offset );
+ m_swap.SwapFieldsToTargetEndian( &rec );
+ if ( rec.signature == PKID( 5, 6 ) )
+ {
+ bCentralDirRecord = true;
+ break;
+ }
+ }
+ }
+ Assert( bCentralDirRecord );
+ if ( !bCentralDirRecord )
+ {
+ // no zip directory, bad zip
+ return false;
+ }
+
+ int numFilesInZip = rec.nCentralDirectoryEntries_Total;
+ if ( numFilesInZip <= 0 )
+ {
+ // empty valid zip
+ return true;
+ }
+
+ int firstFileIdx = 0;
+
+ MEM_ALLOC_CREDIT();
+
+ // read central directory into memory and parse
+ CUtlBuffer zipDirBuff( 0, rec.centralDirectorySize, 0 );
+ zipDirBuff.EnsureCapacity( rec.centralDirectorySize );
+ zipDirBuff.ActivateByteSwapping( IsX360() );
+ ReadFromPack( -1, zipDirBuff.Base(), -1, rec.centralDirectorySize, rec.startOfCentralDirOffset );
+ zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize );
+
+ ZIP_FileHeader zipFileHeader;
+ char filename[MAX_PATH] = { 0 };
+
+ // Check for a preload section, expected to be the first file in the zip
+ zipDirBuff.GetObjects( &zipFileHeader );
+ zipDirBuff.Get( filename, Min( (size_t)zipFileHeader.fileNameLength, sizeof(filename) - 1 ) );
+ if ( !V_stricmp( filename, PRELOAD_SECTION_NAME ) )
+ {
+ m_nPreloadSectionSize = zipFileHeader.uncompressedSize;
+ m_nPreloadSectionOffset = zipFileHeader.relativeOffsetOfLocalHeader +
+ sizeof( ZIP_LocalFileHeader ) +
+ zipFileHeader.fileNameLength +
+ zipFileHeader.extraFieldLength;
+ SetupPreloadData();
+
+ // Set up to extract the remaining files
+ int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0;
+ zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
+ firstFileIdx = 1;
+ }
+ else
+ {
+ if ( IsX360() )
+ {
+ // all 360 zip files are expected to have preload sections
+ // only during development, maps are allowed to lack them, due to auto-conversion
+ if ( !m_bIsMapPath || g_pFullFileSystem->GetDVDMode() == DVDMODE_STRICT )
+ {
+ Warning( "ZipFile '%s' missing preload section\n", m_ZipName.String() );
+ }
+ }
+
+ // No preload section, reset buffer pointer
+ zipDirBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ }
+
+ // Parse out central directory and determine absolute file positions of data.
+ // Supports uncompressed zip files, with or without preload sections
+ bool bSuccess = true;
+ char tmpString[MAX_PATH] = { 0 };
+
+ m_PackFiles.EnsureCapacity( numFilesInZip );
+
+ for ( int i = firstFileIdx; i < numFilesInZip; ++i )
+ {
+ CZipPackFile::CPackFileEntry lookup;
+ zipDirBuff.GetObjects( &zipFileHeader );
+
+ if ( zipFileHeader.signature != PKID( 1, 2 ) )
+ {
+ Warning( "Invalid pack file signature\n" );
+ bSuccess = false;
+ break;
+ }
+
+ if ( zipFileHeader.compressionMethod != ZIP_COMPRESSION_NONE && zipFileHeader.compressionMethod != ZIP_COMPRESSION_LZMA )
+ {
+ Warning( "Pack file uses unsupported compression method: %hi\n", zipFileHeader.compressionMethod );
+ bSuccess = false;
+ break;
+ }
+
+ Assert( zipFileHeader.fileNameLength < sizeof( tmpString ) );
+ unsigned int fileNameLen = Min( (size_t)zipFileHeader.fileNameLength, sizeof( tmpString ) - 1 );
+ zipDirBuff.Get( (void *)tmpString, fileNameLen );
+ tmpString[fileNameLen] = '\0';
+ Q_FixSlashes( tmpString );
+
+ lookup.m_hFileName = m_fs->FindOrAddFileName( tmpString );
+ lookup.m_HashName = HashStringCaselessConventional( tmpString );
+ lookup.m_nOriginalSize = zipFileHeader.uncompressedSize;
+ lookup.m_nCompressedSize = zipFileHeader.compressedSize;
+ lookup.m_nPosition = zipFileHeader.relativeOffsetOfLocalHeader +
+ sizeof( ZIP_LocalFileHeader ) +
+ zipFileHeader.fileNameLength +
+ zipFileHeader.extraFieldLength;
+ lookup.m_nCompressionMethod = zipFileHeader.compressionMethod;
+
+ // track the index to this file's possible preload directory entry
+ if ( m_pPreloadRemapTable )
+ {
+ lookup.m_nPreloadIdx = m_pPreloadRemapTable[i];
+ }
+ else
+ {
+ lookup.m_nPreloadIdx = INVALID_PRELOAD_ENTRY;
+ }
+ m_PackFiles.InsertNoSort( lookup );
+
+ int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0;
+ zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
+ }
+
+ m_PackFiles.RedoSort();
+
+ return bSuccess;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CZipPackFile::CZipPackFile( CBaseFileSystem* fs, void *pSection )
+ : m_PackFiles()
+{
+ m_fs = fs;
+ m_pPreloadDirectory = NULL;
+ m_pPreloadData = NULL;
+ m_pPreloadHeader = NULL;
+ m_pPreloadRemapTable = NULL;
+ m_nPreloadSectionOffset = 0;
+ m_nPreloadSectionSize = 0;
+
+#if defined( _X360 )
+ m_pSection = pSection;
+#endif
+}
+
+CZipPackFile::~CZipPackFile()
+{
+ DiscardPreloadData();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : src1 -
+// src2 -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CZipPackFile::CPackFileLessFunc::Less( CZipPackFile::CPackFileEntry const& src1, CZipPackFile::CPackFileEntry const& src2, void *pCtx )
+{
+ return ( src1.m_HashName < src2.m_HashName );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Zip Pack file handle implementation
+//-----------------------------------------------------------------------------
+CZipPackFileHandle::CZipPackFileHandle( CZipPackFile* pOwner, int64 nBase, unsigned int nLength, unsigned int nIndex, unsigned int nFilePointer )
+{
+ m_pOwner = pOwner;
+ m_nBase = nBase;
+ m_nLength = nLength;
+ m_nIndex = nIndex;
+ m_nFilePointer = nFilePointer;
+ pOwner->AddRef();
+}
+
+CZipPackFileHandle::~CZipPackFileHandle()
+{
+ m_pOwner->m_mutex.Lock();
+ --m_pOwner->m_nOpenFiles;
+ // XXX(johns) this doesn't go here, the hell
+ if ( m_pOwner->m_nOpenFiles == 0 && m_pOwner->m_bIsMapPath )
+ {
+ if ( m_pOwner->m_hPackFileHandleFS )
+ {
+ m_pOwner->FileSystem()->Trace_FClose( m_pOwner->m_hPackFileHandleFS );
+ m_pOwner->m_hPackFileHandleFS = NULL;
+ }
+ }
+ m_pOwner->Release();
+ m_pOwner->m_mutex.Unlock();
+}
+
+void CZipPackFileHandle::SetBufferSize( int nBytes )
+{
+ if ( m_pOwner->m_hPackFileHandleFS )
+ {
+ m_pOwner->FileSystem()->FS_setbufsize( m_pOwner->m_hPackFileHandleFS, nBytes );
+ }
+}
+
+int CZipPackFileHandle::GetSectorSize()
+{
+ return m_pOwner->GetSectorSize();
+}
+
+int64 CZipPackFileHandle::AbsoluteBaseOffset()
+{
+ return m_pOwner->GetPackFileBaseOffset() + m_nBase;
+}
+
+#if defined( _DEBUG ) && !defined( OSX )
+#include <atomic>
+static std::atomic<int> sLZMAPackFileHandles( 0 );
+#endif // defined( _DEBUG ) && !defined( OSX )
+
+CLZMAZipPackFileHandle::CLZMAZipPackFileHandle( CZipPackFile* pOwner, int64 nBase, unsigned int nOriginalSize, unsigned int nCompressedSize,
+ unsigned int nIndex, unsigned int nFilePointer )
+ : CZipPackFileHandle( pOwner, nBase, nCompressedSize, nIndex, nFilePointer ),
+ m_BackSeekBuffer( 0, PACKFILE_COMPRESSED_FILEHANDLE_SEEK_BUFFER ),
+ m_ReadBuffer( 0, PACKFILE_COMPRESSED_FILEHANDLE_READ_BUFFER ),
+ m_pLZMAStream( NULL ), m_nSeekPosition( 0 ), m_nOriginalSize( nOriginalSize )
+{
+ Reset();
+#if defined( _DEBUG ) && !defined( OSX )
+ if ( ++sLZMAPackFileHandles == PACKFILE_COMPRESSED_FILE_HANDLES_WARNING )
+ {
+ // By my count a live filehandle is currently around 270k, mostly due to the LZMA dictionary (256k) with the
+ // rest being the read/seek buffers.
+ Warning( "More than %u compressed file handles in use. "
+ "These carry large buffers around, and can cause high memory usage\n",
+ PACKFILE_COMPRESSED_FILE_HANDLES_WARNING );
+ }
+#endif // defined( _DEBUG ) && !defined( OSX )
+}
+
+CLZMAZipPackFileHandle::~CLZMAZipPackFileHandle()
+{
+ delete m_pLZMAStream;
+ m_pLZMAStream = NULL;
+#if defined( _DEBUG ) && !defined( OSX )
+ sLZMAPackFileHandles--;
+ Assert( sLZMAPackFileHandles >= 0 );
+#endif // defined( _DEBUG ) && !defined( OSX )
+}
+
+int CLZMAZipPackFileHandle::Read( void* pBuffer, int nDestSize, int nBytes )
+{
+ int nMaxRead = Min( Min( nDestSize, nBytes ), Size() - Tell() );
+ int nBytesRead = 0;
+
+ // If we have seeked backwards into our buffer, read from there first
+ int nBackSeek = m_BackSeekBuffer.TellPut() - m_BackSeekBuffer.TellGet();
+ Assert( nBackSeek >= 0 );
+ if ( nBackSeek > 0 )
+ {
+ int nBackSeekRead = Min( nBackSeek, nMaxRead );
+ m_BackSeekBuffer.Get( pBuffer, nBackSeekRead );
+ nBytesRead += nBackSeekRead;
+ }
+
+ // Done if nothing to read
+ if ( nMaxRead - nBytesRead <= 0 )
+ {
+ m_nSeekPosition += nBytesRead;
+ return nBytesRead;
+ }
+
+ // Read bytes not fulfilled by backbuffer
+ Assert( m_BackSeekBuffer.TellPut() == m_BackSeekBuffer.TellGet() );
+ while ( nBytesRead < nMaxRead )
+ {
+ // refill read buffer if empty
+ int nRemainingReadBuffer = FillReadBuffer();
+
+ // Consume from read buffer
+ unsigned int nCompressedBytesRead = 0;
+ unsigned int nOutputBytesWritten = 0;
+ bool bSuccess = m_pLZMAStream->Read( (unsigned char *)m_ReadBuffer.PeekGet(), nRemainingReadBuffer,
+ (unsigned char *)pBuffer + nBytesRead, nMaxRead - nBytesRead,
+ nCompressedBytesRead, nOutputBytesWritten );
+
+ if ( bSuccess )
+ {
+ // fixup get position
+ m_ReadBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, nCompressedBytesRead );
+
+ nBytesRead += nOutputBytesWritten;
+
+ AssertMsg( nCompressedBytesRead == (unsigned int)nRemainingReadBuffer || nBytesRead == nMaxRead,
+ "Should have consumed the readbuffer or reached nMaxRead" );
+
+ if ( nCompressedBytesRead == 0 && nOutputBytesWritten == 0 )
+ {
+ AssertMsg( nCompressedBytesRead > 0 || nOutputBytesWritten > 0,
+ "Stuck progress in read loop, aborting. Stream may be defunct." );
+ break;
+ }
+ }
+ else
+ {
+ Warning( "Pack file: reading from LZMA stream failed\n" );
+ break;
+ }
+ }
+
+ // Finally, store last bytes output to the backseek buffer
+
+ // If we read less than BackSeekBuffer.Size() bytes, shift the end of the old backseek buffer up
+ int nOldBackSeek = m_BackSeekBuffer.TellPut();
+ int nReuseBackSeek = Max( Min( m_BackSeekBuffer.Size() - nBytesRead, nOldBackSeek ), 0 );
+ if ( nReuseBackSeek )
+ {
+ // Shift the reused chunk to the front
+ V_memmove( m_BackSeekBuffer.Base(),
+ (unsigned char *)m_BackSeekBuffer.Base() + m_BackSeekBuffer.TellPut() - nReuseBackSeek,
+ nReuseBackSeek );
+ }
+
+ // Update get/put position
+ m_BackSeekBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nReuseBackSeek );
+ m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, nReuseBackSeek );
+
+ // Fill in remainder from what we just read
+ int nReadIntoBackSeek = Min( m_BackSeekBuffer.Size() - nReuseBackSeek, nBytesRead );
+ m_BackSeekBuffer.Put( (unsigned char *)pBuffer + nBytesRead - nReadIntoBackSeek, nReadIntoBackSeek );
+ m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, nReadIntoBackSeek );
+
+ m_nSeekPosition += nBytesRead;
+ return nBytesRead;
+}
+
+int CLZMAZipPackFileHandle::Seek( int nOffset, int nWhence )
+{
+ int nNewPosition = m_nSeekPosition;
+
+ if ( nWhence == SEEK_CUR )
+ {
+ nNewPosition = m_nSeekPosition + nOffset;
+ }
+ else if ( nWhence == SEEK_END )
+ {
+ nNewPosition = Size() + nOffset;
+ }
+ else if ( nWhence == SEEK_SET )
+ {
+ nNewPosition = nOffset;
+ }
+ else
+ {
+ AssertMsg( false, "Unknown seek type" );
+ }
+
+ nNewPosition = Min( Size(), nNewPosition );
+ nNewPosition = Max( 0, nNewPosition );
+
+ if ( nNewPosition == m_nSeekPosition )
+ {
+ return nNewPosition;
+ }
+
+ // Backwards seek
+ if ( nNewPosition < m_nSeekPosition )
+ {
+ int nBackSeekAvailable = m_BackSeekBuffer.TellGet();
+ int nDesiredBackSeek = m_nSeekPosition - nNewPosition;
+
+ if ( nBackSeekAvailable >= nDesiredBackSeek )
+ {
+ // Move get backwards into backseek buffer to account for seek
+ m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, -nDesiredBackSeek );
+ m_nSeekPosition = nNewPosition;
+ }
+ else
+ {
+ // Seeking backwards beyond our backseek buffer. Have to restart stream. This kills the performance.
+ Warning( "LZMA file handle: seeking backwards beyond backseek buffer size ( %u ), "
+ "replaying read & decompression of %u bytes. Should avoid large back seeks in compressed files or "
+ "increase backseek buffer sizing.",
+ m_BackSeekBuffer.Size(), nNewPosition );
+
+ // Reset to beginning of underlying stream
+ Reset();
+
+ // Fall through to performing a forward seek
+ }
+ }
+
+ // Forward seek
+ if ( nNewPosition > m_nSeekPosition )
+ {
+ // Can't actually seek forward without making decode progress. Issue fake reads until we've reached our target.
+ unsigned char dummyBuffer[COMPRESSED_SEEK_READ_CHUNK];
+ while ( nNewPosition > m_nSeekPosition )
+ {
+ int nReadSize = Min( nNewPosition - m_nSeekPosition, COMPRESSED_SEEK_READ_CHUNK );
+ unsigned int nBytesRead = Read( &dummyBuffer, sizeof(dummyBuffer), nReadSize );
+ m_nSeekPosition += nBytesRead;
+ if ( !nBytesRead )
+ {
+ Warning( "LZMA file handle: failed reading forward to desired seek position\n" );
+ break;
+ }
+ }
+ }
+
+ return m_nSeekPosition;
+}
+
+int CLZMAZipPackFileHandle::Tell()
+{
+ return m_nSeekPosition;
+}
+
+int CLZMAZipPackFileHandle::Size()
+{
+ return m_nOriginalSize;
+}
+
+int CLZMAZipPackFileHandle::FillReadBuffer()
+{
+ int nRemainingReadBuffer = m_ReadBuffer.TellPut() - m_ReadBuffer.TellGet();
+ int nRemainingCompressedBytes = CZipPackFileHandle::Size() - CZipPackFileHandle::Tell();
+
+ if ( nRemainingReadBuffer > 0 || nRemainingCompressedBytes <= 0 )
+ {
+ // No action if read buffer isn't empty
+ return nRemainingReadBuffer;
+ }
+
+ // Reset empty read buffer
+ m_ReadBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+ m_ReadBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ int nRefillSize = Min( nRemainingCompressedBytes, m_ReadBuffer.Size() );
+ int nRefillResult = CZipPackFileHandle::Read( m_ReadBuffer.PeekPut(), m_ReadBuffer.Size(), nRefillSize );
+ AssertMsg( nRefillSize == nRefillResult, "Don't expect to fail to read here" );
+
+ // Fixup put pointer after writing into buffer's memory
+ m_ReadBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, nRefillResult );
+
+ return nRefillResult;
+}
+
+void CLZMAZipPackFileHandle::Reset()
+{
+ // Seek underlying stream back to start
+ CZipPackFileHandle::Seek( SEEK_SET, 0 );
+
+ delete m_pLZMAStream;
+ m_pLZMAStream = new CLZMAStream();
+ m_pLZMAStream->InitZIPHeader( CZipPackFileHandle::Size(), m_nOriginalSize );
+ m_nSeekPosition = 0;
+ m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ m_BackSeekBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+ m_ReadBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ m_ReadBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+}