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>
		
			
				
	
	
		
			424 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			424 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*****************************************************************************/
 | |
| /* SFileOpenFileEx.cpp                    Copyright (c) Ladislav Zezula 2003 */
 | |
| /*---------------------------------------------------------------------------*/
 | |
| /* Description :                                                             */
 | |
| /*---------------------------------------------------------------------------*/
 | |
| /*   Date    Ver   Who  Comment                                              */
 | |
| /* --------  ----  ---  -------                                              */
 | |
| /* xx.xx.99  1.00  Lad  Created                                              */
 | |
| /*****************************************************************************/
 | |
| 
 | |
| #define __STORMLIB_SELF__
 | |
| #include "StormLib.h"
 | |
| #include "StormCommon.h"
 | |
| 
 | |
| /*****************************************************************************/
 | |
| /* Local functions                                                           */
 | |
| /*****************************************************************************/
 | |
| 
 | |
| // Finds hash index of the entry that was open by pseudo-name
 | |
| static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex)
 | |
| {
 | |
|     TMPQHash * pHashTableEnd;
 | |
|     TMPQHash * pHash;
 | |
|     DWORD dwHashIndex = HASH_ENTRY_FREE;
 | |
|     DWORD dwCount = 0;
 | |
| 
 | |
|     // Should only be called if the archive has hash table
 | |
|     assert(ha->pHashTable != NULL);
 | |
| 
 | |
|     // Multiple hash table entries can point to the file table entry.
 | |
|     // We need to search all of them
 | |
|     pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
 | |
|     for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
 | |
|     {
 | |
|         if(MPQ_BLOCK_INDEX(pHash) == dwFileIndex)
 | |
|         {
 | |
|             // Example: MPQ_2023_v1_Lusin2Rpg1.28.w3x, file index 24483
 | |
|             // ReplaceableTextures\CommandButtons\BTNHaboss79.blp
 | |
|             // Hash Table Index #1 = 18
 | |
|             // Hash Table Index #2 = 8446
 | |
|             if(dwCount++ > 0)
 | |
|                 return HASH_ENTRY_FREE;
 | |
|             dwHashIndex = (DWORD)(pHash - ha->pHashTable);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Return the found hash index, if there are no duplicities
 | |
|     return dwHashIndex;
 | |
| }
 | |
| 
 | |
| static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
 | |
| {
 | |
|     TMPQNamePrefix * pPrefix;
 | |
| 
 | |
|     // Are there patches in the current MPQ?
 | |
|     if(ha->dwFlags & MPQ_FLAG_PATCH)
 | |
|     {
 | |
|         // The patch prefix must be already known here
 | |
|         assert(ha->pPatchPrefix != NULL);
 | |
|         pPrefix = ha->pPatchPrefix;
 | |
| 
 | |
|         // The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY"
 | |
|         // We need to remove the "OldWorld\\" prefix
 | |
|         if(!_strnicmp(szFileName, "OldWorld\\", 9))
 | |
|             szFileName += 9;
 | |
| 
 | |
|         // Create the file name from the known patch entry
 | |
|         memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength);
 | |
|         strcpy(szBuffer + pPrefix->nLength, szFileName);
 | |
|         szFileName = szBuffer;
 | |
|     }
 | |
| 
 | |
|     return szFileName;
 | |
| }
 | |
| 
 | |
| static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile)
 | |
| {
 | |
|     TFileStream * pStream;
 | |
|     TMPQFile * hf = NULL;
 | |
|     TCHAR szFileNameT[MAX_PATH];
 | |
| 
 | |
|     // Convert the file name to UNICODE (if needed)
 | |
|     StringCopy(szFileNameT, _countof(szFileNameT), szFileName);
 | |
| 
 | |
|     // Open the file and create the TMPQFile structure
 | |
|     pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY);
 | |
|     if(pStream != NULL)
 | |
|     {
 | |
|         // Allocate and initialize file handle
 | |
|         hf = CreateFileHandle(NULL, NULL);
 | |
|         if(hf != NULL)
 | |
|         {
 | |
|             hf->pStream = pStream;
 | |
|             *PtrFile = hf;
 | |
|             return true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             FileStream_Close(pStream);
 | |
|             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
 | |
|         }
 | |
|     }
 | |
