mirror of
				https://github.com/thunderbrewhq/thunderbrew
				synced 2025-10-31 00:06:05 +03:00 
			
		
		
		
	 f86f6d6d09
			
		
	
	
		f86f6d6d09
		
			
		
	
	
	
	
		
			
			* feat(app): add StormLib * feat(app): add OpenArchives * feat(util): update SFile to work with StormLib * feat(app): update SFile * feat(util): update SFile with logging (Windows only) * feat(ui): implemented termination w/o notice * chore(build): update StormLib * chore(util): replace std::string with SStr* functions * fix(stormlib): dwFlags argument for SFileOpenPatchArchive * chore(ui): add Script_* stubs * chore(util): clean up SFile::OpenEx * chore(build): update StormLib --------- Co-authored-by: Phaneron <superp00t@tutanota.com>
		
			
				
	
	
		
			485 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			485 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*****************************************************************************/
 | |
| /* SFileFindFile.cpp                      Copyright (c) Ladislav Zezula 2003 */
 | |
| /*---------------------------------------------------------------------------*/
 | |
| /* A module for file searching within MPQs                                   */
 | |
| /*---------------------------------------------------------------------------*/
 | |
| /*   Date    Ver   Who  Comment                                              */
 | |
| /* --------  ----  ---  -------                                              */
 | |
| /* 25.03.03  1.00  Lad  The first version of SFileFindFile.cpp               */
 | |
| /*****************************************************************************/
 | |
| 
 | |
| #define __STORMLIB_SELF__
 | |
| #include "StormLib.h"
 | |
| #include "StormCommon.h"
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // Private structure used for file search (search handle)
 | |
| 
 | |
| // Used by searching in MPQ archives
 | |
| struct TMPQSearch
 | |
| {
 | |
|     TMPQArchive * ha;                   // Handle to MPQ, where the search runs
 | |
|     TFileEntry ** pSearchTable;         // Table for files that have been already found
 | |
|     DWORD  dwSearchTableItems;          // Number of items in the search table
 | |
|     DWORD  dwNextIndex;                 // Next file index to be checked
 | |
|     DWORD  dwFlagMask;                  // For checking flag mask
 | |
|     char   szSearchMask[1];             // Search mask (variable length)
 | |
| };
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // Local functions
 | |
| 
 | |
| static TMPQSearch * IsValidSearchHandle(HANDLE hFind)
 | |
