source-engine/public/zip_utils.cpp

1873 lines
54 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
// If we are going to include windows.h then we need to disable protected_things.h
// or else we get many warnings.
#undef PROTECTED_THINGS_ENABLE
#include <tier0/platform.h>
#ifdef IS_WINDOWS_PC
#include <windows.h>
#else
#define INVALID_HANDLE_VALUE (void *)0
#define FILE_BEGIN SEEK_SET
#define FILE_END SEEK_END
#endif
#include "utlbuffer.h"
#include "utllinkedlist.h"
#include "zip_utils.h"
#include "zip_uncompressed.h"
#include "checksum_crc.h"
#include "byteswap.h"
#include "utlstring.h"
#include "tier1/lzmaDecoder.h"
// Not every user of zip utils wants to link LZMA encoder
#ifdef ZIP_SUPPORT_LZMA_ENCODE
#include "lzma/lzma.h"
#endif
#include "tier0/memdbgon.h"
// Data descriptions for byte swapping - only needed
// for structures that are written to file for use by the game.
BEGIN_BYTESWAP_DATADESC( ZIP_EndOfCentralDirRecord )
DEFINE_FIELD( signature, FIELD_INTEGER ),
DEFINE_FIELD( numberOfThisDisk, FIELD_SHORT ),
DEFINE_FIELD( numberOfTheDiskWithStartOfCentralDirectory, FIELD_SHORT ),
DEFINE_FIELD( nCentralDirectoryEntries_ThisDisk, FIELD_SHORT ),
DEFINE_FIELD( nCentralDirectoryEntries_Total, FIELD_SHORT ),
DEFINE_FIELD( centralDirectorySize, FIELD_INTEGER ),
DEFINE_FIELD( startOfCentralDirOffset, FIELD_INTEGER ),
DEFINE_FIELD( commentLength, FIELD_SHORT ),
END_BYTESWAP_DATADESC()
BEGIN_BYTESWAP_DATADESC( ZIP_FileHeader )
DEFINE_FIELD( signature, FIELD_INTEGER ),
DEFINE_FIELD( versionMadeBy, FIELD_SHORT ),
DEFINE_FIELD( versionNeededToExtract, FIELD_SHORT ),
DEFINE_FIELD( flags, FIELD_SHORT ),
DEFINE_FIELD( compressionMethod, FIELD_SHORT ),
DEFINE_FIELD( lastModifiedTime, FIELD_SHORT ),
DEFINE_FIELD( lastModifiedDate, FIELD_SHORT ),
DEFINE_FIELD( crc32, FIELD_INTEGER ),
DEFINE_FIELD( compressedSize, FIELD_INTEGER ),
DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ),
DEFINE_FIELD( fileNameLength, FIELD_SHORT ),
DEFINE_FIELD( extraFieldLength, FIELD_SHORT ),
DEFINE_FIELD( fileCommentLength, FIELD_SHORT ),
DEFINE_FIELD( diskNumberStart, FIELD_SHORT ),
DEFINE_FIELD( internalFileAttribs, FIELD_SHORT ),
DEFINE_FIELD( externalFileAttribs, FIELD_INTEGER ),
DEFINE_FIELD( relativeOffsetOfLocalHeader, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()
BEGIN_BYTESWAP_DATADESC( ZIP_LocalFileHeader )
DEFINE_FIELD( signature, FIELD_INTEGER ),
DEFINE_FIELD( versionNeededToExtract, FIELD_SHORT ),
DEFINE_FIELD( flags, FIELD_SHORT ),
DEFINE_FIELD( compressionMethod, FIELD_SHORT ),
DEFINE_FIELD( lastModifiedTime, FIELD_SHORT ),
DEFINE_FIELD( lastModifiedDate, FIELD_SHORT ),
DEFINE_FIELD( crc32, FIELD_INTEGER ),
DEFINE_FIELD( compressedSize, FIELD_INTEGER ),
DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ),
DEFINE_FIELD( fileNameLength, FIELD_SHORT ),
DEFINE_FIELD( extraFieldLength, FIELD_SHORT ),
END_BYTESWAP_DATADESC()
BEGIN_BYTESWAP_DATADESC( ZIP_PreloadHeader )
DEFINE_FIELD( Version, FIELD_INTEGER ),
DEFINE_FIELD( DirectoryEntries, FIELD_INTEGER ),
DEFINE_FIELD( PreloadDirectoryEntries, FIELD_INTEGER ),
DEFINE_FIELD( Alignment, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()
BEGIN_BYTESWAP_DATADESC( ZIP_PreloadDirectoryEntry )
DEFINE_FIELD( Length, FIELD_INTEGER ),
DEFINE_FIELD( DataOffset, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()
#ifdef WIN32
//-----------------------------------------------------------------------------
// For >2 GB File Support
//-----------------------------------------------------------------------------
class CWin32File
{
public:
static HANDLE CreateTempFile( CUtlString &WritePath, CUtlString &FileName )
{
char tempFileName[MAX_PATH];
if ( WritePath.IsEmpty() )
{
// use a safe name in the cwd
char *pBuffer = tmpnam( NULL );
if ( !pBuffer )
{
return INVALID_HANDLE_VALUE;
}
if ( pBuffer[0] == '\\' )
{
pBuffer++;
}
if ( pBuffer[strlen( pBuffer )-1] == '.' )
{
pBuffer[strlen( pBuffer )-1] = '\0';
}
V_snprintf( tempFileName, sizeof( tempFileName ), "_%s.tmp", pBuffer );
}
else
{
// generate safe name at the desired prefix
char uniqueFilename[MAX_PATH];
SYSTEMTIME sysTime; \
GetLocalTime( &sysTime );
sprintf( uniqueFilename, "%d_%d_%d_%d_%d.tmp", sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond, sysTime.wMilliseconds ); \
V_ComposeFileName( WritePath.String(), uniqueFilename, tempFileName, sizeof( tempFileName ) );
}
FileName = tempFileName;
HANDLE hFile = CreateFile( tempFileName, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
return hFile;
}
static unsigned int FileSeek( HANDLE hFile, unsigned int distance, DWORD MoveMethod )
{
LARGE_INTEGER li;
li.QuadPart = distance;
li.LowPart = SetFilePointer( hFile, li.LowPart, &li.HighPart, MoveMethod);
if ( li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR )
{
li.QuadPart = -1;
}
return ( unsigned int )li.QuadPart;
}
static unsigned int FileTell( HANDLE hFile )
{
return FileSeek( hFile, 0, FILE_CURRENT );
}
static bool FileRead( HANDLE hFile, void *pBuffer, unsigned int size )
{
DWORD numBytesRead;
BOOL bSuccess = ::ReadFile( hFile, pBuffer, size, &numBytesRead, NULL );
return bSuccess && ( numBytesRead == size );
}
static bool FileWrite( HANDLE hFile, void *pBuffer, unsigned int size )
{
DWORD numBytesWritten;
BOOL bSuccess = WriteFile( hFile, pBuffer, size, &numBytesWritten, NULL );
return bSuccess && ( numBytesWritten == size );
}
};
#else
class CWin32File
{
public:
static HANDLE CreateTempFile( CUtlString &WritePath, CUtlString &FileName )
{
char tempFileName[MAX_PATH];
if ( WritePath.IsEmpty() )
{
// use a safe name in the cwd
char *pBuffer = tmpnam( NULL );
if ( !pBuffer )
{
return INVALID_HANDLE_VALUE;
}
if ( pBuffer[0] == '\\' )
{
pBuffer++;
}
if ( pBuffer[strlen( pBuffer )-1] == '.' )
{
pBuffer[strlen( pBuffer )-1] = '\0';
}
V_snprintf( tempFileName, sizeof( tempFileName ), "_%s.tmp", pBuffer );
}
else
{
char uniqueFilename[MAX_PATH];
static int counter = 0;
time_t now = time( NULL );
struct tm *tm = localtime( &now );
sprintf( uniqueFilename, "%d_%d_%d_%d_%d.tmp", tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec, ++counter ); \
V_ComposeFileName( WritePath.String(), uniqueFilename, tempFileName, sizeof( tempFileName ) );
}
FileName = tempFileName;
FILE *hFile = fopen( tempFileName, "rw+" );
return (HANDLE)hFile;
}
static unsigned int FileSeek( HANDLE hFile, unsigned int distance, DWORD MoveMethod )
{
if ( fseeko( (FILE *)hFile, distance, MoveMethod ) == 0 )
{
return FileTell( hFile );
}
return 0;
}
static unsigned int FileTell( HANDLE hFile )
{
return ftello( (FILE *)hFile );
}
static bool FileRead( HANDLE hFile, void *pBuffer, unsigned int size )
{
size_t bytesRead = fread( pBuffer, 1, size, (FILE *)hFile );
return bytesRead == size;
}
static bool FileWrite( HANDLE hFile, void *pBuffer, unsigned int size )
{
size_t bytesWrtitten = fwrite( pBuffer, 1, size, (FILE *)hFile );
return bytesWrtitten == size;
}
};
#endif
//-----------------------------------------------------------------------------
// Purpose: Interface to allow abstraction of zip file output methods, and
// avoid duplication of code. Files may be written to a CUtlBuffer or a filestream
//-----------------------------------------------------------------------------
abstract_class IWriteStream
{
public:
virtual void Put( const void* pMem, int size ) = 0;
virtual unsigned int Tell( void ) = 0;
};
//-----------------------------------------------------------------------------
// Purpose: Wrapper for CUtlBuffer methods
//-----------------------------------------------------------------------------
class CBufferStream : public IWriteStream
{
public:
CBufferStream( CUtlBuffer& buff ) : IWriteStream(), m_buff( &buff ) {}
// Implementing IWriteStream method
virtual void Put( const void* pMem, int size ) {m_buff->Put( pMem, size );}
// Implementing IWriteStream method
virtual unsigned int Tell( void ) { return m_buff->TellPut(); }
private:
CUtlBuffer *m_buff;
};
//-----------------------------------------------------------------------------
// Purpose: Wrapper for file I/O methods
//-----------------------------------------------------------------------------
class CFileStream : public IWriteStream
{
public:
CFileStream( FILE *fout ) : IWriteStream(), m_file( fout ), m_hFile( INVALID_HANDLE_VALUE ) {}
CFileStream( HANDLE hOutFile ) : IWriteStream(), m_file( NULL ), m_hFile( hOutFile ) {}
// Implementing IWriteStream method
virtual void Put( const void* pMem, int size )
{
if ( m_file )
{
fwrite( pMem, size, 1, m_file );
}
#ifdef WIN32
else
{
DWORD numBytesWritten;
WriteFile( m_hFile, pMem, size, &numBytesWritten, NULL );
}
#endif
}
// Implementing IWriteStream method
virtual unsigned int Tell( void )
{
if ( m_file )
{
return ftell( m_file );
}
else
{
#ifdef WIN32
return CWin32File::FileTell( m_hFile );
#else
return 0;
#endif
}
}
private:
FILE *m_file;
HANDLE m_hFile;
};
//-----------------------------------------------------------------------------
// Purpose: Container for modifiable pak file which is embedded inside the .bsp file
// itself. It's used to allow one-off files to be stored local to the map and it is
// hooked into the file system as an override for searching for named files.
//-----------------------------------------------------------------------------
class CZipFile
{
public:
// Construction
CZipFile( const char *pDiskCacheWritePath, bool bSortByName );
~CZipFile( void );
// Public API
// Clear all existing data
void Reset( void );
// Add file to zip under relative name
void AddFileToZip( const char *relativename, const char *fullpath, IZip::eCompressionType compressionType );
// Delete file from zip
void RemoveFileFromZip( const char *relativename );
// Add buffer to zip as a file with given name
void AddBufferToZip( const char *relativename, void *data, int length, bool bTextMode, IZip::eCompressionType compressionType );
// Check if a file already exists in the zip.
bool FileExistsInZip( const char *relativename );
// Reads a file from a zip file
bool ReadFileFromZip( const char *relativename, bool bTextMode, CUtlBuffer &buf );
bool ReadFileFromZip( HANDLE hZipFile, const char *relativename, bool bTextMode, CUtlBuffer &buf );
// Initialize the zip file from a buffer
void ParseFromBuffer( void *buffer, int bufferlength );
HANDLE ParseFromDisk( const char *pFilename );
// Estimate the size of the zip file (including header, padding, etc.)
unsigned int EstimateSize();
// Print out a directory of files in the zip.
void PrintDirectory( void );
// Use to iterate directory, pass 0 for first element
// returns nonzero element id with filled buffer, or -1 at list conclusion
int GetNextFilename( int id, char *pBuffer, int bufferSize, int &fileSize );
// Write the zip to a buffer
void SaveToBuffer( CUtlBuffer& buffer );
// Write the zip to a filestream
void SaveToDisk( FILE *fout );
void SaveToDisk( HANDLE hOutFile );
unsigned int CalculateSize( void );
void ForceAlignment( bool aligned, bool bCompatibleFormat, unsigned int alignmentSize );
unsigned int GetAlignment();
void SetBigEndian( bool bigEndian );
void ActivateByteSwapping( bool bActivate );
private:
enum
{
MAX_FILES_IN_ZIP = 32768,
};
typedef struct
{
CUtlSymbol m_Name;
unsigned int filepos;
int filelen;
int uncompressedLen;
CRC32_t crc32;
IZip::eCompressionType compressionType;
} TmpFileInfo_t;
CByteswap m_Swap;
unsigned int m_AlignmentSize;
bool m_bForceAlignment;
bool m_bCompatibleFormat;
unsigned short CalculatePadding( unsigned int filenameLen, unsigned int pos );
void SaveDirectory( IWriteStream& stream );
int MakeXZipCommentString( char *pComment );
void ParseXZipCommentString( const char *pComment );
// Internal entry for faster searching, etc.
class CZipEntry
{
public:
CZipEntry( void );
~CZipEntry( void );
CZipEntry( const CZipEntry& src );
// RB tree compare function
static bool ZipFileLessFunc( CZipEntry const& src1, CZipEntry const& src2 );
static bool ZipFileLessFunc_CaselessSort( CZipEntry const& src1, CZipEntry const& src2 );
// Name of entry
CUtlSymbol m_Name;
// Lenth of data element
int m_nCompressedSize;
// Original, uncompressed size
int m_nUncompressedSize;
// Raw data, could be null and data may be in disk write cache
void *m_pData;
// Offset in Zip ( set and valid during final write )
unsigned int m_ZipOffset;
// CRC of blob
CRC32_t m_ZipCRC;
// Location of data in disk cache
unsigned int m_DiskCacheOffset;
unsigned int m_SourceDiskOffset;
// The compression used on the data if any
IZip::eCompressionType m_eCompressionType;
};
// For fast name lookup and sorting
CUtlRBTree< CZipEntry, int > m_Files;
// Used to buffer zip data, instead of ram
bool m_bUseDiskCacheForWrites;
HANDLE m_hDiskCacheWriteFile;
CUtlString m_DiskCacheName;
CUtlString m_DiskCacheWritePath;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CZipFile::CZipEntry::CZipEntry( void )
{
m_Name = "";
m_nCompressedSize = 0;
m_nUncompressedSize = 0;
m_pData = NULL;
m_ZipOffset = 0;
m_ZipCRC = 0;
m_DiskCacheOffset = 0;
m_SourceDiskOffset = 0;
m_eCompressionType = IZip::eCompressionType_None;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : src -
//-----------------------------------------------------------------------------
CZipFile::CZipEntry::CZipEntry( const CZipFile::CZipEntry& src )
{
m_Name = src.m_Name;
m_nCompressedSize = src.m_nCompressedSize;
m_nUncompressedSize = src.m_nUncompressedSize;
m_eCompressionType = src.m_eCompressionType;
if ( src.m_nCompressedSize > 0 && src.m_pData )
{
m_pData = malloc( src.m_nCompressedSize );
memcpy( m_pData, src.m_pData, src.m_nCompressedSize );
}
else
{
m_pData = NULL;
}
m_ZipOffset = src.m_ZipOffset;
m_ZipCRC = src.m_ZipCRC;
m_DiskCacheOffset = src.m_DiskCacheOffset;
m_SourceDiskOffset = src.m_SourceDiskOffset;
}
//-----------------------------------------------------------------------------
// Purpose: Clear any leftover data
//-----------------------------------------------------------------------------
CZipFile::CZipEntry::~CZipEntry( void )
{
if ( m_pData )
{
free( m_pData );
}
}
//-----------------------------------------------------------------------------
// Purpose: Construction
//-----------------------------------------------------------------------------
CZipFile::CZipFile( const char *pDiskCacheWritePath, bool bSortByName )
: m_Files( 0, 32 )
{
m_AlignmentSize = 0;
m_bForceAlignment = false;
m_bCompatibleFormat = true;
m_bUseDiskCacheForWrites = ( pDiskCacheWritePath != NULL );
m_DiskCacheWritePath = pDiskCacheWritePath;
m_hDiskCacheWriteFile = INVALID_HANDLE_VALUE;
if ( bSortByName )
{
m_Files.SetLessFunc( CZipEntry::ZipFileLessFunc_CaselessSort );
}
else
{
m_Files.SetLessFunc( CZipEntry::ZipFileLessFunc );
}
}
//-----------------------------------------------------------------------------
// Purpose: Destroy zip data
//-----------------------------------------------------------------------------
CZipFile::~CZipFile( void )
{
m_bUseDiskCacheForWrites = false;
Reset();
}
//-----------------------------------------------------------------------------
// Purpose: Delete all current data
//-----------------------------------------------------------------------------
void CZipFile::Reset( void )
{
m_Files.RemoveAll();
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
#ifdef WIN32
CloseHandle( m_hDiskCacheWriteFile );
DeleteFile( m_DiskCacheName.String() );
#else
fclose( (FILE *)m_hDiskCacheWriteFile );
unlink( m_DiskCacheName.String() );
#endif
m_hDiskCacheWriteFile = INVALID_HANDLE_VALUE;
}
if ( m_bUseDiskCacheForWrites )
{
m_hDiskCacheWriteFile = CWin32File::CreateTempFile( m_DiskCacheWritePath, m_DiskCacheName );
}
}
//-----------------------------------------------------------------------------
// Purpose: Comparison for sorting entries
// Input : src1 -
// src2 -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CZipFile::CZipEntry::ZipFileLessFunc( CZipEntry const& src1, CZipEntry const& src2 )
{
return ( src1.m_Name < src2.m_Name );
}
bool CZipFile::CZipEntry::ZipFileLessFunc_CaselessSort( CZipEntry const& src1, CZipEntry const& src2 )
{
return ( V_stricmp( src1.m_Name.String(), src2.m_Name.String() ) < 0 );
}
void CZipFile::ForceAlignment( bool bAligned, bool bCompatibleFormat, unsigned int alignment )
{
m_bForceAlignment = bAligned;
m_AlignmentSize = alignment;
m_bCompatibleFormat = bCompatibleFormat;
if ( !bAligned )
{
m_AlignmentSize = 0;
}
else if ( !IsPowerOfTwo( m_AlignmentSize ) )
{
m_AlignmentSize = 0;
}
}
unsigned int CZipFile::GetAlignment()
{
if ( !m_bForceAlignment || !m_AlignmentSize )
{
return 0;
}
return m_AlignmentSize;
}
void CZipFile::SetBigEndian( bool bigEndian )
{
m_Swap.SetTargetBigEndian( bigEndian );
}
void CZipFile::ActivateByteSwapping( bool bActivate )
{
m_Swap.ActivateByteSwapping( bActivate );
}
//-----------------------------------------------------------------------------
// Purpose: Load pak file from raw buffer
// Input : *buffer -
// bufferlength -
//-----------------------------------------------------------------------------
void CZipFile::ParseFromBuffer( void *buffer, int bufferlength )
{
// Throw away old data
Reset();
// Initialize a buffer
CUtlBuffer buf( 0, bufferlength +1 ); // +1 for null termination
// need to swap bytes, so set the buffer opposite the machine's endian
buf.ActivateByteSwapping( m_Swap.IsSwappingBytes() );
buf.Put( buffer, bufferlength );
buf.SeekGet( CUtlBuffer::SEEK_TAIL, 0 );
unsigned int fileLen = buf.TellGet();
// Start from beginning
buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
ZIP_EndOfCentralDirRecord rec = { 0 };
#ifdef DBGFLAG_ASSERT
bool bFoundEndOfCentralDirRecord = false;
#endif
unsigned int offset = fileLen - sizeof( ZIP_EndOfCentralDirRecord );
// If offset is ever greater than startOffset then it means that it has
// wrapped. This used to be a tautological >= 0 test.
ANALYZE_SUPPRESS( 6293 ); // warning C6293: Ill-defined for-loop: counts down from minimum
for ( unsigned int startOffset = offset; offset <= startOffset; offset-- )
{
buf.SeekGet( CUtlBuffer::SEEK_HEAD, offset );
buf.GetObjects( &rec );
if ( rec.signature == PKID( 5, 6 ) )
{
#ifdef DBGFLAG_ASSERT
bFoundEndOfCentralDirRecord = true;
#endif
// Set any xzip configuration
if ( rec.commentLength )
{
char commentString[128] = { 0 };
int commentLength = min( (int)rec.commentLength, (int)sizeof( commentString ) );
buf.Get( commentString, commentLength );
if ( commentLength == sizeof( commentString ) )
--commentLength;
commentString[commentLength] = '\0';
ParseXZipCommentString( commentString );
}
break;
}
else
{
// wrong record
rec.nCentralDirectoryEntries_Total = 0;
}
}
Assert( bFoundEndOfCentralDirRecord );
// Make sure there are some files to parse
int numzipfiles = rec.nCentralDirectoryEntries_Total;
if ( numzipfiles <= 0 )
{
// No files
return;
}
buf.SeekGet( CUtlBuffer::SEEK_HEAD, rec.startOfCentralDirOffset );
// Allocate space for directory
TmpFileInfo_t *newfiles = new TmpFileInfo_t[numzipfiles];
Assert( newfiles );
// build directory
int i;
for ( i = 0; i < rec.nCentralDirectoryEntries_Total; i++ )
{
ZIP_FileHeader zipFileHeader;
buf.GetObjects( &zipFileHeader );
Assert( zipFileHeader.signature == PKID( 1, 2 ) );
if ( zipFileHeader.compressionMethod != IZip::eCompressionType_None &&
zipFileHeader.compressionMethod != IZip::eCompressionType_LZMA )
{
Assert( false );
Warning( "Opening ZIP file with unsupported compression type\n");
}
char tmpString[MAX_PATH] = { 0 };
buf.Get( tmpString, Min( (unsigned int)zipFileHeader.fileNameLength, (unsigned int)sizeof( tmpString ) ) );
Q_strlower( tmpString );
// can determine actual filepos, assuming a well formed zip
newfiles[i].m_Name = tmpString;
newfiles[i].filelen = zipFileHeader.compressedSize;
newfiles[i].uncompressedLen = zipFileHeader.uncompressedSize;
newfiles[i].crc32 = zipFileHeader.crc32;
newfiles[i].filepos = zipFileHeader.relativeOffsetOfLocalHeader +
sizeof( ZIP_LocalFileHeader ) +
zipFileHeader.fileNameLength +
zipFileHeader.extraFieldLength;
newfiles[i].compressionType = (IZip::eCompressionType)zipFileHeader.compressionMethod;
int nextOffset;
if ( m_bCompatibleFormat )
{
nextOffset = zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength;
}
else
{
nextOffset = 0;
}
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
}
// Insert current data into rb tree
for ( i=0; i<numzipfiles; i++ )
{
CZipEntry e;
e.m_Name = newfiles[i].m_Name;
e.m_nCompressedSize = newfiles[i].filelen;
e.m_ZipCRC = newfiles[i].crc32;
e.m_nUncompressedSize = newfiles[i].uncompressedLen;
e.m_eCompressionType = newfiles[i].compressionType;
// Make sure length is reasonable
if ( e.m_nCompressedSize > 0 )
{
e.m_pData = malloc( e.m_nCompressedSize );
// Copy in data
buf.SeekGet( CUtlBuffer::SEEK_HEAD, newfiles[i].filepos );
buf.Get( e.m_pData, e.m_nCompressedSize );
}
else
{
e.m_pData = NULL;
}
// Add to tree
m_Files.Insert( e );
}
// Through away directory
delete[] newfiles;
}
//-----------------------------------------------------------------------------
// Purpose: Mount pak file from disk
//-----------------------------------------------------------------------------
HANDLE CZipFile::ParseFromDisk( const char *pFilename )
{
#ifdef WIN32
HANDLE hFile = CreateFile( pFilename, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if ( hFile == INVALID_HANDLE_VALUE )
{
// not found
return NULL;
}
#else
HANDLE hFile = fopen( pFilename, "rw+" );
if ( !hFile )
{
// not found
return NULL;
}
#endif
unsigned int fileLen = CWin32File::FileSeek( hFile, 0, FILE_END );
CWin32File::FileSeek( hFile, 0, FILE_BEGIN );
if ( fileLen < sizeof( ZIP_EndOfCentralDirRecord ) )
{
// bad format
#ifdef WIN32
CloseHandle( hFile );
#else
fclose( (FILE *)hFile );
#endif
return NULL;
}
// need to get the central dir
ZIP_EndOfCentralDirRecord rec = { 0 };
unsigned int offset = fileLen - sizeof( ZIP_EndOfCentralDirRecord );
// If offset is ever greater than startOffset then it means that it has
// wrapped. This used to be a tautological >= 0 test.
ANALYZE_SUPPRESS( 6293 ); // warning C6293: Ill-defined for-loop: counts down from minimum
for ( unsigned int startOffset = offset; offset <= startOffset; offset-- )
{
CWin32File::FileSeek( hFile, offset, FILE_BEGIN );
CWin32File::FileRead( hFile, &rec, sizeof( rec ) );
m_Swap.SwapFieldsToTargetEndian( &rec );
if ( rec.signature == PKID( 5, 6 ) )
{
// Set any xzip configuration
if ( rec.commentLength )
{
char commentString[128] = { 0 };
int commentLength = min( (int)rec.commentLength, (int)sizeof( commentString ) );
CWin32File::FileRead( hFile, commentString, commentLength );
if ( commentLength == sizeof( commentString ) )
--commentLength;
commentString[commentLength] = '\0';
ParseXZipCommentString( commentString );
}
break;
}
else
{
// wrong record
rec.nCentralDirectoryEntries_Total = 0;
}
}
// Make sure there are some files to parse
int numZipFiles = rec.nCentralDirectoryEntries_Total;
if ( numZipFiles <= 0 )
{
// No files
#ifdef WIN32
CloseHandle( hFile );
#else
fclose( (FILE *)hFile );
#endif
return NULL;
}
CWin32File::FileSeek( hFile, rec.startOfCentralDirOffset, FILE_BEGIN );
// read entire central dir into memory
CUtlBuffer zipDirBuff( 0, rec.centralDirectorySize, 0 );
zipDirBuff.ActivateByteSwapping( m_Swap.IsSwappingBytes() );
CWin32File::FileRead( hFile, zipDirBuff.Base(), rec.centralDirectorySize );
zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize );
// build directory
for ( int i = 0; i < numZipFiles; i++ )
{
ZIP_FileHeader zipFileHeader;
zipDirBuff.GetObjects( &zipFileHeader );
if ( zipFileHeader.signature != PKID( 1, 2 )
|| ( zipFileHeader.compressionMethod != IZip::eCompressionType_None
&& zipFileHeader.compressionMethod != IZip::eCompressionType_LZMA ) )
{
// bad contents
#ifdef WIN32
CloseHandle( hFile );
#else
fclose( (FILE *)hFile );
#endif
return NULL;
}
char fileName[MAX_PATH] = { 0 };
zipDirBuff.Get( fileName, Min( (size_t)zipFileHeader.fileNameLength, sizeof( fileName ) - 1 ) );
Q_strlower( fileName );
// can determine actual filepos, assuming a well formed zip
CZipEntry e;
e.m_Name = fileName;
e.m_nCompressedSize = zipFileHeader.compressedSize;
e.m_nUncompressedSize = zipFileHeader.uncompressedSize;
e.m_ZipCRC = zipFileHeader.crc32;
e.m_SourceDiskOffset = zipFileHeader.relativeOffsetOfLocalHeader +
sizeof( ZIP_LocalFileHeader ) +
zipFileHeader.fileNameLength +
zipFileHeader.extraFieldLength;
e.m_eCompressionType = (IZip::eCompressionType)zipFileHeader.compressionMethod;
// Add to tree
m_Files.Insert( e );
int nextOffset;
if ( m_bCompatibleFormat )
{
nextOffset = zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength;
}
else
{
nextOffset = 0;
}
zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
}
return hFile;
}
static int GetLengthOfBinStringAsText( const char *pSrc, int srcSize )
{
const char *pSrcScan = pSrc;
const char *pSrcEnd = pSrc + srcSize;
int numChars = 0;
for( ; pSrcScan < pSrcEnd; pSrcScan++ )
{
if( *pSrcScan == '\n' )
{
numChars += 2;
}
else
{
numChars++;
}
}
return numChars;
}
//-----------------------------------------------------------------------------
// Copies text data from a form appropriate for disk to a normal string
//-----------------------------------------------------------------------------
static void ReadTextData( const char *pSrc, int nSrcSize, CUtlBuffer &buf )
{
buf.EnsureCapacity( nSrcSize + 1 );
const char *pSrcEnd = pSrc + nSrcSize;
for ( const char *pSrcScan = pSrc; pSrcScan < pSrcEnd; ++pSrcScan )
{
if ( *pSrcScan == '\r' )
{
if ( pSrcScan[1] == '\n' )
{
buf.PutChar( '\n' );
++pSrcScan;
continue;
}
}
buf.PutChar( *pSrcScan );
}
// Null terminate
buf.PutChar( '\0' );
}
//-----------------------------------------------------------------------------
// Copies text data into a form appropriate for disk
//-----------------------------------------------------------------------------
static void CopyTextData( char *pDst, const char *pSrc, int dstSize, int srcSize )
{
const char *pSrcScan = pSrc;
const char *pSrcEnd = pSrc + srcSize;
char *pDstScan = pDst;
#ifdef DBGFLAG_ASSERT
char *pDstEnd = pDst + dstSize;
#endif
for ( ; pSrcScan < pSrcEnd; pSrcScan++ )
{
if ( *pSrcScan == '\n' )
{
*pDstScan = '\r';
pDstScan++;
*pDstScan = '\n';
pDstScan++;
}
else
{
*pDstScan = *pSrcScan;
pDstScan++;
}
}
Assert( pSrcScan == pSrcEnd );
Assert( pDstScan == pDstEnd );
}
//-----------------------------------------------------------------------------
// Purpose: Adds a new lump, or overwrites existing one
// Input : *relativename -
// *data -
// length -
//-----------------------------------------------------------------------------
void CZipFile::AddBufferToZip( const char *relativename, void *data, int length, bool bTextMode, IZip::eCompressionType compressionType )
{
// Lower case only
char name[512];
Q_strcpy( name, relativename );
Q_strlower( name );
int outLength = length;
int uncompressedLength = length;
void *outData = data;
CUtlBuffer textTransform;
CUtlBuffer compressionTransform;
if ( bTextMode )
{
int textLen = GetLengthOfBinStringAsText( ( const char * )outData, outLength );
textTransform.EnsureCapacity( textLen );
CopyTextData( (char *)textTransform.Base(), (char *)outData, textLen, outLength );
outData = (void *)textTransform.Base();
outLength = textLen;
uncompressedLength = textLen;
}
// uncompressed data final at this point (CRC is before compression)
CRC32_t zipCRC;
CRC32_Init( &zipCRC );
CRC32_ProcessBuffer( &zipCRC, outData, outLength );
CRC32_Final( &zipCRC );
#ifdef ZIP_SUPPORT_LZMA_ENCODE
if ( compressionType == IZip::eCompressionType_LZMA )
{
unsigned int compressedSize = 0;
unsigned char *pCompressedOutput = LZMA_Compress( (unsigned char *)outData, outLength, &compressedSize );
if ( !pCompressedOutput || compressedSize < sizeof( lzma_header_t ) )
{
Warning( "ZipFile: LZMA compression failed\n" );
return;
}
// Fixup LZMA header for ZIP payload usage
// The output of LZMA_Compress uses lzma_header_t, defined alongside it.
//
// ZIP payload format, see ZIP spec 5.8.8:
// LZMA Version Information 2 bytes
// LZMA Properties Size 2 bytes
// LZMA Properties Data variable, defined by "LZMA Properties Size"
unsigned int nZIPHeader = 2 + 2 + sizeof( lzma_header_t().properties );
unsigned int finalCompressedSize = compressedSize - sizeof( lzma_header_t ) + nZIPHeader;
compressionTransform.EnsureCapacity( finalCompressedSize );
// LZMA version
compressionTransform.PutUnsignedChar( LZMA_SDK_VERSION_MAJOR );
compressionTransform.PutUnsignedChar( LZMA_SDK_VERSION_MINOR );
// properties size
uint16 nSwappedPropertiesSize = LittleWord( sizeof( lzma_header_t().properties ) );
compressionTransform.Put( &nSwappedPropertiesSize, sizeof( nSwappedPropertiesSize ) );
// properties
compressionTransform.Put( &(((lzma_header_t *)pCompressedOutput)->properties), sizeof( lzma_header_t().properties ) );
// payload
compressionTransform.Put( pCompressedOutput + sizeof( lzma_header_t ), compressedSize - sizeof( lzma_header_t ) );
// Free original
free( pCompressedOutput );
pCompressedOutput = NULL;
outData = (void *)compressionTransform.Base();
outLength = finalCompressedSize;
// (Not updating uncompressedLength)
}
else
#endif
/* else from ifdef */ if ( compressionType != IZip::eCompressionType_None )
{
Error( "Calling AddBufferToZip with unknown compression type\n" );
return;
}
// See if entry is in list already
CZipEntry e;
e.m_Name = name;
int index = m_Files.Find( e );
// If already existing, throw away old data and update data and length
if ( index != m_Files.InvalidIndex() )
{
CZipEntry *update = &m_Files[ index ];
if ( update->m_pData )
{
free( update->m_pData );
}
update->m_eCompressionType = compressionType;
update->m_pData = malloc( outLength );
memcpy( update->m_pData, outData, outLength );
update->m_nCompressedSize = outLength;
update->m_nUncompressedSize = uncompressedLength;
update->m_ZipCRC = zipCRC;
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
update->m_DiskCacheOffset = CWin32File::FileTell( m_hDiskCacheWriteFile );
CWin32File::FileWrite( m_hDiskCacheWriteFile, update->m_pData, update->m_nCompressedSize );
free( update->m_pData );
update->m_pData = NULL;
}
}
else
{
// Create a new entry
e.m_nCompressedSize = outLength;
e.m_nUncompressedSize = uncompressedLength;
e.m_eCompressionType = compressionType;
e.m_ZipCRC = zipCRC;
if ( outLength > 0 )
{
e.m_pData = malloc( outLength );
memcpy( e.m_pData, outData, outLength );
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
e.m_DiskCacheOffset = CWin32File::FileTell( m_hDiskCacheWriteFile );
CWin32File::FileWrite( m_hDiskCacheWriteFile, e.m_pData, e.m_nCompressedSize );
free( e.m_pData );
e.m_pData = NULL;
}
}
else
{
e.m_pData = NULL;
}
m_Files.Insert( e );
}
}
//-----------------------------------------------------------------------------
// Reads a file from the zip
//-----------------------------------------------------------------------------
bool CZipFile::ReadFileFromZip( const char *pRelativeName, bool bTextMode, CUtlBuffer &buf )
{
return ReadFileFromZip( 0, pRelativeName, bTextMode, buf );
}
//-----------------------------------------------------------------------------
// Reads a file from the zip. Requires the zip file handle if this zip was loaded via ParseFromDisk
//-----------------------------------------------------------------------------
bool CZipFile::ReadFileFromZip( HANDLE hZipFile, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf )
{
// Lower case only
char pName[512];
Q_strncpy( pName, pRelativeName, 512 );
Q_strlower( pName );
// See if entry is in list already
CZipEntry e;
e.m_Name = pName;
int nIndex = m_Files.Find( e );
if ( nIndex == m_Files.InvalidIndex() )
{
// not found
return false;
}
CZipEntry *pEntry = &m_Files[nIndex];
void *pData = pEntry->m_pData;
CUtlBuffer readBuffer;
if ( !pData && hZipFile )
{
readBuffer.EnsureCapacity( pEntry->m_nCompressedSize );
CWin32File::FileSeek( hZipFile, pEntry->m_SourceDiskOffset, FILE_BEGIN );
if ( !CWin32File::FileRead( hZipFile, readBuffer.Base(), pEntry->m_nCompressedSize ) )
{
return false;
}
pData = readBuffer.Base();
}
CUtlBuffer decompressTransform;
if ( pEntry->m_eCompressionType != IZip::eCompressionType_None )
{
if ( pEntry->m_eCompressionType == IZip::eCompressionType_LZMA )
{
decompressTransform.EnsureCapacity( pEntry->m_nUncompressedSize );
CLZMAStream decompressStream;
decompressStream.InitZIPHeader( pEntry->m_nCompressedSize, pEntry->m_nUncompressedSize );
unsigned int nCompressedBytesRead = 0;
unsigned int nOutputBytesWritten = 0;
bool bSuccess = decompressStream.Read( (unsigned char *)pData, pEntry->m_nCompressedSize,
(unsigned char *)decompressTransform.Base(), decompressTransform.Size(),
nCompressedBytesRead, nOutputBytesWritten );
if ( !bSuccess ||
(int)nCompressedBytesRead != pEntry->m_nCompressedSize ||
(int)nOutputBytesWritten != pEntry->m_nUncompressedSize )
{
Error( "Zip: Failed decompressing LZMA data\n" );
return false;
}
pData = decompressTransform.Base();
}
else
{
Error( "Unsupported compression type in Zip file: %u\n", pEntry->m_eCompressionType );
return false;
}
}
if ( bTextMode )
{
buf.SetBufferType( true, false );
ReadTextData( (const char *)pData, pEntry->m_nUncompressedSize, buf );
}
else
{
buf.SetBufferType( false, false );
buf.Put( pData, pEntry->m_nUncompressedSize );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Check if a file already exists in the zip.
// Input : *relativename -
//-----------------------------------------------------------------------------
bool CZipFile::FileExistsInZip( const char *pRelativeName )
{
// Lower case only
char pName[512];
Q_strncpy( pName, pRelativeName, 512 );
Q_strlower( pName );
// See if entry is in list already
CZipEntry e;
e.m_Name = pName;
int nIndex = m_Files.Find( e );
// If it is, then it exists in the pack!
return nIndex != m_Files.InvalidIndex();
}
//-----------------------------------------------------------------------------
// Purpose: Adds a new file to the zip.
//-----------------------------------------------------------------------------
void CZipFile::AddFileToZip( const char *relativename, const char *fullpath, IZip::eCompressionType compressionType )
{
FILE *temp = fopen( fullpath, "rb" );
if ( !temp )
return;
// Determine length
fseek( temp, 0, SEEK_END );
int size = ftell( temp );
fseek( temp, 0, SEEK_SET );
byte *buf = (byte *)malloc( size + 1 );
// Read data
fread( buf, size, 1, temp );
fclose( temp );
// Now add as a buffer
AddBufferToZip( relativename, buf, size, false, compressionType );
free( buf );
}
//-----------------------------------------------------------------------------
// Purpose: Removes a file from the zip.
//-----------------------------------------------------------------------------
void CZipFile::RemoveFileFromZip( const char *relativename )
{
CZipEntry e;
e.m_Name = relativename;
int index = m_Files.Find( e );
if ( index != m_Files.InvalidIndex() )
{
CZipEntry update = m_Files[index];
m_Files.Remove( update );
}
}
//---------------------------------------------------------------
// Purpose: Calculates how many bytes should be added to the extra field
// to push the start of the file data to the next aligned boundary
// Output: Required padding size
//---------------------------------------------------------------
unsigned short CZipFile::CalculatePadding( unsigned int filenameLen, unsigned int pos )
{
if ( m_AlignmentSize == 0 )
{
return 0;
}
unsigned int headerSize = sizeof( ZIP_LocalFileHeader ) + filenameLen;
return (unsigned short)( m_AlignmentSize - ( ( pos + headerSize ) % m_AlignmentSize ) );
}
//-----------------------------------------------------------------------------
// Purpose: Create the XZIP identifying comment string
// Output : Length
//-----------------------------------------------------------------------------
int CZipFile::MakeXZipCommentString( char *pCommentString )
{
char tempString[XZIP_COMMENT_LENGTH];
memset( tempString, 0, sizeof( tempString ) );
V_snprintf( tempString, sizeof( tempString ), "XZP%c %d", m_bCompatibleFormat ? '1' : '2', m_AlignmentSize );
if ( pCommentString )
{
memcpy( pCommentString, tempString, sizeof( tempString ) );
}
// expected fixed length
return XZIP_COMMENT_LENGTH;
}
//-----------------------------------------------------------------------------
// Purpose: An XZIP has its configuration in the ascii comment
//-----------------------------------------------------------------------------
void CZipFile::ParseXZipCommentString( const char *pCommentString )
{
if ( !V_strnicmp( pCommentString, "XZP", 3 ) )
{
m_bCompatibleFormat = true;
if ( pCommentString[3] == '2' )
{
m_bCompatibleFormat = false;
}
// parse out the alignement configuration
if ( !m_bForceAlignment )
{
m_AlignmentSize = 0;
sscanf( pCommentString + 4, "%d", &m_AlignmentSize );
if ( !IsPowerOfTwo( m_AlignmentSize ) )
{
m_AlignmentSize = 0;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Calculate the exact size of zip file, with headers and padding
// Output : int
//-----------------------------------------------------------------------------
unsigned int CZipFile::CalculateSize( void )
{
unsigned int size = 0;
unsigned int dirHeaders = 0;
for ( int i = m_Files.FirstInorder(); i != m_Files.InvalidIndex(); i = m_Files.NextInorder( i ) )
{
CZipEntry *e = &m_Files[ i ];
if ( e->m_nCompressedSize == 0 )
continue;
// local file header
size += sizeof( ZIP_LocalFileHeader );
size += strlen( e->m_Name.String() );
// every file has a directory header that duplicates the filename
dirHeaders += sizeof( ZIP_FileHeader ) + strlen( e->m_Name.String() );
// calculate padding
if ( m_AlignmentSize != 0 )
{
// round up to next boundary
unsigned int nextBoundary = ( size + m_AlignmentSize ) & ~( m_AlignmentSize - 1 );
// the directory header also duplicates the padding
dirHeaders += nextBoundary - size;
size = nextBoundary;
}
// data size
size += e->m_nCompressedSize;
}
size += dirHeaders;
// All processed zip files will have a comment string
size += sizeof( ZIP_EndOfCentralDirRecord ) + MakeXZipCommentString( NULL );
return size;
}
//-----------------------------------------------------------------------------
// Purpose: Print a directory of files in the zip
//-----------------------------------------------------------------------------
void CZipFile::PrintDirectory( void )
{
for ( int i = m_Files.FirstInorder(); i != m_Files.InvalidIndex(); i = m_Files.NextInorder( i ) )
{
CZipEntry *e = &m_Files[ i ];
Msg( "%s\n", e->m_Name.String() );
}
}
//-----------------------------------------------------------------------------
// Purpose: Iterate through directory
//-----------------------------------------------------------------------------
int CZipFile::GetNextFilename( int id, char *pBuffer, int bufferSize, int &fileSize )
{
if ( id == -1 )
{
id = m_Files.FirstInorder();
}
else
{
id = m_Files.NextInorder( id );
}
if ( id == m_Files.InvalidIndex() )
{
// list is empty
return -1;
}
CZipEntry *e = &m_Files[id];
Q_strncpy( pBuffer, e->m_Name.String(), bufferSize );
fileSize = e->m_nUncompressedSize;
return id;
}
//-----------------------------------------------------------------------------
// Purpose: Store data out to disk
//-----------------------------------------------------------------------------
void CZipFile::SaveToDisk( FILE *fout )
{
CFileStream stream( fout );
SaveDirectory( stream );
}
void CZipFile::SaveToDisk( HANDLE hOutFile )
{
CFileStream stream( hOutFile );
SaveDirectory( stream );
}
//-----------------------------------------------------------------------------
// Purpose: Store data out to a CUtlBuffer
//-----------------------------------------------------------------------------
void CZipFile::SaveToBuffer( CUtlBuffer& buf )
{
// Estimate size for buffer, since the linear growth of CUtlBuffer is a virtual memory steamroller. This is
// best-effort. Ideally CUtlBuffer's growth strategy would be sane and this would be unnecessary.
int sizeEstimate = 0;
for ( int i = m_Files.FirstInorder(); i != m_Files.InvalidIndex(); i = m_Files.NextInorder( i ) )
{
CZipEntry *e = &m_Files[i];
Assert( e );
int nameLen = V_strlen( e->m_Name.String() );
// Both the per-file header and central directory have these
sizeEstimate += 2 * sizeof( ZIP_LocalFileHeader );
sizeEstimate += 2 * nameLen;
sizeEstimate += 2 * CalculatePadding( nameLen, e->m_ZipOffset );
sizeEstimate += sizeof( ZIP_EndOfCentralDirRecord );
sizeEstimate += e->m_nCompressedSize;
// XZip comment string, max 128
sizeEstimate += 128;
// We align things to m_AlignmentSize at two points
sizeEstimate += m_AlignmentSize * 2;
}
int start = buf.TellPut();
buf.EnsureCapacity( start + sizeEstimate );
CBufferStream stream( buf );
SaveDirectory( stream );
int end = buf.TellPut();
if ( start + sizeEstimate < end )
{
Warning( "ZIP Output overshot buffer estimate: Estimated %i, actual %i\n", sizeEstimate, end - start );
}
else
{
DevMsg( "Wrote ZIP buffer, estimated size %i, actual size %i\n", sizeEstimate, end - start );
}
}
//-----------------------------------------------------------------------------
// Purpose: Store data back out to a stream (could be CUtlBuffer or filestream)
//-----------------------------------------------------------------------------
void CZipFile::SaveDirectory( IWriteStream& stream )
{
void *pPaddingBuffer = NULL;
if ( m_AlignmentSize )
{
// get a temp buffer for all padding work
pPaddingBuffer = malloc( m_AlignmentSize );
memset( pPaddingBuffer, 0x00, m_AlignmentSize );
}
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
#ifdef WIN32
FlushFileBuffers( m_hDiskCacheWriteFile );
#else
fflush( (FILE *)m_hDiskCacheWriteFile );
#endif
}
// Might be writing a zip into a larger stream
unsigned int zipOffsetInStream = stream.Tell();
int i;
for ( i = m_Files.FirstInorder(); i != m_Files.InvalidIndex(); i = m_Files.NextInorder( i ) )
{
CZipEntry *e = &m_Files[i];
Assert( e );
// Fix up the offset
e->m_ZipOffset = stream.Tell() - zipOffsetInStream;
if ( e->m_nCompressedSize > 0 && ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE ) )
{
// get the data back from the write cache
e->m_pData = malloc( e->m_nCompressedSize );
if ( e->m_pData )
{
CWin32File::FileSeek( m_hDiskCacheWriteFile, e->m_DiskCacheOffset, FILE_BEGIN );
CWin32File::FileRead( m_hDiskCacheWriteFile, e->m_pData, e->m_nCompressedSize );
}
}
if ( e->m_nCompressedSize > 0 && e->m_pData != NULL )
{
ZIP_LocalFileHeader hdr = { 0 };
hdr.signature = PKID( 3, 4 );
hdr.versionNeededToExtract = 10; // No special features or even compression here, set to 1.0
#ifdef ZIP_SUPPORT_LZMA_ENCODE
if ( e->m_eCompressionType == IZip::eCompressionType_LZMA )
{
// Per ZIP spec 5.8.8
hdr.versionNeededToExtract = 63;
}
#endif
hdr.flags = 0;
hdr.compressionMethod = e->m_eCompressionType;
hdr.lastModifiedTime = 0;
hdr.lastModifiedDate = 0;
hdr.crc32 = e->m_ZipCRC;
const char *pFilename = e->m_Name.String();
hdr.compressedSize = e->m_nCompressedSize;
hdr.uncompressedSize = e->m_nUncompressedSize;
hdr.fileNameLength = strlen( pFilename );
hdr.extraFieldLength = CalculatePadding( hdr.fileNameLength, e->m_ZipOffset );
int extraFieldLength = hdr.extraFieldLength;
// Swap header in place
m_Swap.SwapFieldsToTargetEndian( &hdr );
stream.Put( &hdr, sizeof( hdr ) );
stream.Put( pFilename, strlen( pFilename ) );
stream.Put( pPaddingBuffer, extraFieldLength );
stream.Put( e->m_pData, e->m_nCompressedSize );
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
free( e->m_pData );
// temp hackery for the logic below to succeed
e->m_pData = (void*)0xFFFFFFFF;
}
}
}
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
CWin32File::FileSeek( m_hDiskCacheWriteFile, 0, FILE_END );
}
unsigned int centralDirStart = stream.Tell() - zipOffsetInStream;
if ( m_AlignmentSize )
{
// align the central directory starting position
unsigned int newDirStart = AlignValue( centralDirStart, m_AlignmentSize );
int padLength = newDirStart - centralDirStart;
if ( padLength )
{
stream.Put( pPaddingBuffer, padLength );
centralDirStart = newDirStart;
}
}
int realNumFiles = 0;
for ( i = m_Files.FirstInorder(); i != m_Files.InvalidIndex(); i = m_Files.NextInorder( i ) )
{
CZipEntry *e = &m_Files[i];
Assert( e );
if ( e->m_nCompressedSize > 0 && e->m_pData != NULL )
{
ZIP_FileHeader hdr = { 0 };
hdr.signature = PKID( 1, 2 );
hdr.versionMadeBy = 20; // This is the version that the winzip that I have writes.
hdr.versionNeededToExtract = 10; // No special features or even compression here, set to 1.0
#ifdef ZIP_SUPPORT_LZMA_ENCODE
if ( e->m_eCompressionType == IZip::eCompressionType_LZMA )
{
// Per ZIP spec 5.8.8
hdr.versionNeededToExtract = 63;
}
#endif
hdr.flags = 0;
hdr.compressionMethod = e->m_eCompressionType;
hdr.lastModifiedTime = 0;
hdr.lastModifiedDate = 0;
hdr.crc32 = e->m_ZipCRC;
hdr.compressedSize = e->m_nCompressedSize;
hdr.uncompressedSize = e->m_nUncompressedSize;
hdr.fileNameLength = strlen( e->m_Name.String() );
hdr.extraFieldLength = CalculatePadding( hdr.fileNameLength, e->m_ZipOffset );
hdr.fileCommentLength = 0;
hdr.diskNumberStart = 0;
hdr.internalFileAttribs = 0;
hdr.externalFileAttribs = 0; // This is usually something, but zero is OK as if the input came from stdin
hdr.relativeOffsetOfLocalHeader = e->m_ZipOffset;
int extraFieldLength = hdr.extraFieldLength;
// Swap the header in place
m_Swap.SwapFieldsToTargetEndian( &hdr );
stream.Put( &hdr, sizeof( hdr ) );
stream.Put( e->m_Name.String(), strlen( e->m_Name.String() ) );
if ( m_bCompatibleFormat )
{
stream.Put( pPaddingBuffer, extraFieldLength );
}
realNumFiles++;
if ( m_hDiskCacheWriteFile != INVALID_HANDLE_VALUE )
{
// clear out temp hackery
e->m_pData = NULL;
}
}
}
unsigned int centralDirEnd = stream.Tell() - zipOffsetInStream;
if ( m_AlignmentSize )
{
// align the central directory starting position
unsigned int newDirEnd = AlignValue( centralDirEnd, m_AlignmentSize );
int padLength = newDirEnd - centralDirEnd;
if ( padLength )
{
stream.Put( pPaddingBuffer, padLength );
centralDirEnd = newDirEnd;
}
}
ZIP_EndOfCentralDirRecord rec = { 0 };
rec.signature = PKID( 5, 6 );
rec.numberOfThisDisk = 0;
rec.numberOfTheDiskWithStartOfCentralDirectory = 0;
rec.nCentralDirectoryEntries_ThisDisk = realNumFiles;
rec.nCentralDirectoryEntries_Total = realNumFiles;
rec.centralDirectorySize = centralDirEnd - centralDirStart;
rec.startOfCentralDirOffset = centralDirStart;
char commentString[128];
int commentLength = MakeXZipCommentString( commentString );
rec.commentLength = commentLength;
// Swap the header in place
m_Swap.SwapFieldsToTargetEndian( &rec );
stream.Put( &rec, sizeof( rec ) );
stream.Put( commentString, commentLength );
if ( pPaddingBuffer )
{
free( pPaddingBuffer );
}
}
class CZip : public IZip
{
public:
CZip( const char *pDiskCacheWritePath, bool bSortByName );
virtual ~CZip();
virtual void Reset() OVERRIDE;
// Add a single file to a zip - maintains the zip's previous alignment state
virtual void AddFileToZip( const char *relativename, const char *fullpath, eCompressionType compressionType ) OVERRIDE;
// Whether a file is contained in a zip - maintains alignment
virtual bool FileExistsInZip( const char *pRelativeName ) OVERRIDE;
// Reads a file from the zip - maintains alignement
virtual bool ReadFileFromZip( const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ) OVERRIDE;
virtual bool ReadFileFromZip( HANDLE hZipFile, const char *relativename, bool bTextMode, CUtlBuffer &buf ) OVERRIDE;
// Removes a single file from the zip - maintains alignment
virtual void RemoveFileFromZip( const char *relativename ) OVERRIDE;
// Gets next filename in zip, for walking the directory - maintains alignment
virtual int GetNextFilename( int id, char *pBuffer, int bufferSize, int &fileSize ) OVERRIDE;
// Prints the zip's contents - maintains alignment
virtual void PrintDirectory( void ) OVERRIDE;
// Estimate the size of the Zip (including header, padding, etc.)
virtual unsigned int EstimateSize( void ) OVERRIDE;
// Add buffer to zip as a file with given name - uses current alignment size, default 0 (no alignment)
virtual void AddBufferToZip( const char *relativename, void *data, int length,
bool bTextMode, eCompressionType compressionType ) OVERRIDE;
// Writes out zip file to a buffer - uses current alignment size
// (set by file's previous alignment, or a call to ForceAlignment)
virtual void SaveToBuffer( CUtlBuffer& outbuf ) OVERRIDE;
// Writes out zip file to a filestream - uses current alignment size
// (set by file's previous alignment, or a call to ForceAlignment)
virtual void SaveToDisk( FILE *fout ) OVERRIDE;
virtual void SaveToDisk( HANDLE hOutFile ) OVERRIDE;
// Reads a zip file from a buffer into memory - sets current alignment size to
// the file's alignment size, unless overridden by a ForceAlignment call)
virtual void ParseFromBuffer( void *buffer, int bufferlength ) OVERRIDE;
virtual HANDLE ParseFromDisk( const char *pFilename ) OVERRIDE;
// Forces a specific alignment size for all subsequent file operations, overriding files' previous alignment size.
// Return to using files' individual alignment sizes by passing FALSE.
virtual void ForceAlignment( bool aligned, bool bCompatibleFormat, unsigned int alignmentSize ) OVERRIDE;
// Sets the endianess of the zip
virtual void SetBigEndian( bool bigEndian ) OVERRIDE;
virtual void ActivateByteSwapping( bool bActivate ) OVERRIDE;
virtual unsigned int GetAlignment() OVERRIDE;
private:
CZipFile m_ZipFile;
};
static CUtlLinkedList< CZip* > g_ZipUtils;
IZip *IZip::CreateZip( const char *pDiskCacheWritePath, bool bSortByName )
{
CZip *pZip = new CZip( pDiskCacheWritePath, bSortByName );
g_ZipUtils.AddToTail( pZip );
return pZip;
}
void IZip::ReleaseZip( IZip *pZip )
{
g_ZipUtils.FindAndRemove( (CZip *)pZip );
delete ((CZip *)pZip);
}
CZip::CZip( const char *pDiskCacheWritePath, bool bSortByName ) : m_ZipFile( pDiskCacheWritePath, bSortByName )
{
m_ZipFile.Reset();
}
CZip::~CZip()
{
}
void CZip::SetBigEndian( bool bigEndian )
{
m_ZipFile.SetBigEndian( bigEndian );
}
void CZip::ActivateByteSwapping( bool bActivate )
{
m_ZipFile.ActivateByteSwapping( bActivate );
}
void CZip::AddFileToZip( const char *relativename, const char *fullpath, eCompressionType compressionType )
{
m_ZipFile.AddFileToZip( relativename, fullpath, compressionType );
}
bool CZip::FileExistsInZip( const char *pRelativeName )
{
return m_ZipFile.FileExistsInZip( pRelativeName );
}
bool CZip::ReadFileFromZip( const char *pRelativeName, bool bTextMode, CUtlBuffer &buf )
{
return m_ZipFile.ReadFileFromZip( pRelativeName, bTextMode, buf );
}
bool CZip::ReadFileFromZip( HANDLE hZipFile, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf )
{
return m_ZipFile.ReadFileFromZip( hZipFile, pRelativeName, bTextMode, buf );
}
void CZip::RemoveFileFromZip( const char *relativename )
{
m_ZipFile.RemoveFileFromZip( relativename );
}
int CZip::GetNextFilename( int id, char *pBuffer, int bufferSize, int &fileSize )
{
return m_ZipFile.GetNextFilename( id, pBuffer, bufferSize, fileSize );
}
void CZip::PrintDirectory( void )
{
m_ZipFile.PrintDirectory();
}
void CZip::Reset()
{
m_ZipFile.Reset();
}
unsigned int CZip::EstimateSize( void )
{
return m_ZipFile.CalculateSize();
}
// Add buffer to zip as a file with given name
void CZip::AddBufferToZip( const char *relativename, void *data, int length, bool bTextMode, eCompressionType compressionType )
{
m_ZipFile.AddBufferToZip( relativename, data, length, bTextMode, compressionType );
}
void CZip::SaveToBuffer( CUtlBuffer& outbuf )
{
m_ZipFile.SaveToBuffer( outbuf );
}
void CZip::SaveToDisk( FILE *fout )
{
m_ZipFile.SaveToDisk( fout );
}
void CZip::SaveToDisk( HANDLE hOutFile )
{
m_ZipFile.SaveToDisk( hOutFile );
}
void CZip::ParseFromBuffer( void *buffer, int bufferlength )
{
m_ZipFile.Reset();
m_ZipFile.ParseFromBuffer( buffer, bufferlength );
}
HANDLE CZip::ParseFromDisk( const char *pFilename )
{
m_ZipFile.Reset();
return m_ZipFile.ParseFromDisk( pFilename );
}
void CZip::ForceAlignment( bool aligned, bool bCompatibleFormat, unsigned int alignmentSize )
{
m_ZipFile.ForceAlignment( aligned, bCompatibleFormat, alignmentSize );
}
unsigned int CZip::GetAlignment()
{
return m_ZipFile.GetAlignment();
}