|     *PtrFile = NULL;
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile)
 | |
| {
 | |
|     TMPQArchive * haBase = NULL;
 | |
|     TMPQArchive * ha = (TMPQArchive *)hMpq;
 | |
|     TFileEntry * pFileEntry;
 | |
|     TMPQFile * hfPatch;                     // Pointer to patch file
 | |
|     TMPQFile * hfBase = NULL;               // Pointer to base open file
 | |
|     TMPQFile * hf = NULL;
 | |
|     HANDLE hPatchFile;
 | |
|     char szNameBuffer[MAX_PATH];
 | |
| 
 | |
|     // First of all, find the latest archive where the file is in base version
 | |
|     // (i.e. where the original, unpatched version of the file exists)
 | |
|     while(ha != NULL)
 | |
|     {
 | |
|         // If the file is there, then we remember the archive
 | |
|         pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL);
 | |
|         if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
 | |
|             haBase = ha;
 | |
| 
 | |
|         // Move to the patch archive
 | |
|         ha = ha->haPatch;
 | |
|     }
 | |
| 
 | |
|     // If we couldn't find the base file in any of the patches, it doesn't exist
 | |
|     if((ha = haBase) != NULL)
 | |
|     {
 | |
|         // Now open the base file
 | |
|         if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
 | |
|         {
 | |
|             // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
 | |
|             assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0);
 | |
|             hf = hfBase;
 | |
| 
 | |
|             // Now open all patches and attach them on top of the base file
 | |
|             for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch)
 | |
|             {
 | |
|                 // Prepare the file name with a correct prefix
 | |
|                 if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
 | |
|                 {
 | |
|                     // Remember the new version
 | |
|                     hfPatch = (TMPQFile *)hPatchFile;
 | |
| 
 | |
|                     // We should not find patch file
 | |
|                     assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0);
 | |
| 
 | |
|                     // Attach the patch to the base file
 | |
|                     hf->hfPatch = hfPatch;
 | |
|                     hf = hfPatch;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         SetLastError(ERROR_FILE_NOT_FOUND);
 | |
|     }
 | |
| 
 | |
|     // Give the updated base MPQ
 | |
|     if(PtrFile != NULL)
 | |
|         *PtrFile = (HANDLE)hfBase;
 | |
|     return (hfBase != NULL);
 | |
| }
 | |
| 
 | |
| /*****************************************************************************/
 | |
| /* Public functions                                                          */
 | |
| /*****************************************************************************/
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // SFileEnumLocales enums all locale versions within MPQ.
 | |
| // Functions fills all available language identifiers on a file into the buffer
 | |
| // pointed by PtrFileLocales. There must be enough entries to copy the localed,
 | |
| // otherwise the function returns ERROR_INSUFFICIENT_BUFFER.
 | |
| 
 | |
| DWORD WINAPI SFileEnumLocales(
 | |
|     HANDLE hMpq,
 | |
|     const char * szFileName,
 | |
|     LCID * PtrFileLocales,
 | |
|     LPDWORD PtrMaxLocales,
 | |
|     DWORD dwSearchScope)
 | |
