Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1127 lines
35 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "packfile.h"
  8. #include "zip_utils.h"
  9. #include "tier0/basetypes.h"
  10. #include "tier1/convar.h"
  11. #include "tier1/lzmaDecoder.h"
  12. #include "tier1/utlbuffer.h"
  13. #include "tier1/generichash.h"
  14. ConVar fs_monitor_read_from_pack( "fs_monitor_read_from_pack", "0", 0, "0:Off, 1:Any, 2:Sync only" );
  15. // How many bytes we should decode at a time when doing pseudo-reads to seek forward in a compressed file handle,
  16. // (affects maximum stack allocation by a forward seek)
  17. #define COMPRESSED_SEEK_READ_CHUNK 1024
  18. CPackFile::CPackFile()
  19. {
  20. m_FileLength = 0;
  21. m_hPackFileHandleFS = NULL;
  22. m_fs = NULL;
  23. m_nBaseOffset = 0;
  24. m_bIsMapPath = false;
  25. m_lPackFileTime = 0L;
  26. m_refCount = 0;
  27. m_nOpenFiles = 0;
  28. m_PackFileID = 0;
  29. }
  30. CPackFile::~CPackFile()
  31. {
  32. if ( m_nOpenFiles )
  33. {
  34. Error( "Closing pack file with %d open files!\n", m_nOpenFiles );
  35. }
  36. if ( m_hPackFileHandleFS )
  37. {
  38. m_fs->FS_fclose( m_hPackFileHandleFS );
  39. m_hPackFileHandleFS = NULL;
  40. }
  41. m_fs->m_ZipFiles.FindAndRemove( this );
  42. }
  43. int CPackFile::GetSectorSize()
  44. {
  45. if ( m_hPackFileHandleFS )
  46. {
  47. return m_fs->FS_GetSectorSize( m_hPackFileHandleFS );
  48. }
  49. #if defined( SUPPORT_PACKED_STORE )
  50. else if ( m_hPackFileHandleVPK )
  51. {
  52. return 2048;
  53. }
  54. #endif
  55. else
  56. {
  57. return -1;
  58. }
  59. }
  60. // Read a bit of the file from the pack file:
  61. int CZipPackFileHandle::Read( void* pBuffer, int nDestSize, int nBytes )
  62. {
  63. // Clamp nBytes to not go past the end of the file (async is still possible due to nDestSize)
  64. if ( nBytes + m_nFilePointer > m_nLength )
  65. {
  66. nBytes = m_nLength - m_nFilePointer;
  67. }
  68. // Seek to the given file pointer and read
  69. int nBytesRead = m_pOwner->ReadFromPack( m_nIndex, pBuffer, nDestSize, nBytes, m_nBase + m_nFilePointer );
  70. m_nFilePointer += nBytesRead;
  71. return nBytesRead;
  72. }
  73. // Seek around inside the pack:
  74. int CZipPackFileHandle::Seek( int nOffset, int nWhence )
  75. {
  76. if ( nWhence == SEEK_SET )
  77. {
  78. m_nFilePointer = nOffset;
  79. }
  80. else if ( nWhence == SEEK_CUR )
  81. {
  82. m_nFilePointer += nOffset;
  83. }
  84. else if ( nWhence == SEEK_END )
  85. {
  86. m_nFilePointer = m_nLength + nOffset;
  87. }
  88. // Clamp the file pointer to the actual bounds of the file:
  89. if ( m_nFilePointer > m_nLength )
  90. {
  91. m_nFilePointer = m_nLength;
  92. }
  93. return m_nFilePointer;
  94. }
  95. //-----------------------------------------------------------------------------
  96. // Open a file inside of a pack file.
  97. //-----------------------------------------------------------------------------
  98. CFileHandle *CZipPackFile::OpenFile( const char *pFileName, const char *pOptions )
  99. {
  100. int nIndex, nOriginalSize, nCompressedSize;
  101. int64 nPosition;
  102. unsigned short nCompressionMethod;
  103. // find the file's location in the pack
  104. if ( GetFileInfo( pFileName, nIndex, nPosition, nOriginalSize, nCompressedSize, nCompressionMethod ) )
  105. {
  106. m_mutex.Lock();
  107. #if defined( SUPPORT_PACKED_STORE )
  108. if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL && !m_hPackFileHandleVPK )
  109. #else
  110. if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL )
  111. #endif
  112. {
  113. // Try to open it as a regular file first
  114. m_hPackFileHandleFS = m_fs->Trace_FOpen( m_ZipName, "rb", 0, NULL );
  115. // !NOTE! Pack files inside of VPK not supported
  116. }
  117. m_nOpenFiles++;
  118. m_mutex.Unlock();
  119. CPackFileHandle* ph = NULL;
  120. if ( nCompressionMethod == ZIP_COMPRESSION_LZMA )
  121. {
  122. ph = new CLZMAZipPackFileHandle( this, nPosition, nOriginalSize, nCompressedSize, nIndex );
  123. }
  124. else
  125. {
  126. AssertMsg( nCompressionMethod == ZIP_COMPRESSION_NONE, "Unsupported compression type in zip pack file" );
  127. ph = new CZipPackFileHandle( this, nPosition, nOriginalSize, nIndex );
  128. }
  129. CFileHandle *fh = new CFileHandle( m_fs );
  130. fh->m_pPackFileHandle = ph;
  131. fh->m_nLength = nOriginalSize;
  132. // The default mode for fopen is text, so require 'b' for binary
  133. if ( strstr( pOptions, "b" ) == NULL )
  134. {
  135. fh->m_type = FT_PACK_TEXT;
  136. }
  137. else
  138. {
  139. fh->m_type = FT_PACK_BINARY;
  140. }
  141. #if !defined( _RETAIL )
  142. fh->SetName( pFileName );
  143. #endif
  144. return fh;
  145. }
  146. return NULL;
  147. }
  148. //-----------------------------------------------------------------------------
  149. // Get a directory entry from a pack's preload section
  150. //-----------------------------------------------------------------------------
  151. ZIP_PreloadDirectoryEntry* CZipPackFile::GetPreloadEntry( int nEntryIndex )
  152. {
  153. if ( !m_pPreloadHeader )
  154. {
  155. return NULL;
  156. }
  157. // If this entry doesn't have a corresponding preload entry, fail.
  158. if ( m_PackFiles[nEntryIndex].m_nPreloadIdx == INVALID_PRELOAD_ENTRY )
  159. {
  160. return NULL;
  161. }
  162. return &m_pPreloadDirectory[m_PackFiles[nEntryIndex].m_nPreloadIdx];
  163. }
  164. //-----------------------------------------------------------------------------
  165. // Read a file from the pack
  166. //-----------------------------------------------------------------------------
  167. int CZipPackFile::ReadFromPack( int nEntryIndex, void* pBuffer, int nDestBytes, int nBytes, int64 nOffset )
  168. {
  169. if ( nEntryIndex >= 0 )
  170. {
  171. if ( nBytes <= 0 )
  172. {
  173. return 0;
  174. }
  175. // X360TBD: This is screwy, it works because m_nBaseOffset is 0 for preload capable zips
  176. // It comes into play for files out of the embedded bsp zip,
  177. // this hackery is a pre-bias expecting ReadFromPack() do a symmetric post bias, yuck.
  178. // Attempt to satisfy request from possible preload section, otherwise fall through
  179. // A preload entry may be compressed
  180. ZIP_PreloadDirectoryEntry *pPreloadEntry = GetPreloadEntry( nEntryIndex );
  181. if ( pPreloadEntry )
  182. {
  183. // convert the absolute pack file position to a local file position
  184. int nLocalOffset = nOffset - m_PackFiles[nEntryIndex].m_nPosition;
  185. byte *pPreloadData = (byte*)m_pPreloadData + pPreloadEntry->DataOffset;
  186. if ( CLZMA::IsCompressed( pPreloadData ) )
  187. {
  188. unsigned int actualSize = CLZMA::GetActualSize( pPreloadData );
  189. if ( nLocalOffset + nBytes <= (int)actualSize )
  190. {
  191. // satisfy from compressed preload
  192. if ( fs_monitor_read_from_pack.GetInt() == 1 )
  193. {
  194. char szName[MAX_PATH];
  195. IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
  196. Msg( "Read From Pack: [Preload] Requested:%d, Compressed:%d, %s\n", nBytes, pPreloadEntry->Length, szName );
  197. }
  198. if ( nLocalOffset == 0 && nDestBytes >= (int)actualSize && nBytes == (int)actualSize )
  199. {
  200. // uncompress directly into caller's buffer
  201. CLZMA::Uncompress( (unsigned char *)pPreloadData, (unsigned char *)pBuffer );
  202. return nBytes;
  203. }
  204. // uncompress into temporary memory
  205. CUtlMemory< byte > tempMemory;
  206. tempMemory.EnsureCapacity( actualSize );
  207. CLZMA::Uncompress( pPreloadData, tempMemory.Base() );
  208. // copy only what caller expects
  209. V_memcpy( pBuffer, (byte*)tempMemory.Base() + nLocalOffset, nBytes );
  210. return nBytes;
  211. }
  212. }
  213. else if ( nLocalOffset + nBytes <= (int)pPreloadEntry->Length )
  214. {
  215. // satisfy from uncompressed preload
  216. if ( fs_monitor_read_from_pack.GetInt() == 1 )
  217. {
  218. char szName[MAX_PATH];
  219. IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
  220. Msg( "Read From Pack: [Preload] Requested:%d, Total:%d, %s\n", nBytes, pPreloadEntry->Length, szName );
  221. }
  222. V_memcpy( pBuffer, pPreloadData + nLocalOffset, nBytes );
  223. return nBytes;
  224. }
  225. }
  226. }
  227. #if defined ( _X360 )
  228. // fell through as a direct request from within the pack
  229. // intercept to possible embedded section
  230. if ( m_pSection )
  231. {
  232. // a section is a special update zip that has no files, only preload
  233. // it has to be in the section
  234. V_memcpy( pBuffer, (byte*)m_pSection + nOffset, nBytes );
  235. return nBytes;
  236. }
  237. #endif
  238. // Otherwise, do the read from the pack
  239. m_mutex.Lock();
  240. if ( fs_monitor_read_from_pack.GetInt() == 1 || ( fs_monitor_read_from_pack.GetInt() == 2 && ThreadInMainThread() ) )
  241. {
  242. // spew info about real i/o request
  243. char szName[MAX_PATH];
  244. IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
  245. Msg( "Read From Pack: Sync I/O: Requested:%7d, Offset:0x%16.16llx, %s\n", nBytes, m_nBaseOffset + nOffset, szName );
  246. }
  247. int nBytesRead = 0;
  248. // Seek to the start of the read area and perform the read: TODO: CHANGE THIS INTO A CFileHandle
  249. if ( m_hPackFileHandleFS )
  250. {
  251. m_fs->FS_fseek( m_hPackFileHandleFS, m_nBaseOffset + nOffset, SEEK_SET );
  252. nBytesRead = m_fs->FS_fread( pBuffer, nDestBytes, nBytes, m_hPackFileHandleFS );
  253. }
  254. #if defined( SUPPORT_PACKED_STORE )
  255. else
  256. {
  257. // We're a packfile embedded in a VPK
  258. m_hPackFileHandleVPK.Seek( m_nBaseOffset + nOffset, FILESYSTEM_SEEK_HEAD );
  259. nBytesRead = m_hPackFileHandleVPK.Read( pBuffer, nBytes );
  260. }
  261. #endif
  262. m_mutex.Unlock();
  263. return nBytesRead;
  264. }
  265. //-----------------------------------------------------------------------------
  266. // Gets size, position, and index for a file in the pack.
  267. //-----------------------------------------------------------------------------
  268. bool CZipPackFile::GetFileInfo( const char *pFileName, int &nBaseIndex, int64 &nFileOffset, int &nOriginalSize, int &nCompressedSize, unsigned short &nCompressionMethod )
  269. {
  270. char szCleanName[MAX_FILEPATH];
  271. Q_strncpy( szCleanName, pFileName, sizeof( szCleanName ) );
  272. #ifdef _WIN32
  273. Q_strlower( szCleanName );
  274. #endif
  275. Q_FixSlashes( szCleanName );
  276. if ( !Q_RemoveDotSlashes( szCleanName, CORRECT_PATH_SEPARATOR, false ) )
  277. {
  278. return false;
  279. }
  280. CZipPackFile::CPackFileEntry lookup;
  281. // We may get passed non-canonicalized filenames, so we need to remove the ../ from the path
  282. char szFixedName[MAX_PATH] = {0};
  283. V_strcpy_safe( szFixedName, pFileName );
  284. V_RemoveDotSlashes( szFixedName );
  285. lookup.m_HashName = HashStringCaselessConventional( szFixedName );
  286. int idx = m_PackFiles.Find( lookup );
  287. if ( -1 != idx )
  288. {
  289. nFileOffset = m_PackFiles[idx].m_nPosition;
  290. nOriginalSize = m_PackFiles[idx].m_nOriginalSize;
  291. nCompressedSize = m_PackFiles[idx].m_nCompressedSize;
  292. nBaseIndex = idx;
  293. nCompressionMethod = m_PackFiles[idx].m_nCompressionMethod;
  294. return true;
  295. }
  296. return false;
  297. }
  298. bool CZipPackFile::IndexToFilename( int nIndex, char *pBuffer, int nBufferSize )
  299. {
  300. AssertMsg( nIndex >= 0 && nIndex < m_PackFiles.Count(), "Out of bounds vector access in IndexToFilename" );
  301. if ( nIndex >= 0 )
  302. {
  303. m_fs->String( m_PackFiles[nIndex].m_hFileName, pBuffer, nBufferSize );
  304. return true;
  305. }
  306. Q_strncpy( pBuffer, "unknown", nBufferSize );
  307. return false;
  308. }
  309. //-----------------------------------------------------------------------------
  310. // Find a file in the pack.
  311. //-----------------------------------------------------------------------------
  312. bool CZipPackFile::ContainsFile( const char *pFileName )
  313. {
  314. int nIndex, nOriginalSize, nCompressedSize;
  315. int64 nOffset;
  316. unsigned short nCompressionMethod;
  317. bool bFound = GetFileInfo( pFileName, nIndex, nOffset, nOriginalSize, nCompressedSize, nCompressionMethod );
  318. return bFound;
  319. }
  320. //-----------------------------------------------------------------------------
  321. // Build a list of matching files and directories given a FindFirst() style wildcard
  322. //-----------------------------------------------------------------------------
  323. void CZipPackFile::GetFileAndDirLists( const char *pRawWildCard, CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput )
  324. {
  325. // See also: VPKlib function with same name.
  326. CUtlDict<int,int> AddedDirectories; // Used to remove duplicate paths
  327. char szWildCard[MAX_PATH] = { 0 };
  328. char szWildCardPath[MAX_PATH] = { 0 };
  329. char szWildCardBase[MAX_PATH] = { 0 };
  330. char szWildCardExt[MAX_PATH] = { 0 };
  331. size_t nLenWildcardPath = 0;
  332. size_t nLenWildcardBase = 0;
  333. bool bBaseWildcard = true;
  334. bool bExtWildcard = true;
  335. //
  336. // Parse the wildcard string into a base and extension used for string comparisons
  337. //
  338. V_strncpy( szWildCard, pRawWildCard, sizeof( szWildCard ) );
  339. V_FixSlashes( szWildCard, '/' );
  340. V_RemoveDotSlashes( szWildCard, '/', /* bRemoveDoubleSlashes */ true );
  341. // Workaround edge case in crappy path code. ExtractFilePath extracts a/b/ from a/b/c/ but FileBase would return the empty string.
  342. size_t nLenWildCard = V_strlen( szWildCard );
  343. if ( nLenWildCard && szWildCard[ nLenWildCard - 1 ] == '/' )
  344. {
  345. V_strncpy( szWildCardPath, szWildCard, sizeof( szWildCardPath ) );
  346. }
  347. else
  348. {
  349. V_ExtractFilePath( szWildCard, szWildCardPath, sizeof( szWildCardPath ) );
  350. }
  351. V_FileBase( szWildCard, szWildCardBase, sizeof( szWildCardBase ) );
  352. bool bWildcardHasExt = !!V_strrchr( szWildCard, '.' );
  353. V_ExtractFileExtension( szWildCard, szWildCardExt, sizeof( szWildCardExt ) );
  354. // From the pattern, we now have the directory path up to the file pattern, the filename base, and the filename
  355. // extension.
  356. // We don't support partial wildcards here (foo*bar.*). This support is massively inconsistent in our codebase and
  357. // there's no one point where we implement it, so rather than trying to match one of our broken implementations
  358. // (windows stdio is the only one I could find that was actually right), I'm going with "you shouldn't use this API
  359. // for that".
  360. bBaseWildcard = ( V_strcmp( szWildCardBase, "*" ) == 0 );
  361. bExtWildcard = ( V_strcmp( szWildCardExt, "*" ) == 0 );
  362. if ( !bWildcardHasExt && bBaseWildcard )
  363. {
  364. // For the special case of just '*' (and not, e.g., '*.') match '*.*'
  365. bExtWildcard = true;
  366. }
  367. nLenWildcardPath = V_strlen( szWildCardPath );
  368. nLenWildcardBase = V_strlen( szWildCardBase );
  369. // Generate the list of directories and files that match the wildcard
  370. //
  371. // For each candidate we attempt to walk up its path and consider the directories it represents as well (the
  372. // directories in a zip only exist in that files contain them, there are no empty directories)
  373. FOR_EACH_VEC( m_PackFiles, filesIdx )
  374. {
  375. char szCandidateName[MAX_PATH] = { 0 };
  376. IndexToFilename( filesIdx, szCandidateName, sizeof( szCandidateName ));
  377. if ( !szCandidateName[0] )
  378. {
  379. continue;
  380. }
  381. // Check if this file starts with the wildcard selector's path.
  382. // Note that we only ensure the prefix is the same. There are no specific entries for directories in a zip, they
  383. // only exist in that files in the zip reference them, so handle subdirectory matches from filenames as well.
  384. CUtlDict<int,int> ConsideredDirectories; // Will have duplicate directory matches when multiple files reside in them
  385. if ( ( nLenWildcardPath && ( 0 == V_strnicmp( szCandidateName, szWildCardPath, nLenWildcardPath ) ) )
  386. || ( !nLenWildcardPath && strchr( szCandidateName, '/' ) ) )
  387. {
  388. // Check if we matched because of a sub-directory, e.g. a/b/*.* would match /a/b/c/d/foo (in which case we
  389. // want to add /a/b/c to the matched directories list, ignoring the actual specific file)
  390. char szCandidateBaseName[MAX_PATH] = { 0 };
  391. bool bIsDir = false;
  392. size_t nSubDirLen = 0;
  393. char *pSubDirSlash = strchr( szCandidateName + nLenWildcardPath, '/' );
  394. if ( pSubDirSlash )
  395. {
  396. // This is a subdirectory match, drop everything after it and continue with it as the filename
  397. nSubDirLen = (size_t)( (ptrdiff_t)pSubDirSlash - (ptrdiff_t)( szCandidateName + nLenWildcardPath ) );
  398. V_strncpy( szCandidateBaseName, szCandidateName + nLenWildcardPath, nSubDirLen + 1 );
  399. bIsDir = true;
  400. // Early out if we already considered this exact directory from another file
  401. if ( ConsideredDirectories.Find( szCandidateBaseName ) != ConsideredDirectories.InvalidIndex() )
  402. {
  403. continue;
  404. }
  405. ConsideredDirectories.Insert( szCandidateBaseName, 0 );
  406. }
  407. else
  408. {
  409. V_strncpy( szCandidateBaseName, szCandidateName + nLenWildcardPath, sizeof( szCandidateBaseName ) );
  410. }
  411. char *pExt = strchr( szCandidateBaseName, '.' );
  412. if ( pExt )
  413. {
  414. // Null out the . and move to point to the extension
  415. *pExt = '\0';
  416. pExt++;
  417. }
  418. // Determine if this file matches the wildcart (*.*, *.ext, ext.*)
  419. bool bBaseMatch = false;
  420. bool bExtMatch = false;
  421. // If we have a base dir name, and we have a szWildCardBase to match against
  422. if ( bBaseWildcard )
  423. bBaseMatch = true; // The base is the wildCard ("*"), so whatever we have as the base matches
  424. else
  425. bBaseMatch = ( 0 == V_stricmp( szCandidateBaseName, szWildCardBase ) );
  426. // If we have an extension and we have a szWildCardExtension to mach against
  427. if ( ( bExtWildcard && pExt ) || ( !pExt && !bWildcardHasExt ) )
  428. bExtMatch = true;
  429. else
  430. bExtMatch = bWildcardHasExt && pExt && ( 0 == V_stricmp( pExt, szWildCardExt ) );
  431. // If both parts match, then add it to the list
  432. if ( bBaseMatch && bExtMatch )
  433. {
  434. if ( bIsDir )
  435. {
  436. // Pull up to the subdir we considered out of szCandidateName
  437. size_t nMatchSize = nLenWildcardPath + nSubDirLen + 1;
  438. char *pszFullMatch = new char[ nMatchSize ];
  439. V_strncpy( pszFullMatch, szCandidateName, nMatchSize );
  440. outDirnames.AddToTail( pszFullMatch );
  441. }
  442. else
  443. {
  444. size_t nMatchSize = V_strlen( szCandidateName ) + 1;
  445. char *pszFullMatch = new char[ nMatchSize ];
  446. V_strncpy( pszFullMatch, szCandidateName, nMatchSize );
  447. outFilenames.AddToTail( pszFullMatch );
  448. }
  449. }
  450. }
  451. }
  452. // Sort the output if requested
  453. if ( bSortedOutput )
  454. {
  455. outDirnames.Sort( &CUtlStringList::SortFunc );
  456. outFilenames.Sort( &CUtlStringList::SortFunc );
  457. }
  458. }
  459. //-----------------------------------------------------------------------------
  460. // Set up the preload section
  461. //-----------------------------------------------------------------------------
  462. void CZipPackFile::SetupPreloadData()
  463. {
  464. if ( m_pPreloadHeader || !m_nPreloadSectionSize )
  465. {
  466. // already loaded or not available
  467. return;
  468. }
  469. MEM_ALLOC_CREDIT_( "xZip" );
  470. void *pPreload;
  471. #if defined ( _X360 )
  472. if ( m_pSection )
  473. {
  474. pPreload = (byte*)m_pSection + m_nPreloadSectionOffset;
  475. }
  476. else
  477. #endif
  478. {
  479. pPreload = malloc( m_nPreloadSectionSize );
  480. if ( !pPreload )
  481. {
  482. return;
  483. }
  484. if ( IsX360() )
  485. {
  486. // 360 XZips are always dvd aligned
  487. Assert( ( m_nPreloadSectionSize % XBOX_DVD_SECTORSIZE ) == 0 );
  488. Assert( ( m_nPreloadSectionOffset % XBOX_DVD_SECTORSIZE ) == 0 );
  489. }
  490. // preload data is loaded as a single unbuffered i/o operation
  491. ReadFromPack( -1, pPreload, -1, m_nPreloadSectionSize, m_nPreloadSectionOffset );
  492. }
  493. // setup the header
  494. m_pPreloadHeader = (ZIP_PreloadHeader *)pPreload;
  495. // setup the preload directory
  496. m_pPreloadDirectory = (ZIP_PreloadDirectoryEntry *)((byte *)m_pPreloadHeader + sizeof( ZIP_PreloadHeader ) );
  497. // setup the remap table
  498. m_pPreloadRemapTable = (unsigned short *)((byte *)m_pPreloadDirectory + m_pPreloadHeader->PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) );
  499. // set the preload data base
  500. m_pPreloadData = (byte *)m_pPreloadRemapTable + m_pPreloadHeader->DirectoryEntries * sizeof( unsigned short );
  501. }
  502. void CZipPackFile::DiscardPreloadData()
  503. {
  504. if ( !m_pPreloadHeader )
  505. {
  506. // already discarded
  507. return;
  508. }
  509. #if defined ( _X360 )
  510. // a section is an alias, the header becomes an alias, not owned memory
  511. if ( !m_pSection )
  512. {
  513. free( m_pPreloadHeader );
  514. }
  515. #else
  516. free( m_pPreloadHeader );
  517. #endif
  518. m_pPreloadHeader = NULL;
  519. }
  520. //-----------------------------------------------------------------------------
  521. // Parse the zip file to build the file directory and preload section
  522. //-----------------------------------------------------------------------------
  523. bool CZipPackFile::Prepare( int64 fileLen, int64 nFileOfs )
  524. {
  525. if ( !fileLen || fileLen < sizeof( ZIP_EndOfCentralDirRecord ) )
  526. {
  527. // nonsense zip
  528. return false;
  529. }
  530. // Pack files are always little-endian
  531. m_swap.ActivateByteSwapping( IsX360() );
  532. m_FileLength = fileLen;
  533. m_nBaseOffset = nFileOfs;
  534. ZIP_EndOfCentralDirRecord rec = { 0 };
  535. // Find and read the central header directory from its expected position at end of the file
  536. bool bCentralDirRecord = false;
  537. int64 offset = fileLen - sizeof( ZIP_EndOfCentralDirRecord );
  538. // 360 can have an incompatible format
  539. bool bCompatibleFormat = true;
  540. if ( IsX360() )
  541. {
  542. // 360 has dependable exact zips, backup to handle possible xzip format
  543. if ( offset - XZIP_COMMENT_LENGTH >= 0 )
  544. {
  545. offset -= XZIP_COMMENT_LENGTH;
  546. }
  547. // single i/o operation, scanning forward
  548. char *pTemp = (char *)_alloca( fileLen - offset );
  549. ReadFromPack( -1, pTemp, -1, fileLen - offset, offset );
  550. while ( offset <= (int64)(fileLen - sizeof( ZIP_EndOfCentralDirRecord )) )
  551. {
  552. memcpy( &rec, pTemp, sizeof( ZIP_EndOfCentralDirRecord ) );
  553. m_swap.SwapFieldsToTargetEndian( &rec );
  554. if ( rec.signature == PKID( 5, 6 ) )
  555. {
  556. bCentralDirRecord = true;
  557. if ( rec.commentLength >= 4 )
  558. {
  559. char *pComment = pTemp + sizeof( ZIP_EndOfCentralDirRecord );
  560. if ( !V_strnicmp( pComment, "XZP2", 4 ) )
  561. {
  562. bCompatibleFormat = false;
  563. }
  564. }
  565. break;
  566. }
  567. offset++;
  568. pTemp++;
  569. }
  570. }
  571. else
  572. {
  573. // scan entire file from expected location for central dir
  574. for ( ; offset >= 0; offset-- )
  575. {
  576. ReadFromPack( -1, (void*)&rec, -1, sizeof( rec ), offset );
  577. m_swap.SwapFieldsToTargetEndian( &rec );
  578. if ( rec.signature == PKID( 5, 6 ) )
  579. {
  580. bCentralDirRecord = true;
  581. break;
  582. }
  583. }
  584. }
  585. Assert( bCentralDirRecord );
  586. if ( !bCentralDirRecord )
  587. {
  588. // no zip directory, bad zip
  589. return false;
  590. }
  591. int numFilesInZip = rec.nCentralDirectoryEntries_Total;
  592. if ( numFilesInZip <= 0 )
  593. {
  594. // empty valid zip
  595. return true;
  596. }
  597. int firstFileIdx = 0;
  598. MEM_ALLOC_CREDIT();
  599. // read central directory into memory and parse
  600. CUtlBuffer zipDirBuff( 0, rec.centralDirectorySize, 0 );
  601. zipDirBuff.EnsureCapacity( rec.centralDirectorySize );
  602. zipDirBuff.ActivateByteSwapping( IsX360() );
  603. ReadFromPack( -1, zipDirBuff.Base(), -1, rec.centralDirectorySize, rec.startOfCentralDirOffset );
  604. zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize );
  605. ZIP_FileHeader zipFileHeader;
  606. char filename[MAX_PATH] = { 0 };
  607. // Check for a preload section, expected to be the first file in the zip
  608. zipDirBuff.GetObjects( &zipFileHeader );
  609. zipDirBuff.Get( filename, Min( (size_t)zipFileHeader.fileNameLength, sizeof(filename) - 1 ) );
  610. if ( !V_stricmp( filename, PRELOAD_SECTION_NAME ) )
  611. {
  612. m_nPreloadSectionSize = zipFileHeader.uncompressedSize;
  613. m_nPreloadSectionOffset = zipFileHeader.relativeOffsetOfLocalHeader +
  614. sizeof( ZIP_LocalFileHeader ) +
  615. zipFileHeader.fileNameLength +
  616. zipFileHeader.extraFieldLength;
  617. SetupPreloadData();
  618. // Set up to extract the remaining files
  619. int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0;
  620. zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
  621. firstFileIdx = 1;
  622. }
  623. else
  624. {
  625. if ( IsX360() )
  626. {
  627. // all 360 zip files are expected to have preload sections
  628. // only during development, maps are allowed to lack them, due to auto-conversion
  629. if ( !m_bIsMapPath || g_pFullFileSystem->GetDVDMode() == DVDMODE_STRICT )
  630. {
  631. Warning( "ZipFile '%s' missing preload section\n", m_ZipName.String() );
  632. }
  633. }
  634. // No preload section, reset buffer pointer
  635. zipDirBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  636. }
  637. // Parse out central directory and determine absolute file positions of data.
  638. // Supports uncompressed zip files, with or without preload sections
  639. bool bSuccess = true;
  640. char tmpString[MAX_PATH] = { 0 };
  641. m_PackFiles.EnsureCapacity( numFilesInZip );
  642. for ( int i = firstFileIdx; i < numFilesInZip; ++i )
  643. {
  644. CZipPackFile::CPackFileEntry lookup;
  645. zipDirBuff.GetObjects( &zipFileHeader );
  646. if ( zipFileHeader.signature != PKID( 1, 2 ) )
  647. {
  648. Warning( "Invalid pack file signature\n" );
  649. bSuccess = false;
  650. break;
  651. }
  652. if ( zipFileHeader.compressionMethod != ZIP_COMPRESSION_NONE && zipFileHeader.compressionMethod != ZIP_COMPRESSION_LZMA )
  653. {
  654. Warning( "Pack file uses unsupported compression method: %hi\n", zipFileHeader.compressionMethod );
  655. bSuccess = false;
  656. break;
  657. }
  658. Assert( zipFileHeader.fileNameLength < sizeof( tmpString ) );
  659. unsigned int fileNameLen = Min( (size_t)zipFileHeader.fileNameLength, sizeof( tmpString ) - 1 );
  660. zipDirBuff.Get( (void *)tmpString, fileNameLen );
  661. tmpString[fileNameLen] = '\0';
  662. Q_FixSlashes( tmpString );
  663. lookup.m_hFileName = m_fs->FindOrAddFileName( tmpString );
  664. lookup.m_HashName = HashStringCaselessConventional( tmpString );
  665. lookup.m_nOriginalSize = zipFileHeader.uncompressedSize;
  666. lookup.m_nCompressedSize = zipFileHeader.compressedSize;
  667. lookup.m_nPosition = zipFileHeader.relativeOffsetOfLocalHeader +
  668. sizeof( ZIP_LocalFileHeader ) +
  669. zipFileHeader.fileNameLength +
  670. zipFileHeader.extraFieldLength;
  671. lookup.m_nCompressionMethod = zipFileHeader.compressionMethod;
  672. // track the index to this file's possible preload directory entry
  673. if ( m_pPreloadRemapTable )
  674. {
  675. lookup.m_nPreloadIdx = m_pPreloadRemapTable[i];
  676. }
  677. else
  678. {
  679. lookup.m_nPreloadIdx = INVALID_PRELOAD_ENTRY;
  680. }
  681. m_PackFiles.InsertNoSort( lookup );
  682. int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0;
  683. zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
  684. }
  685. m_PackFiles.RedoSort();
  686. return bSuccess;
  687. }
  688. //-----------------------------------------------------------------------------
  689. //
  690. //-----------------------------------------------------------------------------
  691. CZipPackFile::CZipPackFile( CBaseFileSystem* fs, void *pSection )
  692. : m_PackFiles()
  693. {
  694. m_fs = fs;
  695. m_pPreloadDirectory = NULL;
  696. m_pPreloadData = NULL;
  697. m_pPreloadHeader = NULL;
  698. m_pPreloadRemapTable = NULL;
  699. m_nPreloadSectionOffset = 0;
  700. m_nPreloadSectionSize = 0;
  701. #if defined( _X360 )
  702. m_pSection = pSection;
  703. #endif
  704. }
  705. CZipPackFile::~CZipPackFile()
  706. {
  707. DiscardPreloadData();
  708. }
  709. //-----------------------------------------------------------------------------
  710. // Purpose:
  711. // Input : src1 -
  712. // src2 -
  713. // Output : Returns true on success, false on failure.
  714. //-----------------------------------------------------------------------------
  715. bool CZipPackFile::CPackFileLessFunc::Less( CZipPackFile::CPackFileEntry const& src1, CZipPackFile::CPackFileEntry const& src2, void *pCtx )
  716. {
  717. return ( src1.m_HashName < src2.m_HashName );
  718. }
  719. //-----------------------------------------------------------------------------
  720. // Purpose: Zip Pack file handle implementation
  721. //-----------------------------------------------------------------------------
  722. CZipPackFileHandle::CZipPackFileHandle( CZipPackFile* pOwner, int64 nBase, unsigned int nLength, unsigned int nIndex, unsigned int nFilePointer )
  723. {
  724. m_pOwner = pOwner;
  725. m_nBase = nBase;
  726. m_nLength = nLength;
  727. m_nIndex = nIndex;
  728. m_nFilePointer = nFilePointer;
  729. pOwner->AddRef();
  730. }
  731. CZipPackFileHandle::~CZipPackFileHandle()
  732. {
  733. m_pOwner->m_mutex.Lock();
  734. --m_pOwner->m_nOpenFiles;
  735. // XXX(johns) this doesn't go here, the hell
  736. if ( m_pOwner->m_nOpenFiles == 0 && m_pOwner->m_bIsMapPath )
  737. {
  738. if ( m_pOwner->m_hPackFileHandleFS )
  739. {
  740. m_pOwner->FileSystem()->Trace_FClose( m_pOwner->m_hPackFileHandleFS );
  741. m_pOwner->m_hPackFileHandleFS = NULL;
  742. }
  743. }
  744. m_pOwner->Release();
  745. m_pOwner->m_mutex.Unlock();
  746. }
  747. void CZipPackFileHandle::SetBufferSize( int nBytes )
  748. {
  749. if ( m_pOwner->m_hPackFileHandleFS )
  750. {
  751. m_pOwner->FileSystem()->FS_setbufsize( m_pOwner->m_hPackFileHandleFS, nBytes );
  752. }
  753. }
  754. int CZipPackFileHandle::GetSectorSize()
  755. {
  756. return m_pOwner->GetSectorSize();
  757. }
  758. int64 CZipPackFileHandle::AbsoluteBaseOffset()
  759. {
  760. return m_pOwner->GetPackFileBaseOffset() + m_nBase;
  761. }
  762. #if defined( _DEBUG ) && !defined( OSX )
  763. #include <atomic>
  764. static std::atomic<int> sLZMAPackFileHandles( 0 );
  765. #endif // defined( _DEBUG ) && !defined( OSX )
  766. CLZMAZipPackFileHandle::CLZMAZipPackFileHandle( CZipPackFile* pOwner, int64 nBase, unsigned int nOriginalSize, unsigned int nCompressedSize,
  767. unsigned int nIndex, unsigned int nFilePointer )
  768. : CZipPackFileHandle( pOwner, nBase, nCompressedSize, nIndex, nFilePointer ),
  769. m_BackSeekBuffer( 0, PACKFILE_COMPRESSED_FILEHANDLE_SEEK_BUFFER ),
  770. m_ReadBuffer( 0, PACKFILE_COMPRESSED_FILEHANDLE_READ_BUFFER ),
  771. m_pLZMAStream( NULL ), m_nSeekPosition( 0 ), m_nOriginalSize( nOriginalSize )
  772. {
  773. Reset();
  774. #if defined( _DEBUG ) && !defined( OSX )
  775. if ( ++sLZMAPackFileHandles == PACKFILE_COMPRESSED_FILE_HANDLES_WARNING )
  776. {
  777. // By my count a live filehandle is currently around 270k, mostly due to the LZMA dictionary (256k) with the
  778. // rest being the read/seek buffers.
  779. Warning( "More than %u compressed file handles in use. "
  780. "These carry large buffers around, and can cause high memory usage\n",
  781. PACKFILE_COMPRESSED_FILE_HANDLES_WARNING );
  782. }
  783. #endif // defined( _DEBUG ) && !defined( OSX )
  784. }
  785. CLZMAZipPackFileHandle::~CLZMAZipPackFileHandle()
  786. {
  787. delete m_pLZMAStream;
  788. m_pLZMAStream = NULL;
  789. #if defined( _DEBUG ) && !defined( OSX )
  790. sLZMAPackFileHandles--;
  791. Assert( sLZMAPackFileHandles >= 0 );
  792. #endif // defined( _DEBUG ) && !defined( OSX )
  793. }
  794. int CLZMAZipPackFileHandle::Read( void* pBuffer, int nDestSize, int nBytes )
  795. {
  796. int nMaxRead = Min( Min( nDestSize, nBytes ), Size() - Tell() );
  797. int nBytesRead = 0;
  798. // If we have seeked backwards into our buffer, read from there first
  799. int nBackSeek = m_BackSeekBuffer.TellPut() - m_BackSeekBuffer.TellGet();
  800. Assert( nBackSeek >= 0 );
  801. if ( nBackSeek > 0 )
  802. {
  803. int nBackSeekRead = Min( nBackSeek, nMaxRead );
  804. m_BackSeekBuffer.Get( pBuffer, nBackSeekRead );
  805. nBytesRead += nBackSeekRead;
  806. }
  807. // Done if nothing to read
  808. if ( nMaxRead - nBytesRead <= 0 )
  809. {
  810. m_nSeekPosition += nBytesRead;
  811. return nBytesRead;
  812. }
  813. // Read bytes not fulfilled by backbuffer
  814. Assert( m_BackSeekBuffer.TellPut() == m_BackSeekBuffer.TellGet() );
  815. while ( nBytesRead < nMaxRead )
  816. {
  817. // refill read buffer if empty
  818. int nRemainingReadBuffer = FillReadBuffer();
  819. // Consume from read buffer
  820. unsigned int nCompressedBytesRead = 0;
  821. unsigned int nOutputBytesWritten = 0;
  822. bool bSuccess = m_pLZMAStream->Read( (unsigned char *)m_ReadBuffer.PeekGet(), nRemainingReadBuffer,
  823. (unsigned char *)pBuffer + nBytesRead, nMaxRead - nBytesRead,
  824. nCompressedBytesRead, nOutputBytesWritten );
  825. if ( bSuccess )
  826. {
  827. // fixup get position
  828. m_ReadBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, nCompressedBytesRead );
  829. nBytesRead += nOutputBytesWritten;
  830. AssertMsg( nCompressedBytesRead == (unsigned int)nRemainingReadBuffer || nBytesRead == nMaxRead,
  831. "Should have consumed the readbuffer or reached nMaxRead" );
  832. if ( nCompressedBytesRead == 0 && nOutputBytesWritten == 0 )
  833. {
  834. AssertMsg( nCompressedBytesRead > 0 || nOutputBytesWritten > 0,
  835. "Stuck progress in read loop, aborting. Stream may be defunct." );
  836. break;
  837. }
  838. }
  839. else
  840. {
  841. Warning( "Pack file: reading from LZMA stream failed\n" );
  842. break;
  843. }
  844. }
  845. // Finally, store last bytes output to the backseek buffer
  846. // If we read less than BackSeekBuffer.Size() bytes, shift the end of the old backseek buffer up
  847. int nOldBackSeek = m_BackSeekBuffer.TellPut();
  848. int nReuseBackSeek = Max( Min( m_BackSeekBuffer.Size() - nBytesRead, nOldBackSeek ), 0 );
  849. if ( nReuseBackSeek )
  850. {
  851. // Shift the reused chunk to the front
  852. V_memmove( m_BackSeekBuffer.Base(),
  853. (unsigned char *)m_BackSeekBuffer.Base() + m_BackSeekBuffer.TellPut() - nReuseBackSeek,
  854. nReuseBackSeek );
  855. }
  856. // Update get/put position
  857. m_BackSeekBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nReuseBackSeek );
  858. m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, nReuseBackSeek );
  859. // Fill in remainder from what we just read
  860. int nReadIntoBackSeek = Min( m_BackSeekBuffer.Size() - nReuseBackSeek, nBytesRead );
  861. m_BackSeekBuffer.Put( (unsigned char *)pBuffer + nBytesRead - nReadIntoBackSeek, nReadIntoBackSeek );
  862. m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, nReadIntoBackSeek );
  863. m_nSeekPosition += nBytesRead;
  864. return nBytesRead;
  865. }
  866. int CLZMAZipPackFileHandle::Seek( int nOffset, int nWhence )
  867. {
  868. int nNewPosition = m_nSeekPosition;
  869. if ( nWhence == SEEK_CUR )
  870. {
  871. nNewPosition = m_nSeekPosition + nOffset;
  872. }
  873. else if ( nWhence == SEEK_END )
  874. {
  875. nNewPosition = Size() + nOffset;
  876. }
  877. else if ( nWhence == SEEK_SET )
  878. {
  879. nNewPosition = nOffset;
  880. }
  881. else
  882. {
  883. AssertMsg( false, "Unknown seek type" );
  884. }
  885. nNewPosition = Min( Size(), nNewPosition );
  886. nNewPosition = Max( 0, nNewPosition );
  887. if ( nNewPosition == m_nSeekPosition )
  888. {
  889. return nNewPosition;
  890. }
  891. // Backwards seek
  892. if ( nNewPosition < m_nSeekPosition )
  893. {
  894. int nBackSeekAvailable = m_BackSeekBuffer.TellGet();
  895. int nDesiredBackSeek = m_nSeekPosition - nNewPosition;
  896. if ( nBackSeekAvailable >= nDesiredBackSeek )
  897. {
  898. // Move get backwards into backseek buffer to account for seek
  899. m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, -nDesiredBackSeek );
  900. m_nSeekPosition = nNewPosition;
  901. }
  902. else
  903. {
  904. // Seeking backwards beyond our backseek buffer. Have to restart stream. This kills the performance.
  905. Warning( "LZMA file handle: seeking backwards beyond backseek buffer size ( %u ), "
  906. "replaying read & decompression of %u bytes. Should avoid large back seeks in compressed files or "
  907. "increase backseek buffer sizing.",
  908. m_BackSeekBuffer.Size(), nNewPosition );
  909. // Reset to beginning of underlying stream
  910. Reset();
  911. // Fall through to performing a forward seek
  912. }
  913. }
  914. // Forward seek
  915. if ( nNewPosition > m_nSeekPosition )
  916. {
  917. // Can't actually seek forward without making decode progress. Issue fake reads until we've reached our target.
  918. unsigned char dummyBuffer[COMPRESSED_SEEK_READ_CHUNK];
  919. while ( nNewPosition > m_nSeekPosition )
  920. {
  921. int nReadSize = Min( nNewPosition - m_nSeekPosition, COMPRESSED_SEEK_READ_CHUNK );
  922. unsigned int nBytesRead = Read( &dummyBuffer, sizeof(dummyBuffer), nReadSize );
  923. m_nSeekPosition += nBytesRead;
  924. if ( !nBytesRead )
  925. {
  926. Warning( "LZMA file handle: failed reading forward to desired seek position\n" );
  927. break;
  928. }
  929. }
  930. }
  931. return m_nSeekPosition;
  932. }
  933. int CLZMAZipPackFileHandle::Tell()
  934. {
  935. return m_nSeekPosition;
  936. }
  937. int CLZMAZipPackFileHandle::Size()
  938. {
  939. return m_nOriginalSize;
  940. }
  941. int CLZMAZipPackFileHandle::FillReadBuffer()
  942. {
  943. int nRemainingReadBuffer = m_ReadBuffer.TellPut() - m_ReadBuffer.TellGet();
  944. int nRemainingCompressedBytes = CZipPackFileHandle::Size() - CZipPackFileHandle::Tell();
  945. if ( nRemainingReadBuffer > 0 || nRemainingCompressedBytes <= 0 )
  946. {
  947. // No action if read buffer isn't empty
  948. return nRemainingReadBuffer;
  949. }
  950. // Reset empty read buffer
  951. m_ReadBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  952. m_ReadBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  953. int nRefillSize = Min( nRemainingCompressedBytes, m_ReadBuffer.Size() );
  954. int nRefillResult = CZipPackFileHandle::Read( m_ReadBuffer.PeekPut(), m_ReadBuffer.Size(), nRefillSize );
  955. AssertMsg( nRefillSize == nRefillResult, "Don't expect to fail to read here" );
  956. // Fixup put pointer after writing into buffer's memory
  957. m_ReadBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, nRefillResult );
  958. return nRefillResult;
  959. }
  960. void CLZMAZipPackFileHandle::Reset()
  961. {
  962. // Seek underlying stream back to start
  963. CZipPackFileHandle::Seek( SEEK_SET, 0 );
  964. delete m_pLZMAStream;
  965. m_pLZMAStream = new CLZMAStream();
  966. m_pLZMAStream->InitZIPHeader( CZipPackFileHandle::Size(), m_nOriginalSize );
  967. m_nSeekPosition = 0;
  968. m_BackSeekBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  969. m_BackSeekBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  970. m_ReadBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  971. m_ReadBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  972. }