diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/xbox/MakeGameData/MakeZip.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/xbox/MakeGameData/MakeZip.cpp')
| -rw-r--r-- | utils/xbox/MakeGameData/MakeZip.cpp | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/utils/xbox/MakeGameData/MakeZip.cpp b/utils/xbox/MakeGameData/MakeZip.cpp new file mode 100644 index 0000000..69950b0 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeZip.cpp @@ -0,0 +1,593 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "MakeGameData.h" + +static CUtlSymbolTable g_CriticalPreloadTable( 0, 32, true ); + +//----------------------------------------------------------------------------- +// Purpose: Compute preload data by file type. Calls into appropriate libraries +// to get the preload info. Libraries would use filename to generate +// the preload if there is a compilation step, otherwise the file buffer +// is a buffer loaded filename. +//----------------------------------------------------------------------------- +static bool GetPreloadBuffer( const char *pFilename, CUtlBuffer &fileBuffer, CUtlBuffer &preloadBuffer ) +{ + char fileExtension[MAX_PATH]; + Q_ExtractFileExtension( pFilename, fileExtension, sizeof( fileExtension ) ); + + // adding an entire file IS ONLY for files that are expected to be read by the game as a single read + // NOT for files that have any seek pattern + bool bAddEntireFile = false; + + // trivial small files, always add + if ( !Q_stricmp( fileExtension, "txt" ) || + !Q_stricmp( fileExtension, "dat" ) || + !Q_stricmp( fileExtension, "lst" ) || + !Q_stricmp( fileExtension, "res" ) || + !Q_stricmp( fileExtension, "vmt" ) || + !Q_stricmp( fileExtension, "cfg" ) || + !Q_stricmp( fileExtension, "bnf" ) || + !Q_stricmp( fileExtension, "rc" ) || + !Q_stricmp( fileExtension, "vbf" ) || + !Q_stricmp( fileExtension, "vfe" ) || + !Q_stricmp( fileExtension, "pcf" ) || + !Q_stricmp( fileExtension, "inf" ) ) + { + bAddEntireFile = true; + } + + // critical resources get blindly added to the preload + if ( !bAddEntireFile && ( g_CriticalPreloadTable.Find( pFilename ) != UTL_INVAL_SYMBOL ) ) + { + bAddEntireFile = true; + } + + if ( bAddEntireFile && LZMA_IsCompressed( (unsigned char *)fileBuffer.Base() ) ) + { + // sorry, not allowed to add entirely to preload if already compressed + // breaks the run-time filesystem due to inability to deliver file as-is + bAddEntireFile = false; + } + + if ( bAddEntireFile ) + { + if ( fileBuffer.TellMaxPut() >= 1*1024 ) + { + // only compress preload files of reasonable size + unsigned int compressedSize = 0; + unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)fileBuffer.Base(), + fileBuffer.TellMaxPut(), &compressedSize ); + if ( pCompressedOutput ) + { + // add as compressed + preloadBuffer.EnsureCapacity( compressedSize ); + preloadBuffer.Put( pCompressedOutput, compressedSize ); + free( pCompressedOutput ); + return true; + } + } + + // add entire file to preload section + preloadBuffer.EnsureCapacity( fileBuffer.TellMaxPut() ); + preloadBuffer.Put( fileBuffer.Base(), fileBuffer.TellMaxPut() ); + return true; + } + + // Each library will fetch the optional preload data into caller's buffer + if ( !Q_stricmp( fileExtension, "wav" ) ) + { + return GetPreloadData_WAV( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vtf" ) ) + { + return GetPreloadData_VTF( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vcs" ) ) + { + return GetPreloadData_VCS( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vhv" ) ) + { + return GetPreloadData_VHV( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vtx" ) ) + { + return GetPreloadData_VTX( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vvd" ) ) + { + return GetPreloadData_VVD( pFilename, fileBuffer, preloadBuffer ); + } + + // others... + return false; +} + +void SetupCriticalPreloadScript( const char *pModPath ) +{ + characterset_t breakSet; + CharacterSetBuild( &breakSet, "" ); + + // purge any prior entries + g_CriticalPreloadTable.RemoveAll(); + + // populate table + char szCriticaList[MAX_PATH]; + char szToken[MAX_PATH]; + V_ComposeFileName( pModPath, "scripts\\preload_xbox.xsc", szCriticaList, sizeof( szCriticaList ) ); + CUtlBuffer criticalListBuffer; + if ( ReadFileToBuffer( szCriticaList, criticalListBuffer, true, true ) ) + { + for ( ;; ) + { + int nTokenSize = criticalListBuffer.ParseToken( &breakSet, szToken, sizeof( szToken ) ); + if ( nTokenSize <= 0 ) + { + break; + } + + V_strlower( szToken ); + V_FixSlashes( szToken, CORRECT_PATH_SEPARATOR ); + if ( UTL_INVAL_SYMBOL == g_CriticalPreloadTable.Find( szToken ) ) + { + g_CriticalPreloadTable.AddString( szToken ); + } + } + } +} + +CXZipTool::CXZipTool() +{ + m_pZip = NULL; + m_hPreloadFile = INVALID_HANDLE_VALUE; + m_hOutputZipFile = INVALID_HANDLE_VALUE; + m_PreloadFilename[0] = '\0'; +} + +CXZipTool::~CXZipTool() +{ + Reset(); +} + +void CXZipTool::Reset() +{ + if ( m_hOutputZipFile != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hOutputZipFile ); + m_hOutputZipFile = INVALID_HANDLE_VALUE; + } + + if ( m_hPreloadFile != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hPreloadFile ); + m_hPreloadFile = INVALID_HANDLE_VALUE; + } + + if ( m_PreloadFilename[0] ) + { + DeleteFile( m_PreloadFilename ); + m_PreloadFilename[0] = '\0'; + } + + if ( m_pZip ) + { + IZip::ReleaseZip( m_pZip ); + m_pZip = NULL; + } + + m_ZipPreloadDirectoryEntries.Purge(); + m_ZipCRCList.Purge(); + m_ZipPreloadRemapEntries.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a file buffer to the zip +//----------------------------------------------------------------------------- +bool CXZipTool::AddBuffer( const char *pFilename, CUtlBuffer &fileBuffer, bool bDoPreload ) +{ + if ( !m_pZip ) + { + return false; + } + + // safely strip unecessary prefix, otherise pollutes CRC + if ( !strnicmp( pFilename, ".\\", 2 ) ) + { + pFilename += 2; + } + + // scan for CRC collision now, not at runtime + CRCEntry_t crcEntry; + crcEntry.fileNameCRC = HashStringCaselessConventional( pFilename ); + crcEntry.filename = pFilename; + int idx = m_ZipCRCList.Find( crcEntry ); + if ( -1 != idx ) + { + if ( !V_stricmp( pFilename, m_ZipCRCList[idx].filename.String() ) ) + { + // file has already been added, ignore as succesful + return true; + } + + Msg( "ERROR: CRC Collision: '%s' with '%s'\n", pFilename, m_ZipCRCList[idx].filename.String() ); + return false; + } + else + { + // add unique entry to lists + // must track filenames in a non-sort manner + m_ZipCRCList.Insert( crcEntry ); + } + + // default, no preload entry + unsigned short preloadDir = INVALID_PRELOAD_ENTRY; + + if ( bDoPreload ) + { + CUtlBuffer preloadBuffer; + bool bHasPreload = GetPreloadBuffer( pFilename, fileBuffer, preloadBuffer ); + int preloadSize = preloadBuffer.TellMaxPut(); + if ( bHasPreload && preloadSize > 0 ) + { + if ( m_ZipPreloadDirectoryEntries.Count() >= 65534 ) + { + Msg( "ERROR: Preload section FULL!, skipping %s\n", pFilename ); + return FALSE; + } + + // Initialize the entry header + ZIP_PreloadDirectoryEntry entry; + memset( &entry, 0, sizeof( entry ) ); + + entry.Length = preloadSize; + entry.DataOffset = SetFilePointer( m_hPreloadFile, 0, NULL, FILE_CURRENT ); + + // Add the directory entry to the preload table + preloadDir = m_ZipPreloadDirectoryEntries.AddToTail( entry ); + + // Append the preload data to the preload file + DWORD numBytesWritten; + BOOL bOK = WriteFile( m_hPreloadFile, preloadBuffer.Base(), preloadSize, &numBytesWritten, NULL ); + if ( !bOK || preloadSize != numBytesWritten ) + { + Msg( "ERROR: writing %d preload bytes of '%s'\n", preloadSize, pFilename ); + return false; + } + + if ( !g_bQuiet ) + { + // Spew it + if ( LZMA_IsCompressed( (unsigned char *)preloadBuffer.Base() ) ) + { + unsigned int actualSize = LZMA_GetActualSize( (unsigned char *)preloadBuffer.Base() ); + Msg( "Preload: '%s': Compressed:%u Actual:%u\n", pFilename, preloadSize, actualSize ); + } + else + { + Msg( "Preload: '%s': Length:%u\n", pFilename, preloadSize ); + } + } + } + } + + unsigned int fileSize = fileBuffer.TellMaxPut(); + if ( fileSize > 0 ) + { + // order in zip is sorted, not sequential + m_pZip->AddBufferToZip( pFilename, fileBuffer.Base(), fileSize, false ); + + // track the file in the zip directory to it's preload entry + // order in preload is sequential as buffers are added + preloadRemap_t remap; + remap.filename = pFilename; + remap.preloadDirIndex = preloadDir; + m_ZipPreloadRemapEntries.AddToTail( remap ); + + if ( !g_bQuiet ) + { + Msg( "File: '%s': Length:%u\n", pFilename, fileSize ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Load a file and add it to the zip +//----------------------------------------------------------------------------- +bool CXZipTool::AddFile( const char *pFilename, bool bDoPreload ) +{ + if ( !m_pZip ) + { + return false; + } + + FILE* pFile = fopen( pFilename, "rb" ); + if( !pFile ) + { + Msg( "ERROR: failed to open file: '%s'\n", pFilename ); + return false; + } + + // Get the length of the file + fseek( pFile, 0, SEEK_END ); + unsigned fileSize = ftell( pFile ); + fseek( pFile, 0, SEEK_SET); + + // read file to buffer + CUtlBuffer fileBuffer; + fileBuffer.EnsureCapacity( fileSize ); + fread( fileBuffer.Base(), fileSize, 1, pFile ); + fclose( pFile ); + fileBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, fileSize ); + + return AddBuffer( pFilename, fileBuffer, bDoPreload ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add the preload section and save out the zip file +//----------------------------------------------------------------------------- +bool CXZipTool::End() +{ + if ( !m_pZip ) + { + return false; + } + + // Add the preload section to the zip + if ( m_ZipPreloadDirectoryEntries.Count() ) + { + CUtlBuffer sectionBuffer; + CByteswap byteSwap; + + // pc tools write 360 native data + byteSwap.ActivateByteSwapping( IsPC() ); + + // determine the preload data footprint + unsigned int preloadDataSize = SetFilePointer( m_hPreloadFile, 0, NULL, FILE_CURRENT ); + + // finalize header + m_ZipPreloadHeader.DirectoryEntries = m_ZipPreloadRemapEntries.Count(); + m_ZipPreloadHeader.PreloadDirectoryEntries = m_ZipPreloadDirectoryEntries.Count(); + + // determine the total section size ( treated as a single file inside zip ) + unsigned int sectionSize = sizeof( ZIP_PreloadHeader ) + + m_ZipPreloadHeader.PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) + + m_ZipPreloadHeader.DirectoryEntries * sizeof( unsigned short ) + + preloadDataSize; + sectionSize = AlignValue( sectionSize, m_ZipPreloadHeader.Alignment ); + sectionBuffer.EnsureCapacity( sectionSize ); + + // save data in order + // save the header + byteSwap.SwapFieldsToTargetEndian( &m_ZipPreloadHeader ); + sectionBuffer.Put( &m_ZipPreloadHeader, sizeof( ZIP_PreloadHeader ) ); + + // fixup and save the preload directory + for ( int i=0; i<m_ZipPreloadDirectoryEntries.Count(); i++ ) + { + ZIP_PreloadDirectoryEntry entry = m_ZipPreloadDirectoryEntries[i]; + byteSwap.SwapFieldsToTargetEndian( &entry ); + sectionBuffer.Put( &entry, sizeof( ZIP_PreloadDirectoryEntry ) ); + } + + // generate remap table + char fileName[MAX_PATH]; + int fileSize; + int zipIndex = -1; + unsigned short *pRemapTable = (unsigned short *)malloc( m_ZipPreloadRemapEntries.Count() * sizeof( unsigned short ) ); + for ( int i=0; i<m_ZipPreloadRemapEntries.Count(); i++ ) + { + // zip files get iterated in the same order they are serialized to disk + fileName[0] = '\0'; + fileSize = 0; + zipIndex = m_pZip->GetNextFilename( zipIndex, fileName, sizeof( fileName ), fileSize ); + + // find the file in the preload remap table + bool bFound = false; + int j; + for ( j=0; j<m_ZipPreloadRemapEntries.Count(); j++ ) + { + if ( !Q_stricmp( fileName, m_ZipPreloadRemapEntries[j].filename.String() ) ) + { + bFound = true; + break; + } + } + if ( !bFound ) + { + // shouldn't happen, every file in the zip has a matching preload remap entry, that is valid or marked invalid + Msg( "ERROR: file '%s' was expected to have an entry in preload table\n", fileName ); + } + + // the remap table is used to go find a file's preload data (if available) + pRemapTable[i] = m_ZipPreloadRemapEntries[j].preloadDirIndex; + } + for ( int i=0; i<m_ZipPreloadRemapEntries.Count(); i++ ) + { + unsigned short s = pRemapTable[i]; + sectionBuffer.PutShort( BigShort( s ) ); + } + free( pRemapTable ); + + // get and save preload data + void *pPreloadData = malloc( preloadDataSize ); + SetFilePointer( m_hPreloadFile, 0, NULL, FILE_BEGIN ); + DWORD numBytesRead; + BOOL bOK = ReadFile( m_hPreloadFile, pPreloadData, preloadDataSize, &numBytesRead, NULL ); + if ( !bOK || numBytesRead != preloadDataSize ) + { + Msg( "ERROR: failed to read %d bytes from temporary preload file\n", preloadDataSize ); + } + CloseHandle( m_hPreloadFile ); + m_hPreloadFile = INVALID_HANDLE_VALUE; + + sectionBuffer.Put( pPreloadData, preloadDataSize ); + free( pPreloadData ); + + // cannot have written more than was pre-calced, unless code was broken + Assert( (unsigned int)sectionBuffer.TellMaxPut() <= sectionSize ); + while( (unsigned int)sectionBuffer.TellMaxPut() < sectionSize ) + { + // pad to final alignment + sectionBuffer.PutChar( 0 ); + } + + m_pZip->AddBufferToZip( PRELOAD_SECTION_NAME, sectionBuffer.Base(), sectionBuffer.TellMaxPut(), false ); + } + else + { + // Clear the preload section placeholder + m_pZip->RemoveFileFromZip( PRELOAD_SECTION_NAME ); + } + + m_pZip->SaveToDisk( m_hOutputZipFile ); + + Reset(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Create the zip file +//----------------------------------------------------------------------------- +bool CXZipTool::Begin( const char *pZipFileName, unsigned int alignment ) +{ + // get the volume of the target zip + char drivePath[MAX_PATH]; + _splitpath( pZipFileName, drivePath, NULL, NULL, NULL ); + + m_pZip = IZip::CreateZip( drivePath, true ); + + if ( alignment ) + { + // making an aligned zip that uses an optimized (but incompatible) format + m_pZip->ForceAlignment( true, false, alignment ); + } + + // Open the output file + m_hOutputZipFile = CreateFile( pZipFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( m_hOutputZipFile == INVALID_HANDLE_VALUE ) + { + Msg( "ERROR: failed to create zip file '%s'\n", pZipFileName ); + return false; + } + + // Create a temporary file for storing the preloaded data + scriptlib->MakeTemporaryFilename( g_szModPath, m_PreloadFilename, sizeof( m_PreloadFilename ) ); + m_hPreloadFile = CreateFile( m_PreloadFilename, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( m_hPreloadFile == INVALID_HANDLE_VALUE ) + { + Msg( "ERROR: failed to create temporary file '%s' for preload data\n", m_PreloadFilename ); + CloseHandle( m_hOutputZipFile ); + m_hOutputZipFile = INVALID_HANDLE_VALUE; + return false; + } + memset( &m_ZipPreloadHeader, 0, sizeof( ZIP_PreloadHeader ) ); + + m_ZipPreloadHeader.Version = PRELOAD_HDR_VERSION; + m_ZipPreloadHeader.Alignment = alignment; + + // Add a placeholder for the preload section + m_pZip->AddBufferToZip( PRELOAD_SECTION_NAME, NULL, 0, false ); + preloadRemap_t remap; + remap.filename = PRELOAD_SECTION_NAME; + remap.preloadDirIndex = INVALID_PRELOAD_ENTRY; + m_ZipPreloadRemapEntries.AddToTail( remap ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Dump the preload contents +//----------------------------------------------------------------------------- +void CXZipTool::SpewPreloadInfo( const char *pZipName ) +{ + IZip *pZip = IZip::CreateZip( NULL, true ); + + HANDLE hZipFile = pZip->ParseFromDisk( pZipName ); + if ( !hZipFile ) + { + Msg( "Bad or missing zip file, failed to open '%s'\n", pZipName ); + return; + } + + CUtlBuffer preloadBuffer; + if ( !pZip->ReadFileFromZip( hZipFile, PRELOAD_SECTION_NAME, false, preloadBuffer ) ) + { + Msg( "No preload info for '%s'\n", pZipName ); + return; + } + + preloadBuffer.ActivateByteSwapping( IsPC() ); + + ZIP_PreloadHeader header; + preloadBuffer.GetObjects( &header ); + + // get the dir table + ZIP_PreloadDirectoryEntry *pDir = (ZIP_PreloadDirectoryEntry *)malloc( header.PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) ); + preloadBuffer.GetObjects( pDir, header.PreloadDirectoryEntries ); + + // get the remap table + unsigned short *pRemap = (unsigned short *)malloc( header.DirectoryEntries * sizeof( unsigned short ) ); + for ( unsigned int i = 0; i < header.DirectoryEntries; i++ ) + { + pRemap[i] = preloadBuffer.GetShort(); + } + + int zipIndex = -1; + int fileSize; + char fileName[MAX_PATH]; + + // iterate preload entries sequentially + CUtlDict< unsigned int, int > sizes( true ); + for ( unsigned int i = 0; i < header.DirectoryEntries; i++ ) + { + fileName[0] = '\0'; + fileSize = 0; + zipIndex = pZip->GetNextFilename( zipIndex, fileName, sizeof( fileName ), fileSize ); + + unsigned short zipPreloadDirIndex = pRemap[i]; + if ( zipPreloadDirIndex == INVALID_PRELOAD_ENTRY ) + { + continue; + } + + Msg( "Offset: 0x%8.8x Length: %5d %s (%d)\n", pDir[zipPreloadDirIndex].DataOffset, pDir[zipPreloadDirIndex].Length, fileName, fileSize ); + + // total preload sizes by extension + const char *pExt = V_GetFileExtension( fileName ); + if ( !pExt ) + { + pExt = "???"; + } + int iIndex = sizes.Find( pExt ); + if ( iIndex == sizes.InvalidIndex() ) + { + iIndex = sizes.Insert( pExt ); + sizes[iIndex] = 0; + } + sizes[iIndex] += pDir[zipPreloadDirIndex].Length; + } + + Msg( "\n" ); + Msg( "Preload Size: %.2f MB\n", (float)preloadBuffer.TellMaxPut()/(1024.0f * 1024.0f) ); + Msg( "Zip Entries: %d\n", header.DirectoryEntries ); + Msg( "Preload Entries: %d\n", header.PreloadDirectoryEntries ); + + // dump each extension's total size, necessary for debugging who is the largest contributor + for ( int i = 0; i < sizes.Count(); i++ ) + { + Msg( "Extension: '%3s' %d bytes (%.2f%s)\n", sizes.GetElementName( i ), sizes[i], (float)sizes[i]/(float)preloadBuffer.TellMaxPut() * 100.0f, "%%" ); + } + Msg( "\n" ); + + free( pRemap ); + free( pDir ); + + IZip::ReleaseZip( pZip ); +} |