| {
 | |
|     TMPQArchive * ha = (TMPQArchive *)hMpq;
 | |
|     TMPQHash * pFirstHash;
 | |
|     TMPQHash * pHash;
 | |
|     DWORD dwFileIndex = 0;
 | |
|     DWORD dwMaxLocales;
 | |
|     DWORD dwLocales = 0;
 | |
| 
 | |
|     // Test the parameters
 | |
|     if(!IsValidMpqHandle(hMpq))
 | |
|         return ERROR_INVALID_HANDLE;
 | |
|     if(szFileName == NULL || *szFileName == 0)
 | |
|         return ERROR_INVALID_PARAMETER;
 | |
|     if(ha->pHashTable == NULL)
 | |
|         return ERROR_NOT_SUPPORTED;
 | |
|     if(PtrMaxLocales == NULL)
 | |
|         return ERROR_INVALID_PARAMETER;
 | |
|     if(IsPseudoFileName(szFileName, &dwFileIndex))
 | |
|         return ERROR_INVALID_PARAMETER;
 | |
| 
 | |
|     // Keep compilers happy
 | |
|     dwMaxLocales = PtrMaxLocales[0];
 | |
|     STORMLIB_UNUSED(dwSearchScope);
 | |
| 
 | |
|     // Parse all files with that name
 | |
|     pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
 | |
|     while(pHash != NULL)
 | |
|     {
 | |
|         // Put the locales to the buffer
 | |
|         if(PtrFileLocales != NULL && dwLocales < dwMaxLocales)
 | |
|             *PtrFileLocales++ = SFILE_MAKE_LCID(pHash->Locale, pHash->Platform);
 | |
|         dwLocales++;
 | |
| 
 | |
|         // Get the next locale
 | |
|         pHash = GetNextHashEntry(ha, pFirstHash, pHash);
 | |
|     }
 | |
| 
 | |
|     // Give the caller the number of locales and return
 | |
|     PtrMaxLocales[0] = dwLocales;
 | |
|     return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // SFileOpenFileEx
 | |
| //
 | |
| //   hMpq          - Handle of opened MPQ archive
 | |
| //   szFileName    - Name of file to open
 | |
| //   dwSearchScope - Where to search
 | |
| //   PtrFile       - Pointer to store opened file handle
 | |
| 
 | |
| bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile)
 | |
| {
 | |
|     TMPQArchive * ha = IsValidMpqHandle(hMpq);
 | |
|     TFileEntry  * pFileEntry = NULL;
 | |
|     TMPQFile    * hf = NULL;
 | |
|     DWORD dwHashIndex = HASH_ENTRY_FREE;
 | |
|     DWORD dwFileIndex = 0;
 | |
|     DWORD dwErrCode = ERROR_SUCCESS;
 | |
|     bool bOpenByIndex = false;
 | |
| 
 | |
|     // Don't accept NULL pointer to file handle
 | |
|     if(szFileName == NULL || *szFileName == 0)
 | |
|         dwErrCode = ERROR_INVALID_PARAMETER;
 | |
| 
 | |
|     // When opening a file from MPQ, the handle must be valid
 | |
|     if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL)
 | |
|         dwErrCode = ERROR_INVALID_HANDLE;
 | |
| 
 | |
|     // When not checking for existence, the pointer to file handle must be valid
 | |
|     if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL)
 | |
|         dwErrCode = ERROR_INVALID_PARAMETER;
 | |
| 
 | |
|     // Prepare the file opening
 | |
|     if(dwErrCode == ERROR_SUCCESS)
 | |
