/*****************************************************************************/ /* SListFile.cpp Copyright (c) Ladislav Zezula 2004 */ /*---------------------------------------------------------------------------*/ /* Description: */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ /* 12.06.04 1.00 Lad The first version of SListFile.cpp */ /*****************************************************************************/ #define __STORMLIB_SELF__ #include "StormLib.h" #include "StormCommon.h" #include //----------------------------------------------------------------------------- // Listfile entry structure #define CACHE_BUFFER_SIZE 0x1000 // Size of the cache buffer #define MAX_LISTFILE_SIZE 0x8000000 // Maximum accepted listfile size is 128 MB union TListFileHandle { TFileStream * pStream; // Opened local file HANDLE hFile; // Opened MPQ file }; struct TListFileCache { char * szWildCard; // Self-relative pointer to file mask LPBYTE pBegin; // The begin of the listfile cache LPBYTE pPos; // Current position in the cache LPBYTE pEnd; // The last character in the file cache DWORD dwFlags; // Flags from TMPQArchive // char szWildCard[wildcard_length]; // Followed by the name mask (if any) // char szListFile[listfile_length]; // Followed by the listfile (if any) }; typedef bool (*LOAD_LISTFILE)(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead); //----------------------------------------------------------------------------- // Local functions (cache) // In SFileFindFile.cll bool SFileCheckWildCard(const char * szString, const char * szWildCard); static char * CopyListLine(char * szListLine, const char * szFileName) { // Copy the string while (szFileName[0] != 0) { *szListLine++ = *szFileName++; } // Append the end-of-line *szListLine++ = 0x0D; *szListLine++ = 0x0A; return szListLine; } static bool LoadListFile_Stream(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead) { ULONGLONG ByteOffset = 0; bool bResult; bResult = FileStream_Read(pHandle->pStream, &ByteOffset, pvBuffer, cbBuffer); if(bResult) *pdwBytesRead = cbBuffer; return bResult; } static bool LoadListFile_MPQ(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead) { return SFileReadFile(pHandle->hFile, pvBuffer, cbBuffer, pdwBytesRead, NULL); } static bool FreeListFileCache(TListFileCache * pCache) { // Valid parameter check if(pCache != NULL) STORM_FREE(pCache); return true; } static TListFileCache * CreateListFileCache( LOAD_LISTFILE PfnLoadFile, TListFileHandle * pHandle, const char * szWildCard, DWORD dwFileSize, DWORD dwMaxSize, DWORD dwFlags) { TListFileCache * pCache = NULL; size_t cchWildCardAligned = 0; size_t cchWildCard = 0; DWORD dwBytesRead = 0; // Get the amount of bytes that need to be allocated if(dwFileSize == 0 || dwFileSize > dwMaxSize) return NULL; // Append buffer for name mask, if any if(szWildCard != NULL) { cchWildCard = strlen(szWildCard) + 1; cchWildCardAligned = (cchWildCard + 3) & 0xFFFFFFFC; } // Allocate cache for one file block pCache = (TListFileCache *)STORM_ALLOC(BYTE, sizeof(TListFileCache) + cchWildCardAligned + dwFileSize + 1); if(pCache != NULL) { // Clear the entire structure memset(pCache, 0, sizeof(TListFileCache) + cchWildCard); pCache->dwFlags = dwFlags; // Shall we copy the mask? if(cchWildCard != 0) { pCache->szWildCard = (char *)(pCache + 1); memcpy(pCache->szWildCard, szWildCard, cchWildCard); } // Fill-in the rest of the cache pointers pCache->pBegin = (LPBYTE)(pCache + 1) + cchWildCardAligned; // Load the entire listfile to the cache PfnLoadFile(pHandle, pCache->pBegin, dwFileSize, &dwBytesRead); if(dwBytesRead != 0) { // Allocate pointers pCache->pPos = pCache->pBegin; pCache->pEnd = pCache->pBegin + dwBytesRead; } else { FreeListFileCache(pCache); pCache = NULL; } } // Return the cache return pCache; } static TListFileCache * CreateListFileCache( HANDLE hMpq, const TCHAR * szListFile, const char * szWildCard, DWORD dwMaxSize, DWORD dwFlags) { TListFileCache * pCache = NULL; TListFileHandle ListHandle = {NULL}; // Put default value to dwMaxSize if(dwMaxSize == 0) dwMaxSize = MAX_LISTFILE_SIZE; // Internal listfile: hMPQ must be non NULL and szListFile must be NULL. // We load the MPQ::(listfile) file if(hMpq != NULL && szListFile == NULL) { DWORD dwFileSize = 0; // Open the file from the MPQ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &ListHandle.hFile)) { // Get the file size and create the listfile cache dwFileSize = SFileGetFileSize(ListHandle.hFile, NULL); pCache = CreateListFileCache(LoadListFile_MPQ, &ListHandle, szWildCard, dwFileSize, dwMaxSize, dwFlags); // Close the MPQ file SFileCloseFile(ListHandle.hFile); } // Return the loaded cache return pCache; } // External listfile: hMpq must be NULL and szListFile must be non-NULL. // We load the file using TFileStream if(hMpq == NULL && szListFile != NULL) { ULONGLONG FileSize = 0; // Open the local file ListHandle.pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY); if(ListHandle.pStream != NULL) { // Verify the file size FileStream_GetSize(ListHandle.pStream, &FileSize); if(0 < FileSize && FileSize < dwMaxSize) { pCache = CreateListFileCache(LoadListFile_Stream, &ListHandle, szWildCard, (DWORD)FileSize, dwMaxSize, dwFlags); } // Close the stream FileStream_Close(ListHandle.pStream); } // Return the loaded cache return pCache; } // This combination should never happen SetLastError(ERROR_INVALID_PARAMETER); assert(false); return NULL; } #ifdef _DEBUG /* TMPQNameCache * CreateNameCache(HANDLE hListFile, const char * szSearchMask) { TMPQNameCache * pNameCache; char * szCachePointer; size_t cbToAllocate; size_t nMaskLength = 1; DWORD dwBytesRead = 0; DWORD dwFileSize; // Get the size of the listfile. Ignore zero or too long ones dwFileSize = SFileGetFileSize(hListFile, NULL); if(dwFileSize == 0 || dwFileSize > MAX_LISTFILE_SIZE) return NULL; // Get the length of the search mask if(szSearchMask == NULL) szSearchMask = "*"; nMaskLength = strlen(szSearchMask) + 1; // Allocate the name cache cbToAllocate = sizeof(TMPQNameCache) + nMaskLength + dwFileSize + 1; pNameCache = (TMPQNameCache *)STORM_ALLOC(BYTE, cbToAllocate); if(pNameCache != NULL) { // Initialize the name cache memset(pNameCache, 0, sizeof(TMPQNameCache)); pNameCache->TotalCacheSize = (DWORD)(nMaskLength + dwFileSize + 1); szCachePointer = (char *)(pNameCache + 1); // Copy the search mask, if any memcpy(szCachePointer, szSearchMask, nMaskLength); pNameCache->FirstNameOffset = (DWORD)nMaskLength; pNameCache->FreeSpaceOffset = (DWORD)nMaskLength; // Read the listfile itself SFileSetFilePointer(hListFile, 0, NULL, FILE_BEGIN); SFileReadFile(hListFile, szCachePointer + nMaskLength, dwFileSize, &dwBytesRead, NULL); // If nothing has been read from the listfile, clear the cache if(dwBytesRead == 0) { STORM_FREE(pNameCache); return NULL; } // Move the free space offset pNameCache->FreeSpaceOffset = pNameCache->FirstNameOffset + dwBytesRead + 1; szCachePointer[nMaskLength + dwBytesRead] = 0; } return pNameCache; } static void FreeNameCache(TMPQNameCache * pNameCache) { if(pNameCache != NULL) STORM_FREE(pNameCache); pNameCache = NULL; } */ #endif // _DEBUG static char * ReadListFileLine(TListFileCache * pCache, size_t * PtrLength) { LPBYTE pbLineBegin; LPBYTE pbLineEnd; // Skip newlines. Keep spaces and tabs, as they can be a legal part of the file name while(pCache->pPos < pCache->pEnd && (pCache->pPos[0] == 0x0A || pCache->pPos[0] == 0x0D)) pCache->pPos++; // Set the line begin and end if(pCache->pPos >= pCache->pEnd) return NULL; pbLineBegin = pbLineEnd = pCache->pPos; // Find the end of the line while(pCache->pPos < pCache->pEnd && pCache->pPos[0] != 0x0A && pCache->pPos[0] != 0x0D) pCache->pPos++; // Remember the end of the line pbLineEnd = pCache->pPos++; pbLineEnd[0] = 0; // Give the line to the caller if(PtrLength != NULL) PtrLength[0] = (size_t)(pbLineEnd - pbLineBegin); return (char *)pbLineBegin; } static int STORMLIB_CDECL CompareFileNodes(const void * p1, const void * p2) { char * szFileName1 = *(char **)p1; char * szFileName2 = *(char **)p2; return _stricmp(szFileName1, szFileName2); } static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; char ** SortTable = NULL; char * szListFile = NULL; char * szListLine; size_t nFileNodes = 0; size_t cbListFile = 0; size_t nIndex0; size_t nIndex1; // Allocate the table for sorting listfile SortTable = STORM_ALLOC(char*, ha->dwFileTableSize); if(SortTable == NULL) return NULL; // Construct the sort table // Note: in MPQs with multiple locale versions of the same file, // this code causes adding multiple listfile entries. // They will get removed after the listfile sorting for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // Only take existing items if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) { // Ignore pseudo-names and internal names if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName)) { for (int i = 0; i < strlen(pFileEntry->szFileName); i++) { // OTRTODO //if (pFileEntry->szFileName[i] == '/') //pFileEntry->szFileName[i] = '\\'; } SortTable[nFileNodes++] = pFileEntry->szFileName; } } } // Remove duplicities if(nFileNodes > 0) { // Sort the table qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes); // Count the 0-th item cbListFile += strlen(SortTable[0]) + 2; // Walk through the items and only use the ones that are not duplicated for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++) { // If the next file node is different, we will include it to the result listfile if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0) { cbListFile += strlen(SortTable[nIndex1]) + 2; nIndex0 = nIndex1; } } // Now allocate buffer for the entire listfile szListFile = szListLine = STORM_ALLOC(char, cbListFile + 1); if(szListFile != NULL) { // Copy the 0-th item szListLine = CopyListLine(szListLine, SortTable[0]); // Walk through the items and only use the ones that are not duplicated for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++) { // If the next file node is different, we will include it to the result listfile if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0) { // Copy the listfile line szListLine = CopyListLine(szListLine, SortTable[nIndex1]); nIndex0 = nIndex1; } } // Sanity check - does the size match? assert((size_t)(szListLine - szListFile) == cbListFile); } } else { szListFile = STORM_ALLOC(char, 1); cbListFile = 0; } // Free the sort table STORM_FREE(SortTable); // Give away the listfile if(pcbListFile != NULL) *pcbListFile = (DWORD)cbListFile; return (LPBYTE)szListFile; } //----------------------------------------------------------------------------- // Local functions (listfile nodes) // Adds a name into the list of all names. For each locale in the MPQ, // one entry will be created // If the file name is already there, does nothing. static DWORD SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFileName) { TFileEntry * pFileEntry; TMPQHash * pFirstHash; TMPQHash * pHash; // If we have HET table, use that one if(ha->pHetTable != NULL) { pFileEntry = GetFileEntryLocale(ha, szFileName, 0); if(pFileEntry != NULL) { // Allocate file name for the file entry AllocateFileName(ha, pFileEntry, szFileName); } return ERROR_SUCCESS; } // If we have hash table, we use it if(ha->pHashTable != NULL) { // Go while we found something pFirstHash = pHash = GetFirstHashEntry(ha, szFileName); while(pHash != NULL) { // Allocate file name for the file entry AllocateFileName(ha, ha->pFileTable + MPQ_BLOCK_INDEX(pHash), szFileName); // Now find the next language version of the file pHash = GetNextHashEntry(ha, pFirstHash, pHash); } return ERROR_SUCCESS; } return ERROR_CAN_NOT_COMPLETE; } // Saves the whole listfile to the MPQ DWORD SListFileSaveToMpq(TMPQArchive * ha) { TMPQFile * hf = NULL; LPBYTE pbListFile; DWORD cbListFile = 0; DWORD dwErrCode = ERROR_SUCCESS; // Only save the listfile if we should do so if(ha->dwFileFlags1 != 0) { // At this point, we expect to have at least one reserved entry in the file table assert(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW); assert(ha->dwReservedFiles > 0); // Create the raw data that is to be written to (listfile) // Note: Creating the raw data before the (listfile) has been created in the MPQ // causes that the name of the listfile will not be included in the listfile itself. // That is OK, because (listfile) in Blizzard MPQs does not contain it either. pbListFile = CreateListFile(ha, &cbListFile); if(pbListFile != NULL) { // Determine the real flags for (listfile) if(ha->dwFileFlags1 == MPQ_FILE_DEFAULT_INTERNAL) ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion); // Create the listfile in the MPQ dwErrCode = SFileAddFile_Init(ha, LISTFILE_NAME, 0, cbListFile, LANG_NEUTRAL, ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING, &hf); // Write the listfile raw data to it if(dwErrCode == ERROR_SUCCESS) { // Write the content of the listfile to the MPQ dwErrCode = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB); SFileAddFile_Finish(hf); } // Clear the listfile flags ha->dwFlags &= ~(MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_NONE); ha->dwReservedFiles--; // Free the listfile buffer STORM_FREE(pbListFile); } else { // If the (listfile) file would be empty, its OK dwErrCode = (cbListFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; } } return dwErrCode; } static DWORD SFileAddArbitraryListFile( TMPQArchive * ha, HANDLE hMpq, const TCHAR * szListFile, DWORD dwMaxSize) { TListFileCache * pCache = NULL; // Create the listfile cache for that file pCache = CreateListFileCache(hMpq, szListFile, NULL, dwMaxSize, ha->dwFlags); if(pCache != NULL) { char * szFileName; size_t nLength = 0; // Get the next line while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL) { // Add the line to the MPQ if(nLength != 0) SListFileCreateNodeForAllLocales(ha, szFileName); } // Delete the cache FreeListFileCache(pCache); } return (pCache != NULL) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; } static DWORD SFileAddInternalListFile( TMPQArchive * ha, HANDLE hMpq) { TMPQHash * pFirstHash; TMPQHash * pHash; LCID lcSaveLocale = g_lcFileLocale; DWORD dwMaxSize = MAX_LISTFILE_SIZE; DWORD dwErrCode = ERROR_SUCCESS; // If there is hash table, we need to support multiple listfiles // with different locales (BrooDat.mpq) if(ha->pHashTable != NULL) { // If the archive is a malformed map, ignore too large listfiles if(ha->dwFlags & MPQ_FLAG_MALFORMED) dwMaxSize = 0x40000; pFirstHash = pHash = GetFirstHashEntry(ha, LISTFILE_NAME); while(dwErrCode == ERROR_SUCCESS && pHash != NULL) { // Set the prefered locale to that from list file SFileSetLocale(pHash->lcLocale); // Add that listfile dwErrCode = SFileAddArbitraryListFile(ha, hMpq, NULL, dwMaxSize); // Move to the next hash pHash = GetNextHashEntry(ha, pFirstHash, pHash); } // Restore the original locale SFileSetLocale(lcSaveLocale); } else { // Add the single listfile dwErrCode = SFileAddArbitraryListFile(ha, hMpq, NULL, dwMaxSize); } // Return the result of the operation return dwErrCode; } static bool DoListFileSearch(TListFileCache * pCache, SFILE_FIND_DATA * lpFindFileData) { // Check for the valid search handle if(pCache != NULL) { char * szFileName; size_t nLength = 0; // Get the next line while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL) { // Check search mask if(nLength != 0 && SFileCheckWildCard(szFileName, pCache->szWildCard)) { if(nLength >= sizeof(lpFindFileData->cFileName)) nLength = sizeof(lpFindFileData->cFileName) - 1; memcpy(lpFindFileData->cFileName, szFileName, nLength); lpFindFileData->cFileName[nLength] = 0; return true; } } } // No more files memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA)); SetLastError(ERROR_NO_MORE_FILES); return false; } //----------------------------------------------------------------------------- // File functions // Adds a listfile into the MPQ archive. DWORD WINAPI SFileAddListFile(HANDLE hMpq, const TCHAR * szListFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; DWORD dwErrCode = ERROR_SUCCESS; // Add the listfile for each MPQ in the patch chain while(ha != NULL) { if(szListFile != NULL) dwErrCode = SFileAddArbitraryListFile(ha, NULL, szListFile, MAX_LISTFILE_SIZE); else dwErrCode = SFileAddInternalListFile(ha, hMpq); // Also, add three special files to the listfile: // (listfile) itself, (attributes) and (signature) SListFileCreateNodeForAllLocales(ha, LISTFILE_NAME); SListFileCreateNodeForAllLocales(ha, SIGNATURE_NAME); SListFileCreateNodeForAllLocales(ha, ATTRIBUTES_NAME); // Move to the next archive in the chain ha = ha->haPatch; } return dwErrCode; } //----------------------------------------------------------------------------- // Enumerating files in listfile HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const TCHAR * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData) { TListFileCache * pCache = NULL; // Initialize the structure with zeros memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA)); // Open the local/internal listfile pCache = CreateListFileCache(hMpq, szListFile, szMask, 0, 0); if(pCache != NULL) { if(!DoListFileSearch(pCache, lpFindFileData)) { memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA)); SetLastError(ERROR_NO_MORE_FILES); FreeListFileCache(pCache); pCache = NULL; } } // Return the listfile cache as handle return (HANDLE)pCache; } bool WINAPI SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) { return DoListFileSearch((TListFileCache *)hFind, lpFindFileData); } bool WINAPI SListFileFindClose(HANDLE hFind) { TListFileCache * pCache = (TListFileCache *)hFind; return FreeListFileCache(pCache); }