//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include #include #include #include #include "tier1/utlbuffer.h" #include "tier1/strtools.h" #include "tier2/riff.h" #if defined( _WIN32 ) && !defined( _X360 ) #include #endif #ifdef MAKE_GAMEDATA_TOOL #include "../public/materialsystem/shader_vcs_version.h" #include "../public/materialsystem/imaterial.h" #include "../public/materialsystem/hardwareverts.h" #include "../public/vtf/vtf.h" #else #include "materialsystem/shader_vcs_version.h" #include "materialsystem/imaterial.h" #include "materialsystem/hardwareverts.h" #endif #include "xwvfile.h" #include "xzp.h" CByteswap g_xzpSwap; extern IFileReadBinary *g_pSndIO; // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Datadesc blocks for byteswapping: //----------------------------------------------------------------------------- BEGIN_BYTESWAP_DATADESC( xZipHeader_t ) DEFINE_FIELD( Magic, FIELD_INTEGER ), DEFINE_FIELD( Version, FIELD_INTEGER ), DEFINE_FIELD( PreloadDirectoryEntries, FIELD_INTEGER ), DEFINE_FIELD( DirectoryEntries, FIELD_INTEGER ), DEFINE_FIELD( PreloadBytes, FIELD_INTEGER ), DEFINE_FIELD( HeaderLength, FIELD_INTEGER ), DEFINE_FIELD( FilenameEntries, FIELD_INTEGER ), DEFINE_FIELD( FilenameStringsOffset, FIELD_INTEGER ), DEFINE_FIELD( FilenameStringsLength, FIELD_INTEGER ), END_BYTESWAP_DATADESC() BEGIN_BYTESWAP_DATADESC( xZipDirectoryEntry_t ) DEFINE_FIELD( FilenameCRC, FIELD_INTEGER ), DEFINE_FIELD( Length, FIELD_INTEGER ), DEFINE_FIELD( StoredOffset, FIELD_INTEGER ), END_BYTESWAP_DATADESC() BEGIN_BYTESWAP_DATADESC( xZipFilenameEntry_t ) DEFINE_FIELD( FilenameCRC, FIELD_INTEGER ), DEFINE_FIELD( FilenameOffset, FIELD_INTEGER ), DEFINE_FIELD( TimeStamp, FIELD_INTEGER ), END_BYTESWAP_DATADESC() BEGIN_BYTESWAP_DATADESC( xZipFooter_t ) DEFINE_FIELD( Size, FIELD_INTEGER ), DEFINE_FIELD( Magic, FIELD_INTEGER ), END_BYTESWAP_DATADESC() CXZip::CXZip() { // Ensure that the header doesn't contain a valid magic yet. m_Header.Magic = 0; m_pPreloadedData = NULL; m_nPreloadStart = 0; m_pDirectory = NULL; m_pPreloadDirectory = NULL; m_nRegular2PreloadEntryMapping = NULL; m_bByteSwapped = false; m_pFilenames = NULL; m_hZip = NULL; m_pRead = NULL; m_hUser = 0; m_nMonitorLevel = 0; } CXZip::CXZip( const char* filename ) { // Ensure that the header doesn't contain a valid magic yet. m_Header.Magic = 0; m_nPreloadStart = 0; m_pPreloadedData = NULL; m_pDirectory = NULL; m_pPreloadDirectory = NULL; m_nRegular2PreloadEntryMapping = NULL; m_bByteSwapped = false; m_pFilenames = NULL; m_hZip = NULL; m_pRead = NULL; m_hUser = 0; m_nMonitorLevel = 0; Load( filename ); } CXZip::CXZip( FILE* handle, int offset, int size ) // file handle and offset of the zip file { m_pRead = NULL; m_hUser = 0; m_nPreloadStart = 0; m_pDirectory = NULL; m_pPreloadDirectory = NULL; m_nRegular2PreloadEntryMapping = NULL; m_bByteSwapped = false; m_pFilenames = NULL; m_pPreloadedData = NULL; m_nMonitorLevel = 0; Load( handle, offset, size ); } CXZip::~CXZip() { Unload(); } bool CXZip::InstallAlternateIO( int (*read)( void* buffer, int offset, int length, int nDestLength, int hUser), int hUser ) { m_pRead = read; m_hUser = hUser; return true; } // Loads an xZip file into memory: bool CXZip::Load( const char* filename, bool bPreload ) { FILE* hZip = fopen( filename, "rb" ); fseek(hZip,0,SEEK_END); int nSize = ftell( hZip ); return Load( hZip, 0, nSize, bPreload ); } bool CXZip::Load( FILE* handle, int nOffset, int nSize, bool bPreload ) // Load a pack file into this instance. Returns true on success. { Unload(); m_bByteSwapped = false; m_hZip = handle; m_nOffset = nOffset; m_nSize = nSize; // Hacky, clean up: if( m_hZip && !m_pRead ) { InstallAlternateIO( defaultRead, (int)m_hZip ); } if( m_hZip == NULL && m_pRead == NULL ) { return false; } // Read the header: m_pRead( &m_Header, 0, -1, sizeof(m_Header), m_hUser ); // Validate the Magic number and at the same time determine if I am reading a regular or swappped xZip file: switch( m_Swap.SourceIsNativeEndian( m_Header.Magic, xZipHeader_t::MAGIC ) ) { // Does the magic match exactly? case 1: m_Swap.ActivateByteSwapping( false ); m_bByteSwapped = false; break; // Does the magic match, but is swapped? case 0: m_bByteSwapped = true; m_Swap.ActivateByteSwapping( true ); // We must be reading the opposite endianness. m_Swap.SwapFieldsToTargetEndian( &m_Header ); break; default: assert( 0 ); // Fail gently in release: // The magic doesn't match in any respect: case -1: { printf("Invalid xZip file\n"); if( m_hZip ) { fclose( m_hZip ); m_hZip = NULL; } return false; } } // Validate the archive version: if( m_Header.Version != xZipHeader_t::VERSION ) { // Backward compatable support for version 1 Msg("Incorrect xZip version found %u - expected %u\n", m_Header.Version, xZipHeader_t::VERSION ); if( m_hZip ) { fclose( m_hZip ); m_hZip = NULL; } m_Header.Magic = xZipHeader_t::FREE; return false; } // Read the directory: { MEM_ALLOC_CREDIT(); m_pDirectory = (xZipDirectoryEntry_t*)malloc( sizeof(xZipDirectoryEntry_t) * m_Header.DirectoryEntries ); m_pRead( m_pDirectory, m_Header.HeaderLength, -1, sizeof( xZipDirectoryEntry_t ) * m_Header.DirectoryEntries, m_hUser ); // Swap the directory entries if nessecary if( m_bByteSwapped ) { for( unsigned nDirectoryEntry = 0; nDirectoryEntry < m_Header.DirectoryEntries; nDirectoryEntry++ ) { m_Swap.SwapFieldsToTargetEndian( &( m_pDirectory[nDirectoryEntry] ) ); } } m_nPreloadStart = m_Header.HeaderLength + ( sizeof( xZipDirectoryEntry_t ) * m_Header.DirectoryEntries ); } // Preload the preload chunk if desired: if( bPreload ) { PreloadData(); } return true; } void CXZip::Unload() { DiscardPreloadedData(); // Dump the directory: if( m_pDirectory ) { free( m_pDirectory ); m_pDirectory = NULL; } if( m_pFilenames ) { free( m_pFilenames ); m_pFilenames = NULL; } // Invalidate the header: m_Header.Magic = 0; if( m_hZip ) { fclose( m_hZip ); m_hZip = NULL; } } //----------------------------------------------------------------------------- // CXZip::PreloadData // // Loads the preloaded data if it isn't already. //----------------------------------------------------------------------------- void CXZip::PreloadData() { Assert( IsValid() ); // Ensure it isn't already preloaded if( m_pPreloadedData ) return; // If I don't have a preloaded section, ignore the request. if( !m_Header.PreloadBytes || !m_Header.PreloadDirectoryEntries ) return; // Allocate and read the data block in: #ifndef _X360 MEM_ALLOC_CREDIT_( "xZip" ); m_pPreloadedData = malloc( m_Header.PreloadBytes ); // Just drop out if allocation fails; if ( !m_pPreloadedData ) return; m_pRead( m_pPreloadedData, m_nPreloadStart, -1, m_Header.PreloadBytes, m_hUser ); #else int nAlignedStart = AlignValue( ( m_nPreloadStart - XBOX_HDD_SECTORSIZE ) + 1, XBOX_HDD_SECTORSIZE ); int nBytesToRead = AlignValue( ( m_nPreloadStart - nAlignedStart ) + m_Header.PreloadBytes, XBOX_HDD_SECTORSIZE ); int nBytesBuffer = AlignValue( nBytesToRead, XBOX_HDD_SECTORSIZE ); byte *pReadData = (byte *)malloc( nBytesBuffer ); // Just drop out if allocation fails; if ( !pReadData ) return; MEM_ALLOC_CREDIT_( "xZip" ); m_pRead( pReadData, nAlignedStart, nBytesBuffer,nBytesToRead, m_hUser ); m_pPreloadedData = pReadData + ( m_nPreloadStart - nAlignedStart ); #endif // Set up the preload directory: m_pPreloadDirectory = (xZipDirectoryEntry_t*)m_pPreloadedData; // Swap the preload directory: if ( m_bByteSwapped ) { for ( unsigned nDirectoryEntry = 0; nDirectoryEntry < m_Header.PreloadDirectoryEntries; nDirectoryEntry++ ) { m_Swap.SwapFieldsToTargetEndian( &( m_pPreloadDirectory[nDirectoryEntry] ) ); } } // Set up the regular 2 preload mapping section: m_nRegular2PreloadEntryMapping = (unsigned short*)(((unsigned char*)m_pPreloadDirectory) + ( sizeof(xZipDirectoryEntry_t) * m_Header.PreloadDirectoryEntries )); // Swap the regular to preload mapping if ( m_bByteSwapped ) { m_Swap.SwapBufferToTargetEndian( (short *)m_nRegular2PreloadEntryMapping, (short *)m_nRegular2PreloadEntryMapping, m_Header.DirectoryEntries ); } } //----------------------------------------------------------------------------- // CXZip::DiscardPreloadedData // // frees the preloaded data cache if it's present. //----------------------------------------------------------------------------- void CXZip::DiscardPreloadedData() { if ( m_pPreloadedData ) { #ifndef _X360 free( m_pPreloadedData ); #else int nAlignedStart = AlignValue( ( m_nPreloadStart - XBOX_HDD_SECTORSIZE ) + 1, XBOX_HDD_SECTORSIZE ); byte *pReadData = (byte *)m_pPreloadedData - ( m_nPreloadStart - nAlignedStart ); free( pReadData ); #endif m_pPreloadedData = NULL; m_pPreloadDirectory = NULL; m_nRegular2PreloadEntryMapping = NULL; } } int CXZip::defaultRead( void* buffer, int offset, int destLength, int length, int hUser) { fseek( (FILE*)hUser, offset, SEEK_SET ); return fread( buffer, 1, length, (FILE*)hUser ); } char* CXZip::GetEntryFileName( unsigned CRC, char* pDefault ) { Assert( IsValid() ); if( IsRetail() ) { return pDefault; } else { // Make sure I have a filename section: if( m_Header.FilenameStringsOffset == 0 || m_Header.FilenameEntries == 0 || CRC == 0 ) { return pDefault; } // If the filename chunk isn't here, load it up: if( !m_pFilenames ) { MEM_ALLOC_CREDIT_("xZip"); m_pFilenames = (xZipFilenameEntry_t*)malloc( m_Header.FilenameStringsLength ); m_pRead( m_pFilenames, m_Header.FilenameStringsOffset, -1, m_Header.FilenameStringsLength, m_hUser ); // TODO: Swap! for( unsigned int i=0; i< m_Header.FilenameEntries;i++ ) { m_Swap.SwapFieldsToTargetEndian(&m_pFilenames[i]); } } // Find this entry in the preload directory xZipFilenameEntry_t entry; entry.FilenameCRC = CRC; xZipFilenameEntry_t* found = (xZipFilenameEntry_t*)bsearch( &entry, m_pFilenames, m_Header.FilenameEntries, sizeof(xZipFilenameEntry_t), xZipFilenameEntry_t::xZipFilenameEntryCompare ); if( !found ) return pDefault; return (((char*)m_pFilenames) + found->FilenameOffset) - m_Header.FilenameStringsOffset; } } // Sanity checks that the zip file is ready and readable: bool CXZip::IsValid() { if( m_Header.Magic != xZipHeader_t::MAGIC ) return false; if( m_Header.Version > xZipHeader_t::VERSION ) return false; if( !m_pDirectory ) return false; return true; } void CXZip::WarningDir() { Assert( IsValid()); for( unsigned i = 0; i< m_Header.DirectoryEntries; i++ ) { Msg( GetEntryFileName( m_pDirectory[i].FilenameCRC ) ); } } int CXZip::ReadIndex( int nEntryIndex, int nFileOffset, int nDestBytes, int nLength, void* pBuffer ) { Assert( IsValid() ); if( nLength <=0 || nEntryIndex < 0 ) return 0; // HACK HACK HACK - convert the pack file index to a local file index (ie, assuming the full file index is being passed in) nFileOffset -= m_pDirectory[nEntryIndex].StoredOffset; // HACK HACK HACK // If I've got my preload section loaded, first check there: xZipDirectoryEntry_t* pPreloadEntry = GetPreloadEntry(nEntryIndex); if( pPreloadEntry ) { Assert( pPreloadEntry->FilenameCRC == m_pDirectory[nEntryIndex].FilenameCRC ); if( nFileOffset + nLength <= (int)pPreloadEntry->Length ) { if( m_nMonitorLevel >= 2 ) { char* filename = GetEntryFileName( m_pDirectory[nEntryIndex].FilenameCRC, "(!!! unknown !!!)" ); Msg("PACK(preload) %s: length:%i offset:%i",filename,nLength, nFileOffset); } memcpy( pBuffer, (char*)m_pPreloadedData + pPreloadEntry->StoredOffset + nFileOffset - m_nPreloadStart, nLength ); return nLength; } } // Offset int the zip to start the read: int ZipOffset = m_pDirectory[nEntryIndex].StoredOffset + nFileOffset; int nBytesRead = m_pRead( pBuffer, ZipOffset, nDestBytes, nLength, m_hUser); if( m_nMonitorLevel ) { char* filename = GetEntryFileName( m_pDirectory[nEntryIndex].FilenameCRC, "(!!! unknown !!!)" ); unsigned preload = 0; if( m_pPreloadedData && m_nRegular2PreloadEntryMapping[nEntryIndex] != 0xFFFF ) { // Find this entry in the preload directory xZipDirectoryEntry_t* entry = &(m_pPreloadDirectory[m_nRegular2PreloadEntryMapping[nEntryIndex]]); Assert(entry->FilenameCRC == m_pDirectory[nEntryIndex].FilenameCRC); preload = entry->Length; } Msg("PACK %s: length:%i offset:%i (preload bytes:%i)",filename,nLength, nFileOffset, preload); } return nBytesRead; } bool CXZip::GetSimpleFileOffsetLength( const char* FileName, int& nBaseIndex, int &nFileOffset, int &nLength ) { Assert( IsValid() ); xZipDirectoryEntry_t entry; entry.FilenameCRC = xZipCRCFilename( FileName ); xZipDirectoryEntry_t* found = (xZipDirectoryEntry_t*)bsearch( &entry, m_pDirectory, m_Header.DirectoryEntries, sizeof(xZipDirectoryEntry_t), xZipDirectoryEntry_t::xZipDirectoryEntryFindCompare ); if( found == NULL ) return false; nFileOffset = found[0].StoredOffset; nLength = found[0].Length; nBaseIndex = (((int)((char*)found - (char*)m_pDirectory))/sizeof(xZipDirectoryEntry_t)); return true; } bool CXZip::ExtractFile( const char* FileName ) { return false; } // Compares to xZipDirectoryEntries. // // Sorts in the following order: // FilenameCRC // FileOffset // Length // StoredOffset // // The sort function may look overly complex, but it is actually useful for locating different pieces of // the same file in a meaningful order. // int __cdecl xZipDirectoryEntry_t::xZipDirectoryEntrySortCompare( const void* left, const void* right ) { xZipDirectoryEntry_t *l = (xZipDirectoryEntry_t*)left, *r = (xZipDirectoryEntry_t*)right; if( l->FilenameCRC < r->FilenameCRC ) { return -1; } else if( l->FilenameCRC > r->FilenameCRC ) { return 1; } // else l->FileOffset == r->FileOffset if( l->Length < r->Length ) { return -1; } else if( l->Length > r->Length ) { return 1; } // else l->Length == r->Length if( l->StoredOffset < r->StoredOffset ) { return -1; } else if( l->StoredOffset > r->StoredOffset ) { return 1; } // else everything is identical: return 0; } // Find an entry with matching CRC only int __cdecl xZipDirectoryEntry_t::xZipDirectoryEntryFindCompare( const void* left, const void* right ) { xZipDirectoryEntry_t *l = (xZipDirectoryEntry_t*)left, *r = (xZipDirectoryEntry_t*)right; if( l->FilenameCRC < r->FilenameCRC ) { return -1; } else if( l->FilenameCRC > r->FilenameCRC ) { return 1; } return 0; } int __cdecl xZipFilenameEntry_t::xZipFilenameEntryCompare( const void* left, const void* right ) { xZipFilenameEntry_t *l = (xZipFilenameEntry_t*)left, *r = (xZipFilenameEntry_t*)right; if( l->FilenameCRC < r->FilenameCRC ) { return -1; } else if( l->FilenameCRC > r->FilenameCRC ) { return 1; } return 0; } // CRC's an individual xZip filename: unsigned xZipCRCFilename( const char* filename ) { unsigned hash = 0xAAAAAAAA; // Alternating 1's and 0's for( ; *filename ; filename++ ) { char c = *filename; // Fix slashes if( c == '/' ) c = '\\'; else c = (char)tolower(c); hash = hash * 33 + c; } return hash; } #if defined( MAKE_GAMEDATA_TOOL ) // ------------ xZipHeader_t Header; xZipDirectoryEntry_t *pDirectoryEntries = NULL; xZipDirectoryEntry_t *pPreloadDirectoryEntries = NULL; xZipFilenameEntry_t *pFilenameEntries = NULL; char *pFilenameData = NULL; unsigned nFilenameDataLength = 0; unsigned InputFileBytes = 0; char* CleanFilename( char* filename ) { // Trim leading white space: while( isspace(*filename) ) filename++; // Trim trailing white space: while( isspace( filename[strlen(filename)-1] ) ) { filename[strlen(filename)-1] = '\0'; } return filename; } bool CopyFileBytes( FILE* hDestination, FILE* hSource, unsigned nBytes ) { char buffer[16384]; while( nBytes > 0 ) { int nBytesRead = fread( buffer, 1, nBytes > sizeof(buffer) ? sizeof(buffer) : nBytes, hSource ); fwrite(buffer, 1, nBytesRead, hDestination ); nBytes -= nBytesRead; } return true; } bool WriteFileBytes( FILE* hDestination, CUtlBuffer &source, unsigned nBytes ) { unsigned int nBytesWritten = fwrite(source.Base(), 1, nBytes, hDestination ); return (nBytesWritten == nBytes); } void PadFileBytes(FILE* hFile, int nPreloadPadding ) { if( nPreloadPadding < 0 || nPreloadPadding >= 512) { puts("Invalid padding"); return; } char padding[512]; memset(padding,0,nPreloadPadding); fwrite(padding,1,nPreloadPadding,hFile); } void AddFilename( const char* filename ) { unsigned CRCfilename = xZipCRCFilename( filename ); // If we already have this filename don't add it again: for( int i = 0; i < (int)Header.FilenameEntries; i++ ) { if( pFilenameEntries[i].FilenameCRC == CRCfilename ) { return; } } Header.FilenameEntries++; // Add the file to the file string table: pFilenameEntries = (xZipFilenameEntry_t*)realloc( pFilenameEntries, sizeof(xZipFilenameEntry_t) * Header.FilenameEntries ); int filenameLength = (int)strlen(filename) + 1; pFilenameEntries[Header.FilenameEntries-1].FilenameCRC = CRCfilename; pFilenameEntries[Header.FilenameEntries-1].FilenameOffset = nFilenameDataLength; // Grab the timestamp for the file: struct stat buf; if( stat( filename, &buf ) != -1 ) { pFilenameEntries[Header.FilenameEntries - 1].TimeStamp = buf.st_mtime; } else { pFilenameEntries[Header.FilenameEntries - 1].TimeStamp = 0; } nFilenameDataLength += filenameLength; pFilenameData = (char*)realloc(pFilenameData, nFilenameDataLength); memcpy(pFilenameData + nFilenameDataLength - filenameLength, filename, filenameLength); } FILE* hTempFilePreload; FILE* hTempFileData; FILE* hOutputFile; bool xZipAddFile( const char* filename, CUtlBuffer &fileBuff, bool bPrecacheEntireFile, bool bProcessPrecacheHeader, bool bProcessPrecacheHeaderOnly ) { unsigned int fileSize = fileBuff.TellMaxPut(); // Track total input bytes for stats reasons InputFileBytes += fileSize; unsigned customPreloadSize = 0; if( bPrecacheEntireFile ) { customPreloadSize = fileSize; } else if( bProcessPrecacheHeader ) { customPreloadSize = xZipComputeCustomPreloads( filename ); } else if( bProcessPrecacheHeaderOnly ) { customPreloadSize = xZipComputeCustomPreloads( filename ); fileSize = min( fileSize, customPreloadSize ); } unsigned CRC = xZipCRCFilename( filename ); // Does this file have a split header? if( customPreloadSize > 0 ) { // Initialize the entry header: xZipDirectoryEntry_t entry; memset( &entry, 0, sizeof( entry ) ); entry.FilenameCRC = CRC; entry.Length = customPreloadSize; entry.StoredOffset = ftell(hTempFilePreload); // Add the directory entry to the preload table: Header.PreloadDirectoryEntries++; pPreloadDirectoryEntries = (xZipDirectoryEntry_t*)realloc( pPreloadDirectoryEntries, sizeof( xZipDirectoryEntry_t ) * Header.PreloadDirectoryEntries ); memcpy( pPreloadDirectoryEntries + Header.PreloadDirectoryEntries - 1, &entry, sizeof( entry ) ); // Concatenate the data in the preload file: fileBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); WriteFileBytes( hTempFilePreload, fileBuff, entry.Length ); fileBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); // Add the filename entry: AddFilename( filename ); // Spew it: printf("+Preload: \"%s\": Length:%u\n", filename, entry.Length ); } // Copy the file to the regular data region: xZipDirectoryEntry_t entry; memset(&entry,0,sizeof(entry)); entry.FilenameCRC = CRC; entry.Length = fileSize; entry.StoredOffset = ftell(hTempFileData); // Add the directory entry to the table: Header.DirectoryEntries++; pDirectoryEntries = (xZipDirectoryEntry_t*)realloc( pDirectoryEntries, sizeof( xZipDirectoryEntry_t ) * Header.DirectoryEntries ); memcpy( pDirectoryEntries + Header.DirectoryEntries - 1, &entry, sizeof( entry ) ); WriteFileBytes( hTempFileData, fileBuff, entry.Length ); // Align the data region to a 512 byte boundry: (has to be on last entry as well to ensure enough space to perform the final read, // and initial alignment is taken careof by assembexzip) int nPadding = ( XBOX_HDD_SECTORSIZE - ( ftell( hTempFileData ) % XBOX_HDD_SECTORSIZE) ) % XBOX_HDD_SECTORSIZE; PadFileBytes( hTempFileData, nPadding ); // Add the file to the file string table: AddFilename( filename ); // Print a summary printf("+File: \"%s\": Length:%u Padding:%i\n", filename, entry.Length, nPadding ); return true; } bool xZipAddFile( const char* zipname, bool bPrecacheEntireFile, bool bProcessPrecacheHeader, bool bProcessPrecacheHeaderOnly ) { // Clean up the filename: char buffer[MAX_PATH]; strcpy(buffer, zipname); // Fix slashes and convert it to lower case: char *filename; for( filename = buffer; *filename; filename++ ) { if( *filename == '/' ) *filename = '\\'; else { *filename = (char)tolower(*filename); } } // Skip leading white space: for( filename = buffer; isspace(*filename); filename++ ) ; // Obliterate trailing white space: for(;;) { int len = (int)strlen( filename ); if( len <= 0 ) { printf("!!!! BAD FILENAME: \"%s\"\n", filename ); return false; } if( isspace( filename[len-1] ) ) filename[len-1]='\0'; else break; } // Ensure we don't already have this file: unsigned CRC = xZipCRCFilename( filename ); for( unsigned i=0; i < Header.DirectoryEntries; i++ ) { if( pDirectoryEntries[i].FilenameCRC == CRC ) { printf("!!!! NOT ADDING DUPLICATE FILENAME: \"%s\"\n", filename ); return false; } } // Attempt to open the file: FILE* hFile = fopen( filename, "rb" ); if( !hFile ) { printf("!!!! FAILED TO OPEN FILE: \"%s\"\n", filename ); return false; } // Get the length of the file: fseek(hFile,0,SEEK_END); unsigned fileSize = ftell(hFile); fseek(hFile,0,SEEK_SET); CUtlBuffer fileBuff; fileBuff.EnsureCapacity( fileSize ); fread( fileBuff.Base(), fileSize, 1, hFile ); fclose( hFile ); fileBuff.SeekPut( CUtlBuffer::SEEK_HEAD, fileSize ); return xZipAddFile( zipname, fileBuff, bPrecacheEntireFile, bProcessPrecacheHeader, bProcessPrecacheHeaderOnly ); } int xZipBegin( const char* fileNameXzip ) { // Create and initialize the header: memset( &Header, 0, sizeof(Header) ); // Zero out the header: Header.Magic = xZipHeader_t::MAGIC; Header.Version = xZipHeader_t::VERSION; Header.HeaderLength = sizeof(Header); // Open the output file: hOutputFile = fopen(fileNameXzip,"wb+"); if( !hOutputFile ) { printf("Failed to open \"%s\" for writing.\n", fileNameXzip); exit( EXIT_FAILURE); } // Create a temporary file for storing the preloaded data: hTempFilePreload = tmpfile(); if( !hTempFilePreload ) { printf( "Error: failed to create temporary file\n" ); return EXIT_FAILURE; } // Create a temporary file for storing the non preloaded data hTempFileData = tmpfile(); if( !hTempFileData ) { printf( "Error: failed to create temporary file\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } bool xZipEnd() { int nPreloadDirectorySize = sizeof(xZipDirectoryEntry_t)*Header.PreloadDirectoryEntries; int nRegular2PreloadSize = sizeof(unsigned short) * Header.DirectoryEntries; // Compute the size of the preloaded section: if( Header.PreloadDirectoryEntries ) { fseek( hTempFilePreload, 0, SEEK_END ); Header.PreloadBytes = ftell(hTempFilePreload) + nPreloadDirectorySize + nRegular2PreloadSize; // Raw# of bytes to preload fseek( hTempFilePreload, 0, SEEK_SET ); } else { Header.PreloadBytes = 0; } // Number of bytes preceeding the preloaded section: int nPreloadOffset = sizeof( Header ) + ( sizeof( xZipDirectoryEntry_t ) * Header.DirectoryEntries ); // Number of bytes to pad between the end of the preload section and the start of the data section: int nPadding = ( 512 - ( ( nPreloadOffset + Header.PreloadBytes ) % 512) ) %512; // Number of alignment bytes after the preload section // Offset past the preload section: int nDataOffset = nPreloadOffset + Header.PreloadBytes + nPadding; // Write out the header: (will need to be rewritten at the end as well) - note: not even bothering to byteswap at this point fwrite(&Header,sizeof(Header),1,hOutputFile); // Fixup each of the directory entries to make them relative to the beginning of the file. for( unsigned i=0; i< Header.DirectoryEntries;i++ ) { xZipDirectoryEntry_t* pDir = &(pDirectoryEntries[i]); // Adjust files in the regular data area: pDir->StoredOffset = nDataOffset + pDir->StoredOffset; } // Sort and write the directory: printf("Sorting and writing %i directory entries...\n",Header.DirectoryEntries); qsort(pDirectoryEntries,Header.DirectoryEntries,sizeof(xZipDirectoryEntry_t),&xZipDirectoryEntry_t::xZipDirectoryEntrySortCompare); // Swap the directory entries: for( unsigned i=0; i < Header.DirectoryEntries; i++ ) { g_xzpSwap.SwapFieldsToTargetEndian(&pDirectoryEntries[i]); } fwrite(pDirectoryEntries,Header.DirectoryEntries*sizeof(xZipDirectoryEntry_t),1, hOutputFile); // Swap the directory back for later use: for( unsigned i=0; i < Header.DirectoryEntries; i++ ) { g_xzpSwap.SwapFieldsToTargetEndian(&pDirectoryEntries[i]); } // Copy the preload section: if( Header.PreloadBytes > 0 ) { printf("Generating the preload section...(%u)\n", Header.PreloadBytes); // Fixup each of the directory entries to make them relative to the beginning of the file. for( unsigned i=0; i< Header.PreloadDirectoryEntries;i++ ) { xZipDirectoryEntry_t* pDir = &(pPreloadDirectoryEntries[i]); // Shift preload data down by preload bytes (and skipping over the directory): pDir->StoredOffset += nPreloadOffset + nPreloadDirectorySize + nRegular2PreloadSize; } printf("Sorting %u preload directory entries...\n",Header.PreloadDirectoryEntries); qsort(pPreloadDirectoryEntries,Header.PreloadDirectoryEntries,sizeof(xZipDirectoryEntry_t),&xZipDirectoryEntry_t::xZipDirectoryEntrySortCompare); printf("Building regular to preload mapping table for %u entries...\n", Header.DirectoryEntries ); unsigned short* Regular2Preload = (unsigned short*)malloc( nRegular2PreloadSize ); for( unsigned i = 0; i < Header.DirectoryEntries; i++ ) { unsigned short j; for( j = 0; j < Header.PreloadDirectoryEntries; j++ ) { if( pDirectoryEntries[i].FilenameCRC == pPreloadDirectoryEntries[j].FilenameCRC ) break; } // If I couldn't find it mark it as non-existant: if( j == Header.PreloadDirectoryEntries ) j = 0xFFFF; Regular2Preload[i] = j; } printf("Writing preloaded directory entreis...\n" ); // Swap the preload directory entries: for( unsigned i=0; i < Header.PreloadDirectoryEntries; i++ ) { g_xzpSwap.SwapFieldsToTargetEndian(&pPreloadDirectoryEntries[i]); } fwrite( pPreloadDirectoryEntries, Header.PreloadDirectoryEntries*sizeof(xZipDirectoryEntry_t),1, hOutputFile ); // Swap them back: for( unsigned i=0; i < Header.PreloadDirectoryEntries; i++ ) { g_xzpSwap.SwapFieldsToTargetEndian(&pPreloadDirectoryEntries[i]); } printf("Writing regular to preload mapping (%u bytes)...\n", sizeof(unsigned short)*Header.DirectoryEntries ); // Swap regular to preload mapping: g_xzpSwap.SwapBufferToTargetEndian((short*)Regular2Preload, (short*)Regular2Preload, nRegular2PreloadSize / sizeof(short) ); fwrite( Regular2Preload, nRegular2PreloadSize,1,hOutputFile ); // Swap it back g_xzpSwap.SwapBufferToTargetEndian((short*)Regular2Preload, (short*)Regular2Preload, nRegular2PreloadSize / sizeof(short) ); printf("Copying %u Preloadable Bytes...\n", Header.PreloadBytes - nPreloadDirectorySize - nRegular2PreloadSize ); fseek(hTempFilePreload,0,SEEK_SET); CopyFileBytes(hOutputFile, hTempFilePreload, Header.PreloadBytes - nPreloadDirectorySize - nRegular2PreloadSize ); } // Align the data section following the preload section: if( nPadding ) { printf("Aligning Data Section Start by %u bytes...\n", nPadding ); PadFileBytes(hOutputFile, nPadding ); } // Copy the data section: fseek(hTempFileData, 0, SEEK_END ); unsigned length = ftell( hTempFileData ); fseek(hTempFileData, 0, SEEK_SET ); printf("Copying %u Bytes...\n",length); CopyFileBytes(hOutputFile, hTempFileData, length); // Write out the filename data if present: if( nFilenameDataLength && Header.FilenameEntries ) { Header.FilenameStringsOffset = ftell(hOutputFile); Header.FilenameStringsLength = (Header.FilenameEntries*sizeof(xZipFilenameEntry_t)) + nFilenameDataLength; // Adjust the offset in each of the filename offsets to absolute position in the file. for( unsigned i=0;i(&pFilenameEntries[i]); } fwrite(pFilenameEntries,1,Header.FilenameEntries*sizeof(xZipFilenameEntry_t),hOutputFile); // Swap them back: for( unsigned int i = 0; i < Header.FilenameEntries; i++ ) { g_xzpSwap.SwapFieldsToTargetEndian(&pFilenameEntries[i]); } printf("Writing %u bytes of filename data...\n",nFilenameDataLength); fwrite(pFilenameData,1,nFilenameDataLength,hOutputFile); } // Compute the total file size, including the size of the footer: unsigned OutputFileBytes = ftell(hOutputFile) + sizeof(xZipFooter_t); // Write the footer: (block used to keep possibly swapped footer from being used later) { xZipFooter_t footer; footer.Magic = xZipFooter_t::MAGIC; footer.Size = OutputFileBytes; g_xzpSwap.SwapFieldsToTargetEndian( &footer ); // Swap the footer fwrite( &footer, 1, sizeof(footer), hOutputFile ); } // Seek back and rewrite the header (filename data changes it for example) fseek(hOutputFile,0,SEEK_SET); g_xzpSwap.SwapFieldsToTargetEndian( &Header ); // Swap it to write out: fwrite(&Header,1,sizeof(Header),hOutputFile); g_xzpSwap.SwapFieldsToTargetEndian( &Header ); // But then swap it back so we can use it in memory // Shut down fclose(hOutputFile); // Print the summary printf("\n\nSummary: Input:%u, XZip:%u, Directory Entries:%u (%u preloaded), Preloaded Bytes:%u\n\n",InputFileBytes,OutputFileBytes,Header.DirectoryEntries, Header.PreloadDirectoryEntries, Header.PreloadBytes); // Shut down: fclose(hTempFileData); fclose(hTempFilePreload); return true; } #define PADD_ID MAKEID('P','A','D','D') //----------------------------------------------------------------------------- // xZipComputeWAVPreload // // Returns the number of bytes from a xbox compliant WAV file that should go into // the preload section: //----------------------------------------------------------------------------- unsigned xZipComputeWAVPreload( char *pFileName ) { InFileRIFF riff( pFileName, *g_pSndIO ); if ( riff.RIFFName() != RIFF_WAVE ) { return 0; } IterateRIFF walk( riff, riff.RIFFSize() ); while ( walk.ChunkAvailable() ) { // xbox compliant wavs have a single PADD chunk if ( walk.ChunkName() == PADD_ID ) { // want to preload data up through PADD chunk header // and not the actual pad bytes return walk.ChunkFilePosition() + 2*sizeof( int ); } walk.ChunkNext(); } return 0; } //----------------------------------------------------------------------------- // xZipComputeXWVPreload // // Returns the number of bytes from a XWV file that should go into the preload // section: //----------------------------------------------------------------------------- unsigned xZipComputeXWVPreload( const char* filename ) { FILE* hFile = fopen( filename, "rb" ); if ( !hFile ) { printf( "Failed to open xwv file: %s\n", filename ); return 0; } // Read and validate the XWV header: xwvHeader_t header; memset( &header, 0, sizeof(header) ); fread( &header, 1, sizeof(header), hFile ); fclose( hFile ); if ( header.id != XWV_ID || header.headerSize != sizeof(header) ) return 0; return header.GetPreloadSize(); } unsigned xZipComputeXTFPreload( const char* filename ) { #if 0 // X360TBD: Not using XTF anymore FILE* hFile = fopen( filename, "rb" ); if ( !hFile ) { printf("Failed to open file: %s\n", filename); return 0; } XTFFileHeader_t header; memset( &header,0, sizeof( header ) ); fread( &header,1,sizeof(header),hFile); fclose(hFile); if ( !strncmp( header.fileTypeString, "XTF", 4 ) ) return header.preloadDataSize; #endif return 0; } // TODO: ONLY store them in the preload section: unsigned xZipComputeVMTPreload( const char* filename ) { // Store VMT's entirely if ( !strstr(filename,".vmt") ) return 0; FILE* hFile = fopen( filename, "rb" ); if ( !hFile ) { printf("Failed to open file: %s\n", filename); return 0; } fseek( hFile, 0, SEEK_END ); unsigned offset = ftell( hFile ); fclose( hFile ); return offset; } // TODO: ONLY store them in the preload section: unsigned xZipComputeVHVPreload( const char* filename ) { // Store VMT's entirely if ( !strstr(filename,".vhv") ) return 0; FILE* hFile = fopen( filename, "rb" ); if ( !hFile ) { printf("Failed to open file: %s\n", filename); return 0; } fclose( hFile ); // Just load the header: return sizeof(HardwareVerts::FileHeader_t); } unsigned xZipComputeXCSPreload( const char* filename ) { if( !strstr(filename,".vcs") ) return 0; FILE* hFile = fopen( filename, "rb" ); if ( !hFile ) { printf("Failed to open file: %s\n", filename); return 0; } XShaderHeader_t header; fread(&header,1,sizeof(XShaderHeader_t), hFile); fseek(hFile,0,SEEK_END); fclose(hFile); if (!header.IsValid()) return 0; return header.BytesToPreload(); } unsigned xZipComputeCustomPreloads( const char* filename ) { // X360TBD: These all need to act on a utlbuffer Assert( 0 ); return 0; // strlwr(filename); unsigned offset = xZipComputeXWVPreload( filename ); if ( offset ) return offset; offset = xZipComputeVMTPreload( filename ); if ( offset ) return offset; offset = xZipComputeXCSPreload( filename ); if ( offset ) return offset; offset = xZipComputeVHVPreload( filename ); if ( offset ) return offset; return xZipComputeXTFPreload( filename ); } #endif // MAKE_GAMEDATA_TOOL