|     {
 | |
|         switch(dwSearchScope)
 | |
|         {
 | |
|             case SFILE_OPEN_FROM_MPQ:
 | |
|             case SFILE_OPEN_BASE_FILE:
 | |
|             case SFILE_OPEN_CHECK_EXISTS:
 | |
| 
 | |
|                 // If this MPQ has no patches, open the file from this MPQ directly
 | |
|                 if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE)
 | |
|                 {
 | |
|                     pFileEntry = GetFileEntryLocale(ha, szFileName, g_lcFileLocale, &dwHashIndex);
 | |
|                 }
 | |
| 
 | |
|                 // If this MPQ is a patched archive, open the file as patched
 | |
|                 else
 | |
|                 {
 | |
|                     return OpenPatchedFile(hMpq, szFileName, PtrFile);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case SFILE_OPEN_ANY_LOCALE:
 | |
| 
 | |
|                 // This open option is reserved for opening MPQ internal listfile.
 | |
|                 // No argument validation. Tries to open file with neutral locale first,
 | |
|                 // then any other available.
 | |
|                 pFileEntry = GetFileEntryLocale(ha, szFileName, 0, &dwHashIndex);
 | |
|                 break;
 | |
| 
 | |
|             case SFILE_OPEN_LOCAL_FILE:
 | |
| 
 | |
|                 // Open a local file
 | |
|                 return OpenLocalFile(szFileName, PtrFile);
 | |
| 
 | |
|             default:
 | |
| 
 | |
|                 // Don't accept any other value
 | |
|                 dwErrCode = ERROR_INVALID_PARAMETER;
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Check whether the file really exists in the MPQ
 | |
|     if(dwErrCode == ERROR_SUCCESS)
 | |
|     {
 | |
|         // If we didn't find the file, try to open it using pseudo file name ("File
 | |
|         if(pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
 | |
|         {
 | |
|             // Check the pseudo-file name ("File00000001.ext")
 | |
|             if((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true)
 | |
|             {
 | |
|                 // Get the file entry for the file
 | |
|                 if(dwFileIndex < ha->dwFileTableSize)
 | |
|                 {
 | |
|                     pFileEntry = ha->pFileTable + dwFileIndex;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Still not found?
 | |
|             if(pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
 | |
|             {
 | |
|                 dwErrCode = ERROR_FILE_NOT_FOUND;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Perform some checks of invalid files
 | |
|         if(pFileEntry != NULL)
 | |
|         {
 | |
|             // MPQ protectors use insanely amount of fake files, often with very high size.
 | |
|             // We won't open any files whose compressed size is bigger than archive size
 | |
|             // If the file is not compressed, its size cannot be bigger than archive size
 | |
|             if((pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) == 0 && (pFileEntry->dwFileSize > ha->FileSize))
 | |
|             {
 | |
|                 dwErrCode = ERROR_FILE_CORRUPT;
 | |
|                 pFileEntry = NULL;
 | |
|             }
 | |
| 
 | |
|             // Ignore unknown loading flags (example: MPQ_2016_v1_WME4_4.w3x)
 | |
| //          if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS)
 | |
| //          {
 | |
| //              dwErrCode = ERROR_NOT_SUPPORTED;
 | |
| //              pFileEntry = NULL;
 | |
| //          }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Did the caller just wanted to know if the file exists?
 | |
|     if(dwErrCode == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS)
 | |
|     {
 | |
|         // Allocate file handle
 | |
|         hf = CreateFileHandle(ha, pFileEntry);
 | |
|         if(hf != NULL)
 | |
|         {
 | |
|             // Get the hash index for the file
 | |
|             if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE)
 | |
|                 dwHashIndex = FindHashIndex(ha, dwFileIndex);
 | |
|             if(dwHashIndex != HASH_ENTRY_FREE)
 | |
|                 hf->pHashEntry = ha->pHashTable + dwHashIndex;
 | |
|             hf->dwHashIndex = dwHashIndex;
 | |
| 
 | |
|             // If the MPQ has sector CRC enabled, enable if for the file
 | |
|             if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC)
 | |
|                 hf->bCheckSectorCRCs = true;
 | |
| 
 | |
|             // If we know the real file name, copy it to the file entry
 | |
|             if(bOpenByIndex == false)
 | |
|             {
 | |
|                 // If there is no file name yet, allocate it
 | |
|                 AllocateFileName(ha, pFileEntry, szFileName);
 | |
| 
 | |
|                 // If the file is encrypted, we should detect the file key
 | |
|                 if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
 | |
|                 {
 | |
|                     hf->dwFileKey = DecryptFileKey(szFileName,
 | |
|                                                    pFileEntry->ByteOffset,
 | |
|                                                    pFileEntry->dwFileSize,
 | |
|                                                    pFileEntry->dwFlags);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Give the file entry
 | |
|     if(PtrFile != NULL)
 | |
|         PtrFile[0] = hf;
 | |
| 
 | |
|     // Return error code
 | |
|     if(dwErrCode != ERROR_SUCCESS)
 | |
|         SetLastError(dwErrCode);
 | |
|     return (dwErrCode == ERROR_SUCCESS);
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // SFileHasFile
 | |
| //
 | |
| //   hMpq          - Handle of opened MPQ archive
 | |
| //   szFileName    - Name of file to look for
 | |
| 
 | |
| bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
 | |
| {
 | |
|     return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL);
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // bool WINAPI SFileCloseFile(HANDLE hFile);
 | |
| 
 | |
| bool WINAPI SFileCloseFile(HANDLE hFile)
 | |
| {
 | |
|     TMPQFile * hf = (TMPQFile *)hFile;
 | |
| 
 | |
|     if(!IsValidFileHandle(hFile))
 | |
|     {
 | |
|         SetLastError(ERROR_INVALID_HANDLE);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     // Free the structure
 | |
|     FreeFileHandle(hf);
 | |
|     return true;
 | |
| }
 |