| {
 | |
|     TMPQSearch * hs = (TMPQSearch *)hFind;
 | |
| 
 | |
|     if(hs != NULL && IsValidMpqHandle(hs->ha))
 | |
|         return hs;
 | |
| 
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| bool SFileCheckWildCard(const char * szString, const char * szWildCard)
 | |
| {
 | |
|     const char * szWildCardPtr;
 | |
| 
 | |
|     for(;;)
 | |
|     {
 | |
|         // If there is '?' in the wildcard, we skip one char
 | |
|         while(szWildCard[0] == '?')
 | |
|         {
 | |
|             if(szString[0] == 0)
 | |
|                 return false;
 | |
| 
 | |
|             szWildCard++;
 | |
|             szString++;
 | |
|         }
 | |
| 
 | |
|         // Handle '*'
 | |
|         szWildCardPtr = szWildCard;
 | |
|         if(szWildCardPtr[0] != 0)
 | |
|         {
 | |
|             if(szWildCardPtr[0] == '*')
 | |
|             {
 | |
|                 while(szWildCardPtr[0] == '*')
 | |
|                     szWildCardPtr++;
 | |
| 
 | |
|                 if(szWildCardPtr[0] == 0)
 | |
|                     return true;
 | |
| 
 | |
|                 if(AsciiToUpperTable[szWildCardPtr[0]] == AsciiToUpperTable[szString[0]])
 | |
|                 {
 | |
|                     if(SFileCheckWildCard(szString, szWildCardPtr))
 | |
|                         return true;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if(AsciiToUpperTable[szWildCardPtr[0]] != AsciiToUpperTable[szString[0]])
 | |
|                     return false;
 | |
| 
 | |
|                 szWildCard = szWildCardPtr + 1;
 | |
|             }
 | |
| 
 | |
|             if(szString[0] == 0)
 | |
|                 return false;
 | |
|             szString++;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return (szString[0] == 0) ? true : false;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static DWORD GetSearchTableItems(TMPQArchive * ha)
 | |
| {
 | |
|     DWORD dwMergeItems = 0;
 | |
| 
 | |
|     // Loop over all patches
 | |
|     while(ha != NULL)
 | |
|     {
 | |
|         // Append the number of files
 | |
|         dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount
 | |
|                                                 : ha->pHeader->dwBlockTableSize;
 | |
|         // Move to the patched archive
 | |
|         ha = ha->haPatch;
 | |
|     }
 | |
| 
 | |
|     // Return the double size of number of items
 | |
|     return (dwMergeItems | 1);
 | |
| }
 | |
| 
 | |
| static bool FileWasFoundBefore(
 | |
|     TMPQArchive * ha,
 | |
|     TMPQSearch * hs,
 | |
|     TFileEntry * pFileEntry)
 | |
| {
 | |
|     TFileEntry * pEntry;
 | |
|     char * szRealFileName = pFileEntry->szFileName;
 | |
|     DWORD dwStartIndex;
 | |
|     DWORD dwNameHash;
 | |
|     DWORD dwIndex;
 | |
| 
 | |
|     if(hs->pSearchTable != NULL && szRealFileName != NULL)
 | |
|     {
 | |
|         // If we are in patch MPQ, we check if patch prefix matches
 | |
|         // and then trim the patch prefix
 | |
|         if(ha->pPatchPrefix != NULL)
 | |
|         {
 | |
|             // If the patch prefix doesn't fit, we pretend that the file
 | |
|             // was there before and it will be skipped
 | |
|             if(_strnicmp(szRealFileName, ha->pPatchPrefix->szPatchPrefix, ha->pPatchPrefix->nLength))
 | |
|                 return true;
 | |
| 
 | |
|             szRealFileName += ha->pPatchPrefix->nLength;
 | |
|         }
 | |
| 
 | |
|         // Calculate the hash to the table
 | |
|         dwNameHash = ha->pfnHashString(szRealFileName, MPQ_HASH_NAME_A);
 | |
|         dwStartIndex = dwIndex = (dwNameHash % hs->dwSearchTableItems);
 | |
| 
 | |
|         // The file might have been found before
 | |
|         // only if this is not the first MPQ being searched
 | |
|         if(ha->haBase != NULL)
 | |
|         {
 | |
|             // Enumerate all entries in the search table
 | |
|             for(;;)
 | |
|             {
 | |
|                 // Get the file entry at that position
 | |
|                 pEntry = hs->pSearchTable[dwIndex];
 | |
|                 if(pEntry == NULL)
 | |
|                     break;
 | |
| 
 | |
|                 if(pEntry->szFileName != NULL)
 | |
|                 {
 | |
|                     // Does the name match?
 | |
|                     if(!_stricmp(pEntry->szFileName, szRealFileName))
 | |
|                         return true;
 | |
|                 }
 | |
| 
 | |
|                 // Move to the next entry
 | |
|                 dwIndex = (dwIndex + 1) % hs->dwSearchTableItems;
 | |
|                 if(dwIndex == dwStartIndex)
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Put the entry to the table for later use
 | |
|         hs->pSearchTable[dwIndex] = pFileEntry;
 | |
|     }
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
 | |
| {
 | |
|     TFileEntry * pPatchEntry = pFileEntry;
 | |
|     TFileEntry * pTempEntry;
 | |
|     char szFileName[MAX_PATH+1];
 | |
| 
 | |
|     // Can't find patch entry for a file that doesn't have name
 | |
|     if(pFileEntry->szFileName != NULL && pFileEntry->szFileName[0] != 0)
 | |
|     {
 | |
|         // Go while there are patches
 | |
|         while(ha->haPatch != NULL)
 | |
|         {
 | |
|             // Move to the patch archive
 | |
|             ha = ha->haPatch;
 | |
|             szFileName[0] = 0;
 | |
| 
 | |
|             // Prepare the prefix for the file name
 | |
|             if(ha->pPatchPrefix && ha->pPatchPrefix->nLength)
 | |
|                 StringCopy(szFileName, _countof(szFileName), ha->pPatchPrefix->szPatchPrefix);
 | |
|             StringCat(szFileName, _countof(szFileName), pFileEntry->szFileName);
 | |
| 
 | |
|             // Try to find the file there
 | |
|             pTempEntry = GetFileEntryExact(ha, szFileName, 0, NULL);
 | |
|             if(pTempEntry != NULL)
 | |
|                 pPatchEntry = pTempEntry;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Return the found patch entry
 | |
|     return pPatchEntry;
 | |
| }
 | |
| 
 | |
| static bool DoMPQSearch_FileEntry(
 | |
|     TMPQSearch * hs,
 | |
|     SFILE_FIND_DATA * lpFindFileData,
 | |
|     TMPQArchive * ha,
 | |
|     TMPQHash * pHashEntry,
 | |
|     TFileEntry * pFileEntry)
 | |
| {
 | |
|     TFileEntry * pPatchEntry;
 | |
|     HANDLE hFile = NULL;
 | |
|     const char * szFileName;
 | |
|     size_t nGlobalPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0;
 | |
|     DWORD dwBlockIndex;
 | |
|     char szNameBuff[MAX_PATH];
 | |
| 
 | |
|     // Is it a file but not a patch file?
 | |
|     if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS)
 | |
|     {
 | |
|         // Ignore fake files which are not compressed but have size higher than the archive
 | |
|         if((pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) == 0 && (pFileEntry->dwFileSize > ha->FileSize))
 | |
|             return false;
 | |
| 
 | |
|         // Now we have to check if this file was not enumerated before
 | |
|         if(!FileWasFoundBefore(ha, hs, pFileEntry))
 | |
|         {
 | |
|             size_t nPrefixLength = nGlobalPrefixLength;
 | |
| 
 | |
| //          if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy"))
 | |
| //              DebugBreak();
 | |
| 
 | |
|             // Find a patch to this file
 | |
|             // Note: This either succeeds or returns pFileEntry
 | |
|             pPatchEntry = FindPatchEntry(ha, pFileEntry);
 | |
| 
 | |
|             // Prepare the block index
 | |
|             dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
 | |
| 
 | |
|             // Get the file name. If it's not known, we will create pseudo-name
 | |
|             szFileName = pFileEntry->szFileName;
 | |
|             if(szFileName == NULL)
 | |
|             {
 | |
|                 // Open the file by its pseudo-name.
 | |
|                 StringCreatePseudoFileName(szNameBuff, _countof(szNameBuff), dwBlockIndex, "xxx");
 | |
|                 if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile))
 | |
|                 {
 | |
|                     SFileGetFileName(hFile, szNameBuff);
 | |
|                     SFileCloseFile(hFile);
 | |
|                     szFileName = szNameBuff;
 | |
|                     nPrefixLength = 0;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If the file name is still NULL, we cannot include the file to search results
 | |
|             if(szFileName != NULL)
 | |
|             {
 | |
|                 // Check the file name against the wildcard
 | |
|                 if(SFileCheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
 | |
|                 {
 | |
|                     // Fill the found entry. hash entry and block index are taken from the base MPQ
 | |
|                     lpFindFileData->dwHashIndex  = HASH_ENTRY_FREE;
 | |
|                     lpFindFileData->dwBlockIndex = dwBlockIndex;
 | |
|                     lpFindFileData->dwFileSize   = pPatchEntry->dwFileSize;
 | |
|                     lpFindFileData->dwFileFlags  = pPatchEntry->dwFlags;
 | |
|                     lpFindFileData->dwCompSize   = pPatchEntry->dwCmpSize;
 | |
|                     lpFindFileData->lcLocale     = 0;   // pPatchEntry->lcFileLocale;
 | |
| 
 | |
|                     // Fill the filetime
 | |
|                     lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
 | |
|                     lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
 | |
| 
 | |
|                     // Fill-in the entries from hash table entry, if given
 | |
|                     if(pHashEntry != NULL)
 | |
|                     {
 | |
|                         lpFindFileData->dwHashIndex = (DWORD)(pHashEntry - ha->pHashTable);
 | |
|                         lpFindFileData->lcLocale = SFILE_MAKE_LCID(pHashEntry->Locale, pHashEntry->Platform);
 | |
|                     }
 | |
| 
 | |
|                     // Fill the file name and plain file name
 | |
|                     StringCopy(lpFindFileData->cFileName, _countof(lpFindFileData->cFileName), szFileName + nPrefixLength);
 | |
|                     lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName);
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Either not a valid item or was found before
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| static DWORD DoMPQSearch_HashTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha)
 | |
| {
 | |
|     TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
 | |
|     TMPQHash * pHash;
 | |
| 
 | |
|     // Parse the file table
 | |
|     for(pHash = ha->pHashTable + hs->dwNextIndex; pHash < pHashTableEnd; pHash++)
 | |
|     {
 | |
|         // Increment the next index for subsequent search
 | |
|         hs->dwNextIndex++;
 | |
| 
 | |
|         // Does this hash table entry point to a proper block table entry?
 | |
|         if(IsValidHashEntry(ha, pHash))
 | |
|         {
 | |
|             // Check if this file entry should be included in the search result
 | |
|             if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + MPQ_BLOCK_INDEX(pHash)))
 | |
|                 return ERROR_SUCCESS;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // No more files
 | |
|     return ERROR_NO_MORE_FILES;
 | |
| }
 | |
| 
 | |
| static DWORD DoMPQSearch_FileTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha)
 | |
| {
 | |
|     TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
 | |
|     TFileEntry * pFileEntry;
 | |
| 
 | |
|     // Parse the file table
 | |
|     for(pFileEntry = ha->pFileTable + hs->dwNextIndex; pFileEntry < pFileTableEnd; pFileEntry++)
 | |
|     {
 | |
|         // Increment the next index for subsequent search
 | |
|         hs->dwNextIndex++;
 | |
| 
 | |
|         // Check if this file entry should be included in the search result
 | |
|         if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, NULL, pFileEntry))
 | |
|             return ERROR_SUCCESS;
 | |
|     }
 | |
| 
 | |
|     // No more files
 | |
|     return ERROR_NO_MORE_FILES;
 | |
| }
 | |
| 
 | |
| // Performs one MPQ search
 | |
| static DWORD DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
 | |
| {
 | |
|     TMPQArchive * ha = hs->ha;
 | |
|     DWORD dwErrCode;
 | |
| 
 | |
|     // Start searching with base MPQ
 | |
|     while(ha != NULL)
 | |
|     {
 | |
|         // If the archive has hash table, we need to use hash table
 | |
|         // in order to catch hash table index and file locale.
 | |
|         // Note: If multiple hash table entries, point to the same block entry,
 | |
|         // we need, to report them all
 | |
|         dwErrCode = (ha->pHashTable != NULL) ? DoMPQSearch_HashTable(hs, lpFindFileData, ha)
 | |
|                                              : DoMPQSearch_FileTable(hs, lpFindFileData, ha);
 | |
|         if(dwErrCode == ERROR_SUCCESS)
 | |
|             return dwErrCode;
 | |
| 
 | |
|         // If there is no more patches in the chain, stop it.
 | |
|         // This also keeps hs->ha non-NULL, which is required
 | |
|         // for freeing the handle later
 | |
|         if(ha->haPatch == NULL)
 | |
|             break;
 | |
| 
 | |
|         // Move to the next patch in the patch chain
 | |
|         hs->ha = ha = ha->haPatch;
 | |
|         hs->dwNextIndex = 0;
 | |
|     }
 | |
| 
 | |
|     // No more files found, return error
 | |
|     return ERROR_NO_MORE_FILES;
 | |
| }
 | |
| 
 | |
| static void FreeMPQSearch(TMPQSearch *& hs)
 | |
| {
 | |
|     if(hs != NULL)
 | |
|     {
 | |
|         if(hs->pSearchTable != NULL)
 | |
|             STORM_FREE(hs->pSearchTable);
 | |
|         STORM_FREE(hs);
 | |
|         hs = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // Public functions
 | |
| 
 | |
| HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DATA * lpFindFileData, const TCHAR * szListFile)
 | |
| {
 | |
|     TMPQArchive * ha = (TMPQArchive *)hMpq;
 | |
|     TMPQSearch * hs = NULL;
 | |
|     size_t nSize  = 0;
 | |
|     DWORD dwErrCode = ERROR_SUCCESS;
 | |
| 
 | |
|     // Check for the valid parameters
 | |
|     if(!IsValidMpqHandle(hMpq))
 | |
|         dwErrCode = ERROR_INVALID_HANDLE;
 | |
|     if(szMask == NULL || lpFindFileData == NULL)
 | |
|         dwErrCode = ERROR_INVALID_PARAMETER;
 | |
| 
 | |
|     // Include the listfile into the MPQ's internal listfile
 | |
|     // Note that if the listfile name is NULL, do nothing because the
 | |
|     // internal listfile is always included.
 | |
|     if(dwErrCode == ERROR_SUCCESS && szListFile != NULL && *szListFile != 0)
 | |
|         dwErrCode = SFileAddListFile((HANDLE)ha, szListFile);
 | |
| 
 | |
|     // Allocate the structure for MPQ search
 | |
|     if(dwErrCode == ERROR_SUCCESS)
 | |
|     {
 | |
|         nSize = sizeof(TMPQSearch) + strlen(szMask) + 1;
 | |
|         if((hs = (TMPQSearch *)STORM_ALLOC(char, nSize)) == NULL)
 | |
|             dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
 | |
|     }
 | |
| 
 | |
|     // Perform the first search
 | |
|     if(dwErrCode == ERROR_SUCCESS)
 | |
|     {
 | |
|         memset(hs, 0, sizeof(TMPQSearch));
 | |
|         strcpy(hs->szSearchMask, szMask);
 | |
|         hs->dwFlagMask = MPQ_FILE_EXISTS;
 | |
|         hs->ha = ha;
 | |
| 
 | |
|         // If the archive is patched archive, we have to create a merge table
 | |
|         // to prevent files being repeated
 | |
|         if(ha->haPatch != NULL)
 | |
|         {
 | |
|             hs->dwSearchTableItems = GetSearchTableItems(ha);
 | |
|             hs->pSearchTable = STORM_ALLOC(TFileEntry *, hs->dwSearchTableItems);
 | |
|             hs->dwFlagMask = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE;
 | |
|             if(hs->pSearchTable != NULL)
 | |
|                 memset(hs->pSearchTable, 0, hs->dwSearchTableItems * sizeof(TFileEntry *));
 | |
|             else
 | |
|                 dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Perform first item searching
 | |
|     if(dwErrCode == ERROR_SUCCESS)
 | |
|     {
 | |
|         dwErrCode = DoMPQSearch(hs, lpFindFileData);
 | |
|     }
 | |
| 
 | |
|     // Cleanup
 | |
|     if(dwErrCode != ERROR_SUCCESS)
 | |
|     {
 | |
|         FreeMPQSearch(hs);
 | |
|         SetLastError(dwErrCode);
 | |
|     }
 | |
| 
 | |
|     // Return the result value
 | |
|     return (HANDLE)hs;
 | |
| }
 | |
| 
 | |
| bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
 | |
| {
 | |
|     TMPQSearch * hs = IsValidSearchHandle(hFind);
 | |
|     DWORD dwErrCode = ERROR_SUCCESS;
 | |
| 
 | |
|     // Check the parameters
 | |
|     if(hs == NULL)
 | |
|         dwErrCode = ERROR_INVALID_HANDLE;
 | |
|     if(lpFindFileData == NULL)
 | |
|         dwErrCode = ERROR_INVALID_PARAMETER;
 | |
| 
 | |
|     if(dwErrCode == ERROR_SUCCESS)
 | |
|         dwErrCode = DoMPQSearch(hs, lpFindFileData);
 | |
| 
 | |
|     if(dwErrCode != ERROR_SUCCESS)
 | |
|         SetLastError(dwErrCode);
 | |
|     return (dwErrCode == ERROR_SUCCESS);
 | |
| }
 | |
| 
 | |
| bool WINAPI SFileFindClose(HANDLE hFind)
 | |
| {
 | |
|     TMPQSearch * hs = IsValidSearchHandle(hFind);
 | |
| 
 | |
|     // Check the parameters
 | |
|     if(hs == NULL)
 | |
|     {
 | |
|         SetLastError(ERROR_INVALID_HANDLE);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     FreeMPQSearch(hs);
 | |
|     return true;
 | |
| }
 |