diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:59:00 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:59:00 +0100 |
commit | c34c37b3418f1e5ab3678ce65d46f81803dec91d (patch) | |
tree | 4a9cf4c61634691981f9dc367b53dac4070f8d0d /src | |
parent | ff0c25952a28a927c48738ab5207b9bda69e588a (diff) |
+ StormLib 9.0 BETA
Diffstat (limited to 'src')
-rw-r--r-- | src/FileStream.cpp | 208 | ||||
-rw-r--r-- | src/SBaseCommon.cpp | 357 | ||||
-rw-r--r-- | src/SBaseFileTable.cpp | 1710 | ||||
-rw-r--r-- | src/SBaseSubTypes.cpp | 63 | ||||
-rw-r--r-- | src/SCompression.cpp | 12 | ||||
-rw-r--r-- | src/SFileAddFile.cpp | 194 | ||||
-rw-r--r-- | src/SFileAttributes.cpp | 559 | ||||
-rw-r--r-- | src/SFileCompactArchive.cpp | 159 | ||||
-rw-r--r-- | src/SFileCreateArchive.cpp | 48 | ||||
-rw-r--r-- | src/SFileFindFile.cpp | 62 | ||||
-rw-r--r-- | src/SFileGetFileInfo.cpp | 1698 | ||||
-rw-r--r-- | src/SFileListFile.cpp | 236 | ||||
-rw-r--r-- | src/SFileOpenArchive.cpp | 85 | ||||
-rw-r--r-- | src/SFileOpenFileEx.cpp | 96 | ||||
-rw-r--r-- | src/SFilePatchArchives.cpp | 10 | ||||
-rw-r--r-- | src/SFileReadFile.cpp | 476 | ||||
-rw-r--r-- | src/SFileVerify.cpp | 162 | ||||
-rw-r--r-- | src/StormCommon.h | 97 | ||||
-rw-r--r-- | src/StormLib.h | 545 | ||||
-rw-r--r-- | src/StormPort.h | 14 |
20 files changed, 3031 insertions, 3760 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp index 373562a..81933fd 100644 --- a/src/FileStream.cpp +++ b/src/FileStream.cpp @@ -2006,7 +2006,7 @@ bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream) * * \a pStream Pointer to an open stream */ -TCHAR * FileStream_GetFileName(TFileStream * pStream) +const TCHAR * FileStream_GetFileName(TFileStream * pStream) { assert(pStream != NULL); return pStream->szFileName; @@ -2095,6 +2095,61 @@ void FileStream_Close(TFileStream * pStream) } //----------------------------------------------------------------------------- +// Utility functions (ANSI) + +const char * GetPlainFileName(const char * szFileName) +{ + const char * szPlainName = szFileName; + + while(*szFileName != 0) + { + if(*szFileName == '\\' || *szFileName == '/') + szPlainName = szFileName + 1; + szFileName++; + } + + return szPlainName; +} + +void CopyFileName(char * szTarget, const char * szSource, size_t cchLength) +{ + memcpy(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} + +//----------------------------------------------------------------------------- +// Utility functions (UNICODE) only exist in the ANSI version of the library +// In ANSI builds, TCHAR = char, so we don't need these functions implemented + +#ifdef _UNICODE +const TCHAR * GetPlainFileName(const TCHAR * szFileName) +{ + const TCHAR * szPlainName = szFileName; + + while(*szFileName != 0) + { + if(*szFileName == '\\' || *szFileName == '/') + szPlainName = szFileName + 1; + szFileName++; + } + + return szPlainName; +} + +void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength) +{ + mbstowcs(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} + +void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength) +{ + wcstombs(szTarget, szSource, cchLength); + szTarget[cchLength] = 0; +} +#endif + +//----------------------------------------------------------------------------- // main - for testing purposes #ifdef __STORMLIB_TEST__ @@ -2141,154 +2196,3 @@ int FileStream_Test(const TCHAR * szFileName, DWORD dwStreamFlags) return ERROR_SUCCESS; } #endif - -/* -int FileStream_Test() -{ - TFileStream * pStream; - - InitializeMpqCryptography(); - - // - // Test 1: Write to a stream - // - - pStream = FileStream_CreateFile("E:\\Stream.bin", 0); - if(pStream != NULL) - { - char szString1[100] = "This is a single line\n\r"; - DWORD dwLength = strlen(szString1); - - for(int i = 0; i < 10; i++) - { - if(!FileStream_Write(pStream, NULL, szString1, dwLength)) - { - printf("Failed to write to the stream\n"); - return ERROR_CAN_NOT_COMPLETE; - } - } - FileStream_Close(pStream); - } - - // - // Test2: Read from the stream - // - - pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - char szString1[100] = "This is a single line\n\r"; - char szString2[100]; - DWORD dwLength = strlen(szString1); - - // This call must end with an error - if(FileStream_Write(pStream, NULL, "aaa", 3)) - { - printf("Write succeeded while it should fail\n"); - return -1; - } - - for(int i = 0; i < 10; i++) - { - if(!FileStream_Read(pStream, NULL, szString2, dwLength)) - { - printf("Failed to read from the stream\n"); - return -1; - } - - szString2[dwLength] = 0; - if(strcmp(szString1, szString2)) - { - printf("Data read from file are different from data written\n"); - return -1; - } - } - FileStream_Close(pStream); - } - - // - // Test3: Open the temp stream, write some data and switch it to the original stream - // - - pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - TFileStream * pTempStream; - ULONGLONG FileSize; - - pTempStream = FileStream_CreateFile("E:\\TempStream.bin", 0); - if(pTempStream == NULL) - { - printf("Failed to create temp stream\n"); - return -1; - } - - // Copy the original stream to the temp - if(!FileStream_GetSize(pStream, FileSize)) - { - printf("Failed to get the file size\n"); - return -1; - } - - while(FileSize != 0) - { - DWORD dwBytesToRead = (DWORD)FileSize; - char Buffer[0x80]; - - if(dwBytesToRead > sizeof(Buffer)) - dwBytesToRead = sizeof(Buffer); - - if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead)) - { - printf("CopyStream: Read source file failed\n"); - return -1; - } - - if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead)) - { - printf("CopyStream: Write target file failed\n"); - return -1; - } - - FileSize -= dwBytesToRead; - } - - // Switch the streams - // Note that the pTempStream is closed by the operation - FileStream_Switch(pStream, pTempStream); - FileStream_Close(pStream); - } - - // - // Test4: Read from the stream again - // - - pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - char szString1[100] = "This is a single line\n\r"; - char szString2[100]; - DWORD dwLength = strlen(szString1); - - for(int i = 0; i < 10; i++) - { - if(!FileStream_Read(pStream, NULL, szString2, dwLength)) - { - printf("Failed to read from the stream\n"); - return -1; - } - - szString2[dwLength] = 0; - if(strcmp(szString1, szString2)) - { - printf("Data read from file are different from data written\n"); - return -1; - } - } - FileStream_Close(pStream); - } - - return 0; -} -*/ - diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index 2e9366f..3bb10de 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -15,7 +15,7 @@ #include "StormLib.h" #include "StormCommon.h" -char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2012"; +char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014"; //----------------------------------------------------------------------------- // Local variables @@ -97,6 +97,8 @@ unsigned char AsciiToUpperTable_Slash[256] = #define STORM_BUFFER_SIZE 0x500 +#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0) + static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine static bool bMpqCryptographyInitialized = false; @@ -204,19 +206,17 @@ DWORD HashStringLower(const char * szFileName, DWORD dwHashType) DWORD GetHashTableSizeForFileCount(DWORD dwFileCount) { - DWORD dwPowerOfTwo; - - // Round the hash table size up to the nearest power of two - for(dwPowerOfTwo = HASH_TABLE_SIZE_MIN; dwPowerOfTwo < HASH_TABLE_SIZE_MAX; dwPowerOfTwo <<= 1) - { - if(dwPowerOfTwo >= dwFileCount) - { - return dwPowerOfTwo; - } - } + DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN; + // For zero files, there is no hash table needed + if(dwFileCount == 0) + return 0; + + // Round the hash table size up to the nearest power of two // Don't allow the hash table size go over allowed maximum - return HASH_TABLE_SIZE_MAX; + while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount) + dwPowerOfTwo <<= 1; + return dwPowerOfTwo; } //----------------------------------------------------------------------------- @@ -252,224 +252,12 @@ ULONGLONG HashStringJenkins(const char * szFileName) } //----------------------------------------------------------------------------- -// Copies the string from char * to TCHAR * and back - -#ifdef _UNICODE - -// For UNICODE builds, we need two functions -void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength) -{ - mbstowcs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength) -{ - wcstombs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -#else - -// For ANSI build, we only need one -void CopyFileName(char * szTarget, const char * szSource, size_t cchLength) -{ - memcpy(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -#endif - -//----------------------------------------------------------------------------- -// This function converts the MPQ header so it always looks like version 4 - -int ConvertMpqHeaderToFormat4( - TMPQArchive * ha, - ULONGLONG FileSize, - DWORD dwFlags) -{ - TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; - ULONGLONG ByteOffset; - DWORD dwExpectedArchiveSize; - USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion); - int nError = ERROR_SUCCESS; - - // If version 1.0 is forced, then the format version is forced to be 1.0 - // Reason: Storm.dll in Warcraft III ignores format version value - if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) - wFormatVersion = MPQ_FORMAT_VERSION_1; - - // Format-specific fixes - switch(wFormatVersion) - { - case MPQ_FORMAT_VERSION_1: - - // Make sure that the V1 header part is BSWAPPed - BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1); - - // Check for malformed MPQ header version 1.0 - if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) - { - pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; - ha->dwFlags |= MPQ_FLAG_PROTECTED; - } - - // - // The value of "dwArchiveSize" member in the MPQ header - // is ignored by Storm.dll and can contain garbage value - // ("w3xmaster" protector). - // - - dwExpectedArchiveSize = (DWORD)(FileSize - ha->MpqPos); - if(pHeader->dwArchiveSize != dwExpectedArchiveSize) - { - // Note: dwExpectedArchiveSize might be incorrect at this point. - // MPQs version 1.0 can have strong digital signature appended at the end, - // or they might just have arbitrary data there. - // In either case, we recalculate the archive size later when - // block table is loaded and positions of all files is known. - pHeader->dwArchiveSize = dwExpectedArchiveSize; - ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE; - } - - // Zero the fields in 2.0 part of the MPQ header - pHeader->HiBlockTablePos64 = 0; - pHeader->wHashTablePosHi = 0; - pHeader->wBlockTablePosHi = 0; - // No break here !!! - - case MPQ_FORMAT_VERSION_2: - case MPQ_FORMAT_VERSION_3: - - // Make sure that the V2+V3 header part is BSWAPPed - BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3); - - // In MPQ format 3.0, the entire header is optional - // and the size of the header can actually be identical - // to size of header 2.0 - if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3) - { - ULONGLONG ArchiveSize64 = pHeader->dwArchiveSize; - - // In format 2.0, the archive size is obsolete and is calculated - // as the highest offset of hash table, block table or hi-block table. - // However, we can still rely on it, if the size of the archive is under 4 GB - if((FileSize - ha->MpqPos) >> 32) - { - ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + (pHeader->dwHashTableSize * sizeof(TMPQHash)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - - ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - - // Only if we actually have a hi-block table - if(pHeader->HiBlockTablePos64) - { - ByteOffset = pHeader->HiBlockTablePos64 + (pHeader->dwBlockTableSize * sizeof(USHORT)); - if(ByteOffset > ArchiveSize64) - ArchiveSize64 = ByteOffset; - } - - // We need to recalculate archive size later, - // when block table is loaded and the position of files is known - ha->dwFlags |= MPQ_FLAG_NEED_FIX_SIZE; - } - - // Initialize the rest of the 3.0 header - pHeader->ArchiveSize64 = ArchiveSize64; - pHeader->HetTablePos64 = 0; - pHeader->BetTablePos64 = 0; - } - - // - // Calculate compressed size of each table. We assume the following order: - // 1) HET table - // 2) BET table - // 3) Classic hash table - // 4) Classic block table - // 5) Hi-block table - // - - // Set all sizes to zero - pHeader->HetTableSize64 = 0; - pHeader->BetTableSize64 = 0; - - // Either both HET and BET table exist or none of them does. - if(pHeader->HetTablePos64) - { - // Compressed size of the HET and BET tables - pHeader->HetTableSize64 = pHeader->BetTablePos64 - pHeader->HetTablePos64; - pHeader->BetTableSize64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) - pHeader->HetTablePos64; - } - - // Compressed size of hash and block table - if(wFormatVersion >= MPQ_FORMAT_VERSION_2) - { - // Compressed size of the hash table - pHeader->HashTableSize64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); - - // Block and hi-block table - if(pHeader->HiBlockTablePos64) - { - pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64; - } - else - { - pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - pHeader->HiBlockTableSize64 = 0; - } - } - else - { - // No known MPQ in format 1.0 has any of the tables compressed - pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - pHeader->HiBlockTableSize64 = 0; - } - - // Set the data chunk size for MD5 to zero - pHeader->dwRawChunkSize = 0; - - // Fill the MD5's - memset(pHeader->MD5_BlockTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HashTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HiBlockTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_BetTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_HetTable, 0, MD5_DIGEST_SIZE); - memset(pHeader->MD5_MpqHeader, 0, MD5_DIGEST_SIZE); - // No break here !!!! - - case MPQ_FORMAT_VERSION_4: - - // Make sure that the V4 header part is BSWAPPed - BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4); - - // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A' - // signature until the position of header MD5 at offset 0xC0 - if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader)) - nError = ERROR_FILE_CORRUPT; - break; - - default: - - // Last resort: Check if it's a War of the Immortal data file (SQP) - nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags); - break; - } - - return nError; -} - -//----------------------------------------------------------------------------- // Default flags for (attributes) and (listfile) -DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize) +DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion) { // Fixed for format 1.0 - if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) + if(wFormatVersion == MPQ_FORMAT_VERSION_1) return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY; // Size-dependent for formats 2.0-4.0 @@ -664,7 +452,7 @@ DWORD DecryptFileKey( DWORD dwMpqPos = (DWORD)MpqPos; // File key is calculated from plain name - szFileName = GetPlainFileNameA(szFileName); + szFileName = GetPlainFileName(szFileName); dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY); // Fix the key, if needed @@ -678,28 +466,30 @@ DWORD DecryptFileKey( //----------------------------------------------------------------------------- // Handle validation functions -bool IsValidMpqHandle(TMPQArchive * ha) +TMPQArchive * IsValidMpqHandle(HANDLE hMpq) { - if(ha == NULL) - return false; - if(ha->pHeader == NULL || ha->pHeader->dwID != ID_MPQ) - return false; + TMPQArchive * ha = (TMPQArchive *)hMpq; - return (bool)(ha->pHeader->dwID == ID_MPQ); + return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL; } -bool IsValidFileHandle(TMPQFile * hf) +TMPQFile * IsValidFileHandle(HANDLE hFile) { - if(hf == NULL) - return false; + TMPQFile * hf = (TMPQFile *)hFile; - if(hf->dwMagic != ID_MPQ_FILE) - return false; + // Must not be NULL + if(hf != NULL && hf->dwMagic == ID_MPQ_FILE) + { + // Local file handle? + if(hf->pStream != NULL) + return hf; - if(hf->pStream != NULL) - return true; + // Also verify the MPQ handle within the file handle + if(IsValidMpqHandle(hf->ha)) + return hf; + } - return IsValidMpqHandle(hf->ha); + return NULL; } //----------------------------------------------------------------------------- @@ -710,10 +500,11 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1 { TMPQHash * pDeletedEntry = NULL; // If a deleted entry was found in the continuous hash range TMPQHash * pFreeEntry = NULL; // If a free entry was found in the continuous hash range + DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); DWORD dwIndex; // Set the initial index - dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask); + dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask); // Search the hash table and return the found entries in the following priority: // 1) <MATCHING_ENTRY> @@ -741,7 +532,7 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1 // Move to the next hash entry. // If we reached the starting entry, it's failure. - dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + dwIndex = (dwIndex + 1) & dwHashIndexMask; if(dwIndex == dwStartIndex) break; } @@ -750,18 +541,18 @@ TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1 return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry; } - // Retrieves the first hash entry for the given file. // Every locale version of a file has its own hash entry TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) { + DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX); DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A); DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B); DWORD dwIndex; // Set the initial index - dwStartIndex = dwIndex = (dwStartIndex & ha->dwHashIndexMask); + dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask); // Search the hash table for(;;) @@ -778,7 +569,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) // Move to the next hash entry. Stop searching // if we got reached the original hash entry - dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + dwIndex = (dwIndex + 1) & dwHashIndexMask; if(dwIndex == dwStartIndex) return NULL; } @@ -786,6 +577,7 @@ TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash) { + DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable); DWORD dwName1 = pHash->dwName1; DWORD dwName2 = pHash->dwName2; @@ -797,7 +589,7 @@ TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * { // Move to the next hash entry. Stop searching // if we got reached the original hash entry - dwIndex = (dwIndex + 1) & ha->dwHashIndexMask; + dwIndex = (dwIndex + 1) & dwHashIndexMask; if(dwIndex == dwStartIndex) return NULL; pHash = ha->pHashTable + dwIndex; @@ -904,21 +696,23 @@ void * LoadMpqTable( TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, - DWORD dwRealSize, + DWORD dwTableSize, DWORD dwKey) { LPBYTE pbCompressed = NULL; LPBYTE pbMpqTable; LPBYTE pbToRead; + DWORD dwBytesToRead = dwCompressedSize; + DWORD dwValidLength = dwTableSize; int nError = ERROR_SUCCESS; // Allocate the MPQ table - pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwRealSize); + pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize); if(pbMpqTable != NULL) { // "interface.MPQ.part" in trial version of World of Warcraft // has block table and hash table compressed. - if(dwCompressedSize < dwRealSize) + if(dwCompressedSize < dwTableSize) { // Allocate temporary buffer for holding compressed data pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize); @@ -930,7 +724,7 @@ void * LoadMpqTable( } // If everything succeeded, read the raw table form the MPQ - if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwCompressedSize)) + if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead)) { // First of all, decrypt the table if(dwKey != 0) @@ -941,17 +735,21 @@ void * LoadMpqTable( } // If the table is compressed, decompress it - if(dwCompressedSize < dwRealSize) + if(dwCompressedSize < dwTableSize) { - int cbOutBuffer = (int)dwRealSize; + int cbOutBuffer = (int)dwTableSize; int cbInBuffer = (int)dwCompressedSize; if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer)) nError = GetLastError(); } - // Makre sure that the table is properly byte-swapped - BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwRealSize); + // Make sure that the table is properly byte-swapped + BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize); + + // If the table was not fully readed, fill the rest with zeros + if(dwValidLength < dwTableSize) + memset(pbMpqTable + dwValidLength, 0, (dwTableSize - dwValidLength)); } else { @@ -1563,8 +1361,8 @@ void FreeMPQFile(TMPQFile *& hf) if(hf != NULL) { // If we have patch file attached to this one, free it first - if(hf->hfPatchFile != NULL) - FreeMPQFile(hf->hfPatchFile); + if(hf->hfPatch != NULL) + FreeMPQFile(hf->hfPatch); // Then free all buffers allocated in the file structure if(hf->pPatchHeader != NULL) @@ -1623,34 +1421,6 @@ void FreeMPQArchive(TMPQArchive *& ha) } } -const char * GetPlainFileNameA(const char * szFileName) -{ - const char * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - -const TCHAR * GetPlainFileNameT(const TCHAR * szFileName) -{ - const TCHAR * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - bool IsInternalMpqFileName(const char * szFileName) { if(szFileName != NULL && szFileName[0] == '(') @@ -1824,17 +1594,6 @@ void ConvertUInt64Buffer(void * ptr, size_t length) } } -// Swaps the TMPQUserData structure -void ConvertTMPQUserData(void *userData) -{ - TMPQUserData * theData = (TMPQUserData *)userData; - - theData->dwID = SwapUInt32(theData->dwID); - theData->cbUserDataSize = SwapUInt32(theData->cbUserDataSize); - theData->dwHeaderOffs = SwapUInt32(theData->dwHeaderOffs); - theData->cbUserDataHeader = SwapUInt32(theData->cbUserDataHeader); -} - // Swaps the TMPQHeader structure void ConvertTMPQHeader(void *header, uint16_t version) { @@ -1854,11 +1613,15 @@ void ConvertTMPQHeader(void *header, uint16_t version) theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize); } - if(version == MPQ_FORMAT_VERSION_2 || version == MPQ_FORMAT_VERSION_3) + if(version == MPQ_FORMAT_VERSION_2) { theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64); theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi); theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi); + } + + if(version == MPQ_FORMAT_VERSION_3) + { theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64); theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64); theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64); diff --git a/src/SBaseFileTable.cpp b/src/SBaseFileTable.cpp index 51d01e4..6b4eada 100644 --- a/src/SBaseFileTable.cpp +++ b/src/SBaseFileTable.cpp @@ -19,54 +19,11 @@ #define MAX_FLAG_INDEX 512 //----------------------------------------------------------------------------- -// Local structures - -// Structure for HET table header -typedef struct _HET_TABLE_HEADER -{ - DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes) - DWORD dwFileCount; // Number of occupied entries in the hash table - DWORD dwHashTableSize; // Size of the hash table (in bytes) - DWORD dwHashEntrySize; // Effective size of the hash entry (in bits) - DWORD dwIndexSizeTotal; // Total size of file index (in bits) - DWORD dwIndexSizeExtra; // Extra bits in the file index - DWORD dwIndexSize; // Effective size of the file index (in bits) - DWORD dwIndexTableSize; // Size of the block index subtable (in bytes) - -} HET_TABLE_HEADER, *PHET_TABLE_HEADER; - -// Structure for BET table header -typedef struct _BET_TABLE_HEADER -{ - DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes) - DWORD dwFileCount; // Number of files in the BET table - DWORD dwUnknown08; - DWORD dwTableEntrySize; // Size of one table entry (in bits) - DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record) - DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record) - DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record) - DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record) - DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record) - DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record) - DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record) - DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record) - DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record) - DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record) - DWORD dwBetHashSizeTotal; // Total size of the BET hash - DWORD dwBetHashSizeExtra; // Extra bits in the BET hash - DWORD dwBetHashSize; // Effective size of BET hash (in bits) - DWORD dwBetHashArraySize; // Size of BET hashes array, in bytes - DWORD dwFlagCount; // Number of flags in the following array - -} BET_TABLE_HEADER, *PBET_TABLE_HEADER; - -//----------------------------------------------------------------------------- // Support for calculating bit sizes static void InitFileFlagArray(LPDWORD FlagArray) { - for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) - FlagArray[dwFlagIndex] = INVALID_FLAG_VALUE; + memset(FlagArray, 0xCC, MAX_FLAG_INDEX * sizeof(DWORD)); } static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) @@ -99,6 +56,19 @@ static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) return dwBitCount; } +static int CompareFilePositions(const void * p1, const void * p2) +{ + TMPQBlock * pBlock1 = *(TMPQBlock **)p1; + TMPQBlock * pBlock2 = *(TMPQBlock **)p2; + + if(pBlock1->dwFilePos < pBlock2->dwFilePos) + return -1; + if(pBlock1->dwFilePos > pBlock2->dwFilePos) + return +1; + + return 0; +} + //----------------------------------------------------------------------------- // Support functions for BIT_ARRAY @@ -116,6 +86,7 @@ static TBitArray * CreateBitArray( if(pBitArray != NULL) { memset(pBitArray, FillValue, nSize); + pBitArray->NumberOfBytes = (NumberOfBits + 7) / 8; pBitArray->NumberOfBits = NumberOfBits; } @@ -255,6 +226,242 @@ void SetBits( } } +//----------------------------------------------------------------------------- +// Support for MPQ header + +static DWORD GetArchiveSize32(TMPQArchive * ha, TMPQBlock * pBlockTable, DWORD dwBlockTableSize) +{ + TMPQHeader * pHeader = ha->pHeader; + ULONGLONG FileSize = 0; + DWORD dwArchiveSize = pHeader->dwHeaderSize; + DWORD dwByteOffset; + DWORD dwBlockIndex; + + // Increment by hash table size + dwByteOffset = pHeader->dwHashTablePos + (pHeader->dwHashTableSize * sizeof(TMPQHash)); + if(dwByteOffset > dwArchiveSize) + dwArchiveSize = dwByteOffset; + + // Increment by block table size + dwByteOffset = pHeader->dwBlockTablePos + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + if(dwByteOffset > dwArchiveSize) + dwArchiveSize = dwByteOffset; + + // If any of the MPQ files is beyond the hash table/block table, set the end to the file size + for(dwBlockIndex = 0; dwBlockIndex < dwBlockTableSize; dwBlockIndex++) + { + // Only count files that exists + if(pBlockTable[dwBlockIndex].dwFlags & MPQ_FILE_EXISTS) + { + // If this file begins past the end of tables, + // assume that the hash/block table is not at the end of the archive + if(pBlockTable[dwBlockIndex].dwFilePos > dwArchiveSize) + { + FileStream_GetSize(ha->pStream, &FileSize); + dwArchiveSize = (DWORD)(FileSize - ha->MpqPos); + break; + } + } + } + + // Return what we found + return dwArchiveSize; +} + +// This function converts the MPQ header so it always looks like version 4 +static ULONGLONG GetArchiveSize64(TMPQHeader * pHeader) +{ + ULONGLONG ArchiveSize = pHeader->dwHeaderSize; + ULONGLONG ByteOffset = pHeader->dwHeaderSize; + + // If there is HET table + if(pHeader->HetTablePos64 != 0) + { + ByteOffset = pHeader->HetTablePos64 + pHeader->HetTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is BET table + if(pHeader->BetTablePos64 != 0) + { + ByteOffset = pHeader->BetTablePos64 + pHeader->BetTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is hash table + if(pHeader->dwHashTablePos || pHeader->wHashTablePosHi) + { + ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + pHeader->HashTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is block table + if(pHeader->dwBlockTablePos || pHeader->wBlockTablePosHi) + { + ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + pHeader->BlockTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + // If there is hi-block table + if(pHeader->HiBlockTablePos64) + { + ByteOffset = pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64; + if(ByteOffset > ArchiveSize) + ArchiveSize = ByteOffset; + } + + return ArchiveSize; +} + +int ConvertMpqHeaderToFormat4( + TMPQArchive * ha, + ULONGLONG FileSize, + DWORD dwFlags) +{ + TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData; + ULONGLONG ByteOffset; + USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion); + int nError = ERROR_SUCCESS; + + // If version 1.0 is forced, then the format version is forced to be 1.0 + // Reason: Storm.dll in Warcraft III ignores format version value + if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) + wFormatVersion = MPQ_FORMAT_VERSION_1; + + // Format-specific fixes + switch(wFormatVersion) + { + case MPQ_FORMAT_VERSION_1: + + // Check for malformed MPQ header version 1.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1); + if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1) + { + pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + + // + // Note: The value of "dwArchiveSize" member in the MPQ header + // is ignored by Storm.dll and can contain garbage value + // ("w3xmaster" protector). + // + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V1, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V1); + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + break; + + case MPQ_FORMAT_VERSION_2: + + // Check for malformed MPQ header version 2.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_2); + if(pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V2) + { + pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; + pHeader->HiBlockTablePos64 = 0; + pHeader->wHashTablePosHi = 0; + pHeader->wBlockTablePosHi = 0; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2); + if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) + pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); + if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + if(pHeader->HiBlockTablePos64) + pHeader->HiBlockTableSize64 = pHeader->dwBlockTableSize * sizeof(USHORT); + pHeader->ArchiveSize64 = GetArchiveSize64(pHeader); + break; + + case MPQ_FORMAT_VERSION_3: + + // In MPQ format 3.0, the entire header is optional + // and the size of the header can actually be identical + // to size of header 2.0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3); + if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3) + { + pHeader->ArchiveSize64 = pHeader->dwArchiveSize; + pHeader->HetTablePos64 = 0; + pHeader->BetTablePos64 = 0; + } + + // + // We need to calculate the compressed size of each table. We assume the following order: + // 1) HET table + // 2) BET table + // 3) Classic hash table + // 4) Classic block table + // 5) Hi-block table + // + + // Fill the rest of the header with zeros + memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3); + ByteOffset = pHeader->ArchiveSize64; + + // Size of the hi-block table + if(pHeader->HiBlockTablePos64) + { + pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64; + ByteOffset = pHeader->HiBlockTablePos64; + } + + // Size of the block table + if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos) + { + pHeader->BlockTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + ByteOffset = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + } + + // Size of the hash table + if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos) + { + pHeader->HashTableSize64 = ByteOffset - MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + ByteOffset = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos); + } + + // Size of the BET table + if(pHeader->BetTablePos64) + { + pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64; + ByteOffset = pHeader->BetTablePos64; + } + + // Size of the HET table + if(pHeader->HetTablePos64) + { + pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64; + ByteOffset = pHeader->HetTablePos64; + } + break; + + case MPQ_FORMAT_VERSION_4: + + // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A' + // signature until the position of header MD5 at offset 0xC0 + BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4); + if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader)) + nError = ERROR_FILE_CORRUPT; + break; + + default: + + // Last resort: Check if it's a War of the Immortal data file (SQP) + nError = ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags); + break; + } + + return nError; +} //----------------------------------------------------------------------------- // Support for hash table @@ -339,14 +546,14 @@ static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, L return NULL; } -static int ConvertMpqBlockTable( +static int BuildFileTableFromBlockTable( TMPQArchive * ha, TFileEntry * pFileTable, TMPQBlock * pBlockTable) { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; - TMPQBlock * pMpqBlock; + TMPQBlock * pBlock; TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; TMPQHash * pHash; @@ -355,7 +562,7 @@ static int ConvertMpqBlockTable( if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { pFileEntry = pFileTable + pHash->dwBlockIndex; - pMpqBlock = pBlockTable + pHash->dwBlockIndex; + pBlock = pBlockTable + pHash->dwBlockIndex; // // Yet another silly map protector: For each valid file, @@ -367,14 +574,17 @@ static int ConvertMpqBlockTable( // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid // - if(!(pMpqBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pMpqBlock->dwFlags & MPQ_FILE_EXISTS)) + if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) { - // Fill the entry - pFileEntry->ByteOffset = pMpqBlock->dwFilePos; + // ByteOffset is only valid if file size is not zero + pFileEntry->ByteOffset = pBlock->dwFilePos; + if(pFileEntry->ByteOffset == 0 && pBlock->dwCSize == 0) + pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize; + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->dwFileSize = pMpqBlock->dwFSize; - pFileEntry->dwCmpSize = pMpqBlock->dwCSize; - pFileEntry->dwFlags = pMpqBlock->dwFlags; + pFileEntry->dwFileSize = pBlock->dwFSize; + pFileEntry->dwCmpSize = pBlock->dwCSize; + pFileEntry->dwFlags = pBlock->dwFlags; pFileEntry->lcLocale = pHash->lcLocale; pFileEntry->wPlatform = pHash->wPlatform; } @@ -394,6 +604,31 @@ static int ConvertMpqBlockTable( return ERROR_SUCCESS; } +static int UpdateFileTableFromHashTable( + TMPQArchive * ha, + TFileEntry * pFileTable) +{ + TFileEntry * pFileEntry; + TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; + TMPQHash * pHash; + + for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + { + if(pHash->dwBlockIndex < ha->dwFileTableSize) + { + pFileEntry = pFileTable + pHash->dwBlockIndex; + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); + pFileEntry->lcLocale = pHash->lcLocale; + pFileEntry->wPlatform = pHash->wPlatform; + } + } + } + + return ERROR_SUCCESS; +} + static TMPQHash * TranslateHashTable( TMPQArchive * ha, ULONGLONG * pcbTableSize) @@ -419,7 +654,8 @@ static TMPQHash * TranslateHashTable( return pHashTable; } -static TMPQBlock * TranslateBlockTable( +// Also used in SFileGetFileInfo +TMPQBlock * TranslateBlockTable( TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable) @@ -427,8 +663,8 @@ static TMPQBlock * TranslateBlockTable( TFileEntry * pFileEntry = ha->pFileTable; TMPQBlock * pBlockTable; TMPQBlock * pBlock; + size_t NeedHiBlockTable = 0; size_t BlockTableSize; - bool bNeedHiBlockTable = false; // Allocate copy of the hash table pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, ha->dwFileTableSize); @@ -438,7 +674,7 @@ static TMPQBlock * TranslateBlockTable( BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize; for(DWORD i = 0; i < ha->dwFileTableSize; i++) { - bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false; + NeedHiBlockTable |= (pFileEntry->ByteOffset >> 32); pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset; pBlock->dwFSize = pFileEntry->dwFileSize; pBlock->dwCSize = pFileEntry->dwCmpSize; @@ -453,7 +689,7 @@ static TMPQBlock * TranslateBlockTable( *pcbTableSize = (ULONGLONG)BlockTableSize; if(pbNeedHiBlockTable != NULL) - *pbNeedHiBlockTable = bNeedHiBlockTable; + *pbNeedHiBlockTable = NeedHiBlockTable ? true : false; } return pBlockTable; @@ -488,21 +724,21 @@ static USHORT * TranslateHiBlockTable( //----------------------------------------------------------------------------- // General EXT table functions -TMPQExtTable * LoadExtTable( +TMPQExtHeader * LoadExtTable( TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey) { - TMPQExtTable * pCompressed = NULL; // Compressed table - TMPQExtTable * pExtTable = NULL; // Uncompressed table + TMPQExtHeader * pCompressed = NULL; // Compressed table + TMPQExtHeader * pExtTable = NULL; // Uncompressed table // Do nothing if the size is zero if(ByteOffset != 0 && Size != 0) { // Allocate size for the compressed table - pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, Size); + pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, Size); if(pExtTable != NULL) { // Load the table from the MPQ @@ -514,7 +750,7 @@ TMPQExtTable * LoadExtTable( } // Swap the ext table header - BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtTable)); + BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtHeader)); if(pExtTable->dwSignature != dwSignature) { STORM_FREE(pExtTable); @@ -523,14 +759,14 @@ TMPQExtTable * LoadExtTable( // Decrypt the block BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); - DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtTable)), dwKey); + DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtHeader)), dwKey); BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); // If the table is compressed, decompress it - if((pExtTable->dwDataSize + sizeof(TMPQExtTable)) > Size) + if((pExtTable->dwDataSize + sizeof(TMPQExtHeader)) > Size) { pCompressed = pExtTable; - pExtTable = (TMPQExtTable *)STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + pCompressed->dwDataSize); + pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + pCompressed->dwDataSize); if(pExtTable != NULL) { int cbOutBuffer = (int)pCompressed->dwDataSize; @@ -557,12 +793,6 @@ TMPQExtTable * LoadExtTable( return pExtTable; } -// Used in MPQ Editor -void FreeMpqBuffer(void * pvBuffer) -{ - STORM_FREE(pvBuffer); -} - static int SaveMpqTable( TMPQArchive * ha, void * pMpqTable, @@ -630,7 +860,7 @@ static int SaveMpqTable( static int SaveExtTable( TMPQArchive * ha, - TMPQExtTable * pExtTable, + TMPQExtHeader * pExtTable, ULONGLONG ByteOffset, DWORD dwTableSize, unsigned char * md5, @@ -639,7 +869,7 @@ static int SaveExtTable( LPDWORD pcbTotalSize) { ULONGLONG FileOffset; - TMPQExtTable * pCompressed = NULL; + TMPQExtHeader * pCompressed = NULL; DWORD cbTotalSize = 0; int nError = ERROR_SUCCESS; @@ -650,7 +880,7 @@ static int SaveExtTable( int cbInBuffer = (int)dwTableSize; // Allocate extra space for compressed table - pCompressed = (TMPQExtTable *)STORM_ALLOC(BYTE, dwTableSize); + pCompressed = (TMPQExtHeader *)STORM_ALLOC(BYTE, dwTableSize); if(pCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; @@ -676,7 +906,7 @@ static int SaveExtTable( if(dwKey != 0) { BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); - EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtTable)), dwKey); + EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtHeader)), dwKey); BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize); } @@ -719,125 +949,170 @@ static int SaveExtTable( static void CreateHetHeader( TMPQHetTable * pHetTable, - PHET_TABLE_HEADER pHetHeader) + TMPQHetHeader * pHetHeader) { - // Fill the BET header - pHetHeader->dwFileCount = pHetTable->dwFileCount; - pHetHeader->dwHashTableSize = pHetTable->dwHashTableSize; - pHetHeader->dwHashEntrySize = pHetTable->dwHashBitSize; - pHetHeader->dwIndexSizeTotal = GetNecessaryBitCount(pHetTable->dwHashTableSize); - pHetHeader->dwIndexSizeExtra = 0; - pHetHeader->dwIndexSize = pHetHeader->dwIndexSizeTotal; - pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwHashTableSize) + 7) / 8; + // Fill the common header + pHetHeader->ExtHdr.dwSignature = HET_TABLE_SIGNATURE; + pHetHeader->ExtHdr.dwVersion = 1; + pHetHeader->ExtHdr.dwDataSize = 0; + + // Fill the HET header + pHetHeader->dwEntryCount = pHetTable->dwEntryCount; + pHetHeader->dwTotalCount = pHetTable->dwTotalCount; + pHetHeader->dwNameHashBitSize = pHetTable->dwNameHashBitSize; + pHetHeader->dwIndexSizeTotal = pHetTable->dwIndexSizeTotal; + pHetHeader->dwIndexSizeExtra = pHetTable->dwIndexSizeExtra; + pHetHeader->dwIndexSize = pHetTable->dwIndexSize; + pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwTotalCount) + 7) / 8; // Calculate the total size needed for holding HET table - pHetHeader->dwTableSize = sizeof(HET_TABLE_HEADER) + - pHetHeader->dwHashTableSize + + pHetHeader->ExtHdr.dwDataSize = + pHetHeader->dwTableSize = sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize; } -TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty) +TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData) { TMPQHetTable * pHetTable; pHetTable = STORM_ALLOC(TMPQHetTable, 1); if(pHetTable != NULL) { - pHetTable->dwIndexSizeTotal = 0; - pHetTable->dwIndexSizeExtra = 0; - pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; - pHetTable->dwHashBitSize = dwHashBitSize; + // Zero the HET table + memset(pHetTable, 0, sizeof(TMPQHetTable)); - // If the hash table size is not entered, calculate an optimal - // hash table size as 4/3 of the current file count. - if(dwHashTableSize == 0) - { - dwHashTableSize = (dwFileCount * 4 / 3); - assert(dwFileCount != 0); - } + // Hash sizes less than 0x40 bits are not tested + assert(dwNameHashBitSize == 0x40); - // Store the hash table size and file count - pHetTable->dwHashTableSize = dwHashTableSize; - pHetTable->dwFileCount = dwFileCount; + // Calculate masks + pHetTable->AndMask64 = ((dwNameHashBitSize != 0x40) ? ((ULONGLONG)1 << dwNameHashBitSize) : 0) - 1; + pHetTable->OrMask64 = (ULONGLONG)1 << (dwNameHashBitSize - 1); - // Size of one index is calculated from hash table size - pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwHashTableSize); - pHetTable->dwIndexSizeExtra = 0; - pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; + // Store the HET table parameters + pHetTable->dwEntryCount = dwFileCount; + pHetTable->dwTotalCount = (dwFileCount * 4) / 3; + pHetTable->dwNameHashBitSize = dwNameHashBitSize; + pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwFileCount); + pHetTable->dwIndexSizeExtra = 0; + pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal; - // Allocate hash table - pHetTable->pHetHashes = STORM_ALLOC(BYTE, pHetTable->dwHashTableSize); - if(pHetTable->pHetHashes != NULL) + // Allocate array of hashes + pHetTable->pNameHashes = STORM_ALLOC(BYTE, pHetTable->dwTotalCount); + if(pHetTable->pNameHashes != NULL) { - // Make sure that the HET hashes are initialized - memset(pHetTable->pHetHashes, 0, pHetTable->dwHashTableSize); + // Make sure the data are initialized + memset(pHetTable->pNameHashes, 0, pHetTable->dwTotalCount); - // If we shall create empty HET table, we have to allocate empty block index table as well - if(bCreateEmpty) - pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwHashTableSize * pHetTable->dwIndexSizeTotal, 0xFF); + // Allocate the bit array for file indexes + pHetTable->pBetIndexes = CreateBitArray(pHetTable->dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF); + if(pHetTable->pBetIndexes != NULL) + { + // Initialize the HET table from the source data (if given) + if(pbSrcData != NULL) + { + // Copy the name hashes + memcpy(pHetTable->pNameHashes, pbSrcData, pHetTable->dwTotalCount); - // Calculate masks - pHetTable->AndMask64 = 0; - if(dwHashBitSize != 0x40) - pHetTable->AndMask64 = (ULONGLONG)1 << dwHashBitSize; - pHetTable->AndMask64--; + // Copy the file indexes + memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + pHetTable->dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes); + } + + // Return the result HET table + return pHetTable; + } - pHetTable->OrMask64 = (ULONGLONG)1 << (dwHashBitSize - 1); + // Free the name hashes + STORM_FREE(pHetTable->pNameHashes); } - else + + STORM_FREE(pHetTable); + } + + // Failed + return NULL; +} + +static int InsertHetEntry(TMPQHetTable * pHetTable, ULONGLONG FileNameHash, DWORD dwFileIndex) +{ + DWORD StartIndex; + DWORD Index; + BYTE NameHash1; + + // Get the start index and the high 8 bits of the name hash + StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount); + NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8)); + + // Find a place where to put it + for(;;) + { + // Did we find a free HET entry? + if(pHetTable->pNameHashes[Index] == HET_ENTRY_FREE) { - STORM_FREE(pHetTable); - pHetTable = NULL; + // Set the entry in the name hash table + pHetTable->pNameHashes[Index] = NameHash1; + + // Set the entry in the file index table + SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, + pHetTable->dwIndexSize, + &dwFileIndex, + 4); + return ERROR_SUCCESS; } + + // Move to the next entry in the HET table + // If we came to the start index again, we are done + Index = (Index + 1) % pHetTable->dwEntryCount; + if(Index == StartIndex) + break; } - return pHetTable; + // No space in the HET table. Should never happen, + // because the HET table is created according to the number of files + assert(false); + return ERROR_DISK_FULL; } -static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable) +static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader) { - HET_TABLE_HEADER HetHeader; TMPQHetTable * pHetTable = NULL; - LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1); + LPBYTE pbSrcData = (LPBYTE)(pHetHeader + 1); // Sanity check - assert(pExtTable->dwSignature == HET_TABLE_SIGNATURE); - assert(pExtTable->dwVersion == 1); + assert(pHetHeader->ExtHdr.dwSignature == HET_TABLE_SIGNATURE); + assert(pHetHeader->ExtHdr.dwVersion == 1); // Verify size of the HET table - if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(HET_TABLE_HEADER)) + if(pHetHeader->ExtHdr.dwDataSize >= sizeof(TMPQHetHeader)) { - // Copy the table header in order to have it aligned and swapped - memcpy(&HetHeader, pbSrcData, sizeof(HET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(&HetHeader, sizeof(HET_TABLE_HEADER)); - pbSrcData += sizeof(HET_TABLE_HEADER); - // Verify the size of the table in the header - if(HetHeader.dwTableSize == pExtTable->dwDataSize) + if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize) { + // The size of the HET table must be sum of header, hash and index table size + assert((sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize) == pHetHeader->dwTableSize); + + // So far, all MPQs with HET Table have had total number of entries equal to 4/3 of file count + assert(((pHetHeader->dwEntryCount * 4) / 3) == pHetHeader->dwTotalCount); + + // The size of one index is predictable as well + assert(GetNecessaryBitCount(pHetHeader->dwEntryCount) == pHetHeader->dwIndexSizeTotal); + // The size of index table (in entries) is expected // to be the same like the hash table size (in bytes) - assert(((HetHeader.dwIndexTableSize * 8) / HetHeader.dwIndexSize) == HetHeader.dwHashTableSize); - + assert(((pHetHeader->dwTotalCount * pHetHeader->dwIndexSizeTotal) + 7) / 8 == pHetHeader->dwIndexTableSize); + // Create translated table - pHetTable = CreateHetTable(HetHeader.dwHashTableSize, HetHeader.dwFileCount, HetHeader.dwHashEntrySize, false); + pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwNameHashBitSize, pbSrcData); if(pHetTable != NULL) { - // Copy the hash table size, index size and extra bits from the HET header - pHetTable->dwHashTableSize = HetHeader.dwHashTableSize; - pHetTable->dwIndexSizeTotal = HetHeader.dwIndexSizeTotal; - pHetTable->dwIndexSizeExtra = HetHeader.dwIndexSizeExtra; - - // Fill the hash table - if(pHetTable->pHetHashes != NULL) - memcpy(pHetTable->pHetHashes, pbSrcData, pHetTable->dwHashTableSize); - pbSrcData += pHetTable->dwHashTableSize; - - // Copy the block index table - pHetTable->pBetIndexes = CreateBitArray(HetHeader.dwIndexTableSize * 8, 0xFF); - if(pHetTable->pBetIndexes != NULL) - memcpy(pHetTable->pBetIndexes->Elements, pbSrcData, HetHeader.dwIndexTableSize); - pbSrcData += HetHeader.dwIndexTableSize; + // Now the sizes in the hash table should be already set + assert(pHetTable->dwEntryCount == pHetHeader->dwEntryCount); + assert(pHetTable->dwTotalCount == pHetHeader->dwTotalCount); + assert(pHetTable->dwIndexSizeTotal == pHetHeader->dwIndexSizeTotal); + + // Copy the missing variables + pHetTable->dwIndexSizeExtra = pHetHeader->dwIndexSizeExtra; + pHetTable->dwIndexSize = pHetHeader->dwIndexSize; } } } @@ -845,90 +1120,78 @@ static TMPQHetTable * TranslateHetTable(TMPQExtTable * pExtTable) return pHetTable; } -static TMPQExtTable * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable) +static TMPQExtHeader * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable) { - TMPQExtTable * pExtTable = NULL; - HET_TABLE_HEADER HetHeader; + TMPQHetHeader * pHetHeader = NULL; + TMPQHetHeader HetHeader; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; - size_t HetTableSize; // Prepare header of the HET table CreateHetHeader(pHetTable, &HetHeader); - // Calculate the total size needed for holding the encrypted HET table - HetTableSize = HetHeader.dwTableSize; - // Allocate space for the linear table - pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + HetTableSize); + pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + HetHeader.dwTableSize); if(pbLinearTable != NULL) { - // Create the common ext table header - pExtTable = (TMPQExtTable *)pbLinearTable; - pExtTable->dwSignature = HET_TABLE_SIGNATURE; - pExtTable->dwVersion = 1; - pExtTable->dwDataSize = (DWORD)HetTableSize; - pbTrgData = (LPBYTE)(pExtTable + 1); + // Copy the table header + pHetHeader = (TMPQHetHeader *)pbLinearTable; + memcpy(pHetHeader, &HetHeader, sizeof(TMPQHetHeader)); + pbTrgData = (LPBYTE)(pHetHeader + 1); - // Copy the HET table header - memcpy(pbTrgData, &HetHeader, sizeof(HET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(HET_TABLE_HEADER)); - pbTrgData += sizeof(HET_TABLE_HEADER); - - // Copy the array of HET hashes - memcpy(pbTrgData, pHetTable->pHetHashes, pHetTable->dwHashTableSize); - pbTrgData += pHetTable->dwHashTableSize; + // Copy the array of name hashes + memcpy(pbTrgData, pHetTable->pNameHashes, pHetTable->dwTotalCount); + pbTrgData += pHetTable->dwTotalCount; // Copy the bit array of BET indexes memcpy(pbTrgData, pHetTable->pBetIndexes->Elements, HetHeader.dwIndexTableSize); - // Calculate the total size of the table, including the TMPQExtTable + // Calculate the total size of the table, including the TMPQExtHeader if(pcbHetTable != NULL) { - *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtTable) + HetTableSize); + *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + HetHeader.dwTableSize); } } - return pExtTable; + return &pHetHeader->ExtHdr; } DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) { TMPQHetTable * pHetTable = ha->pHetTable; ULONGLONG FileNameHash; - ULONGLONG AndMask64; - ULONGLONG OrMask64; - ULONGLONG BetHash; DWORD StartIndex; DWORD Index; - BYTE HetHash; // Upper 8 bits of the masked file name hash + BYTE NameHash1; // Upper 8 bits of the masked file name hash + + // If there are no entries in the HET table, do nothing + if(pHetTable->dwEntryCount == 0) + return HASH_ENTRY_FREE; // Do nothing if the MPQ has no HET table assert(ha->pHetTable != NULL); // Calculate 64-bit hash of the file name - AndMask64 = pHetTable->AndMask64; - OrMask64 = pHetTable->OrMask64; - FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64; + FileNameHash = (HashStringJenkins(szFileName) & pHetTable->AndMask64) | pHetTable->OrMask64; // Split the file name hash into two parts: - // Part 1: The highest 8 bits of the name hash - // Part 2: The rest of the name hash (without the highest 8 bits) - HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8)); - BetHash = FileNameHash & (AndMask64 >> 0x08); + // NameHash1: The highest 8 bits of the name hash + // NameHash2: File name hash limited to hash size + // Note: Our file table contains full name hash, no need to cut the high 8 bits before comparison + NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8)); // Calculate the starting index to the hash table - StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize); + StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwEntryCount); // Go through HET table until we find a terminator - while(pHetTable->pHetHashes[Index] != HET_ENTRY_FREE) + while(pHetTable->pNameHashes[Index] != HET_ENTRY_FREE) { // Did we find a match ? - if(pHetTable->pHetHashes[Index] == HetHash) + if(pHetTable->pNameHashes[Index] == NameHash1) { DWORD dwFileIndex = 0; - // Get the index of the BetHash + // Get the file index GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, pHetTable->dwIndexSize, &dwFileIndex, @@ -940,14 +1203,14 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) // assert(dwFileIndex <= ha->dwFileTableSize); // - // Verify the BetHash against the entry in the table of BET hashes - if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash) + // Verify the FileNameHash against the entry in the table of name hashes + if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash) return dwFileIndex; } - // Move to the next entry in the primary search table + // Move to the next entry in the HET table // If we came to the start index again, we are done - Index = (Index + 1) % pHetTable->dwHashTableSize; + Index = (Index + 1) % pHetTable->dwEntryCount; if(Index == StartIndex) break; } @@ -956,103 +1219,12 @@ DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName) return HASH_ENTRY_FREE; } -DWORD AllocateHetEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) -{ - TMPQHetTable * pHetTable = ha->pHetTable; - ULONGLONG FileNameHash; - ULONGLONG AndMask64; - ULONGLONG OrMask64; - ULONGLONG BetHash; - DWORD FileCountIncrement = 0; - DWORD FreeHetIndex = HASH_ENTRY_FREE; - DWORD dwFileIndex; - DWORD StartIndex; - DWORD Index; - BYTE HetHash; // Upper 8 bits of the masked file name hash - - // Do nothing if the MPQ has no HET table - assert(ha->pHetTable != NULL); - - // Calculate 64-bit hash of the file name - AndMask64 = pHetTable->AndMask64; - OrMask64 = pHetTable->OrMask64; - FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64; - - // Calculate the starting index to the hash table - StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwHashTableSize); - - // Split the file name hash into two parts: - // Part 1: The highest 8 bits of the name hash - // Part 2: The rest of the name hash (without the highest 8 bits) - HetHash = (BYTE)(FileNameHash >> (pHetTable->dwHashBitSize - 8)); - BetHash = FileNameHash & (AndMask64 >> 0x08); - - // Go through HET table until we find a terminator - for(;;) - { - // Did we find a match ? - if(pHetTable->pHetHashes[Index] == HetHash) - { - DWORD dwFileIndex = 0; - - // Get the index of the BetHash - GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index, - pHetTable->dwIndexSize, - &dwFileIndex, - 4); - // - // TODO: This condition only happens when we are opening a MPQ - // where some files were deleted by StormLib. Perhaps - // we should not allow shrinking of the file table in MPQs v 4.0? - // assert(dwFileIndex <= ha->dwFileTableSize); - // - - // Verify the BetHash against the entry in the table of BET hashes - if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].BetHash == BetHash) - { - FreeHetIndex = Index; - break; - } - } - - // Check for entries that might have been deleted - if(pHetTable->pHetHashes[Index] == HET_ENTRY_DELETED || pHetTable->pHetHashes[Index] == HET_ENTRY_FREE) - { - FileCountIncrement++; - FreeHetIndex = Index; - break; - } - - // Move to the next entry in the primary search table - // If we came to the start index again, we are done - Index = (Index + 1) % pHetTable->dwHashTableSize; - if(Index == StartIndex) - return HASH_ENTRY_FREE; - } - - // Fill the HET table entry - dwFileIndex = (DWORD)(pFileEntry - ha->pFileTable); - pHetTable->pHetHashes[FreeHetIndex] = HetHash; - pHetTable->dwFileCount += FileCountIncrement; - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * FreeHetIndex, - pHetTable->dwIndexSize, - &dwFileIndex, - 4); - - // Fill the file entry - pFileEntry->BetHash = BetHash; - pFileEntry->dwHetIndex = FreeHetIndex; - return FreeHetIndex; -} - void FreeHetTable(TMPQHetTable * pHetTable) { if(pHetTable != NULL) { - if(pHetTable->pHetHashes != NULL) - STORM_FREE(pHetTable->pHetHashes); + if(pHetTable->pNameHashes != NULL) + STORM_FREE(pHetTable->pNameHashes); if(pHetTable->pBetIndexes != NULL) STORM_FREE(pHetTable->pBetIndexes); @@ -1065,7 +1237,7 @@ void FreeHetTable(TMPQHetTable * pHetTable) static void CreateBetHeader( TMPQArchive * ha, - PBET_TABLE_HEADER pBetHeader) + TMPQBetHeader * pBetHeader) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; @@ -1079,9 +1251,18 @@ static void CreateBetHeader( // Initialize array of flag combinations InitFileFlagArray(FlagArray); + // Fill the common header + pBetHeader->ExtHdr.dwSignature = BET_TABLE_SIGNATURE; + pBetHeader->ExtHdr.dwVersion = 1; + pBetHeader->ExtHdr.dwDataSize = 0; + // Get the maximum values for the BET table for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { + // + // Note: Deleted files must be counted as well + // + // Highest file position in the MPQ if(pFileEntry->ByteOffset > MaxByteOffset) MaxByteOffset = pFileEntry->ByteOffset; @@ -1124,24 +1305,25 @@ static void CreateBetHeader( pBetHeader->dwBitCount_Unknown; // Save the file count and flag count - pBetHeader->dwFileCount = ha->dwFileTableSize; + pBetHeader->dwEntryCount = ha->dwFileTableSize; pBetHeader->dwFlagCount = dwMaxFlagIndex + 1; pBetHeader->dwUnknown08 = 0x10; // Save the total size of the BET hash - pBetHeader->dwBetHashSizeTotal = ha->pHetTable->dwHashBitSize - 0x08; - pBetHeader->dwBetHashSizeExtra = 0; - pBetHeader->dwBetHashSize = pBetHeader->dwBetHashSizeTotal; - pBetHeader->dwBetHashArraySize = ((pBetHeader->dwBetHashSizeTotal * pBetHeader->dwFileCount) + 7) / 8; + pBetHeader->dwBitTotal_NameHash2 = ha->pHetTable->dwNameHashBitSize - 0x08; + pBetHeader->dwBitExtra_NameHash2 = 0; + pBetHeader->dwBitCount_NameHash2 = pBetHeader->dwBitTotal_NameHash2; + pBetHeader->dwNameHashArraySize = ((pBetHeader->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount) + 7) / 8; // Save the total table size - pBetHeader->dwTableSize = sizeof(BET_TABLE_HEADER) + + pBetHeader->ExtHdr.dwDataSize = + pBetHeader->dwTableSize = sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader) + pBetHeader->dwFlagCount * sizeof(DWORD) + - ((pBetHeader->dwTableEntrySize * pBetHeader->dwFileCount) + 7) / 8 + - pBetHeader->dwBetHashArraySize; + ((pBetHeader->dwTableEntrySize * pBetHeader->dwEntryCount) + 7) / 8 + + pBetHeader->dwNameHashArraySize; } -TMPQBetTable * CreateBetTable(DWORD dwFileCount) +TMPQBetTable * CreateBetTable(DWORD dwEntryCount) { TMPQBetTable * pBetTable; @@ -1150,7 +1332,7 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount) if(pBetTable != NULL) { memset(pBetTable, 0, sizeof(TMPQBetTable)); - pBetTable->dwFileCount = dwFileCount; + pBetTable->dwEntryCount = dwEntryCount; } return pBetTable; @@ -1158,90 +1340,87 @@ TMPQBetTable * CreateBetTable(DWORD dwFileCount) static TMPQBetTable * TranslateBetTable( TMPQArchive * ha, - TMPQExtTable * pExtTable) + TMPQBetHeader * pBetHeader) { - BET_TABLE_HEADER BetHeader; TMPQBetTable * pBetTable = NULL; - LPBYTE pbSrcData = (LPBYTE)(pExtTable + 1); + LPBYTE pbSrcData = (LPBYTE)(pBetHeader + 1); DWORD LengthInBytes; // Sanity check - assert(pExtTable->dwSignature == BET_TABLE_SIGNATURE); - assert(pExtTable->dwVersion == 1); + assert(pBetHeader->ExtHdr.dwSignature == BET_TABLE_SIGNATURE); + assert(pBetHeader->ExtHdr.dwVersion == 1); assert(ha->pHetTable != NULL); ha = ha; // Verify size of the HET table - if(pExtTable != NULL && pExtTable->dwDataSize >= sizeof(BET_TABLE_HEADER)) + if(pBetHeader->ExtHdr.dwDataSize >= sizeof(TMPQBetHeader)) { - // Copy the table header in order to have it aligned and swapped - memcpy(&BetHeader, pbSrcData, sizeof(BET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(&BetHeader, sizeof(BET_TABLE_HEADER)); - pbSrcData += sizeof(BET_TABLE_HEADER); - - // Some MPQs affected by a bug in StormLib have pBetTable->dwFileCount - // greater than ha->dwMaxFileCount - if(BetHeader.dwFileCount > ha->dwMaxFileCount) - return NULL; - // Verify the size of the table in the header - if(BetHeader.dwTableSize == pExtTable->dwDataSize) + if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize) { + // The number of entries in the BET table must be the same like number of entries in the block table + assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize); + assert(pBetHeader->dwEntryCount <= ha->dwMaxFileCount); + + // The number of entries in the BET table must be the same like number of entries in the HET table + // Note that if it's not, it is not a problem + //assert(pBetHeader->dwEntryCount == ha->pHetTable->dwEntryCount); + // Create translated table - pBetTable = CreateBetTable(BetHeader.dwFileCount); + pBetTable = CreateBetTable(pBetHeader->dwEntryCount); if(pBetTable != NULL) { // Copy the variables from the header to the BetTable - pBetTable->dwTableEntrySize = BetHeader.dwTableEntrySize; - pBetTable->dwBitIndex_FilePos = BetHeader.dwBitIndex_FilePos; - pBetTable->dwBitIndex_FileSize = BetHeader.dwBitIndex_FileSize; - pBetTable->dwBitIndex_CmpSize = BetHeader.dwBitIndex_CmpSize; - pBetTable->dwBitIndex_FlagIndex = BetHeader.dwBitIndex_FlagIndex; - pBetTable->dwBitIndex_Unknown = BetHeader.dwBitIndex_Unknown; - pBetTable->dwBitCount_FilePos = BetHeader.dwBitCount_FilePos; - pBetTable->dwBitCount_FileSize = BetHeader.dwBitCount_FileSize; - pBetTable->dwBitCount_CmpSize = BetHeader.dwBitCount_CmpSize; - pBetTable->dwBitCount_FlagIndex = BetHeader.dwBitCount_FlagIndex; - pBetTable->dwBitCount_Unknown = BetHeader.dwBitCount_Unknown; - - // Since we don't know what the "unknown" is, we'll assert when it's nonzero + pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize; + pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos; + pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize; + pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize; + pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex; + pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown; + pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos; + pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize; + pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize; + pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex; + pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown; + + // Since we don't know what the "unknown" is, we'll assert when it's zero assert(pBetTable->dwBitCount_Unknown == 0); // Allocate array for flags - if(BetHeader.dwFlagCount != 0) + if(pBetHeader->dwFlagCount != 0) { // Allocate array for file flags and load it - pBetTable->pFileFlags = STORM_ALLOC(DWORD, BetHeader.dwFlagCount); + pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount); if(pBetTable->pFileFlags != NULL) { - LengthInBytes = BetHeader.dwFlagCount * sizeof(DWORD); + LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD); memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes); BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes); pbSrcData += LengthInBytes; } // Save the number of flags - pBetTable->dwFlagCount = BetHeader.dwFlagCount; + pBetTable->dwFlagCount = pBetHeader->dwFlagCount; } // Load the bit-based file table - pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * BetHeader.dwFileCount, 0); + pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0); LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8; if(pBetTable->pFileTable != NULL) memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes); pbSrcData += LengthInBytes; // Fill the sizes of BET hash - pBetTable->dwBetHashSizeTotal = BetHeader.dwBetHashSizeTotal; - pBetTable->dwBetHashSizeExtra = BetHeader.dwBetHashSizeExtra; - pBetTable->dwBetHashSize = BetHeader.dwBetHashSize; + pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2; + pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2; + pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2; // Create and load the array of BET hashes - pBetTable->pBetHashes = CreateBitArray(pBetTable->dwBetHashSizeTotal * BetHeader.dwFileCount, 0); - LengthInBytes = (pBetTable->pBetHashes->NumberOfBits + 7) / 8; - if(pBetTable->pBetHashes != NULL) - memcpy(pBetTable->pBetHashes->Elements, pbSrcData, LengthInBytes); - pbSrcData += BetHeader.dwBetHashArraySize; + pBetTable->pNameHashes = CreateBitArray(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0); + LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8; + if(pBetTable->pNameHashes != NULL) + memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes); + pbSrcData += pBetHeader->dwNameHashArraySize; // Dump both tables // DumpHetAndBetTable(ha->pHetTable, pBetTable); @@ -1252,63 +1431,47 @@ static TMPQBetTable * TranslateBetTable( return pBetTable; } -TMPQExtTable * TranslateBetTable( +TMPQExtHeader * TranslateBetTable( TMPQArchive * ha, ULONGLONG * pcbBetTable) { - TMPQExtTable * pExtTable = NULL; - BET_TABLE_HEADER BetHeader; + TMPQBetHeader * pBetHeader = NULL; + TMPQBetHeader BetHeader; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pFileEntry; TBitArray * pBitArray = NULL; LPBYTE pbLinearTable = NULL; LPBYTE pbTrgData; - size_t BetTableSize; DWORD LengthInBytes; DWORD FlagArray[MAX_FLAG_INDEX]; - DWORD i; // Calculate the bit sizes of various entries InitFileFlagArray(FlagArray); CreateBetHeader(ha, &BetHeader); - // Calculate the size of the BET table - BetTableSize = sizeof(BET_TABLE_HEADER) + - BetHeader.dwFlagCount * sizeof(DWORD) + - ((BetHeader.dwTableEntrySize * BetHeader.dwFileCount) + 7) / 8 + - BetHeader.dwBetHashArraySize; - // Allocate space - pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtTable) + BetTableSize); + pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + BetHeader.dwTableSize); if(pbLinearTable != NULL) { - // Create the common ext table header - pExtTable = (TMPQExtTable *)pbLinearTable; - pExtTable->dwSignature = BET_TABLE_SIGNATURE; - pExtTable->dwVersion = 1; - pExtTable->dwDataSize = (DWORD)BetTableSize; - pbTrgData = (LPBYTE)(pExtTable + 1); - - // Copy the BET table header - memcpy(pbTrgData, &BetHeader, sizeof(BET_TABLE_HEADER)); - BSWAP_ARRAY32_UNSIGNED(pbTrgData, sizeof(BET_TABLE_HEADER)); - pbTrgData += sizeof(BET_TABLE_HEADER); + // Copy the BET header to the linear buffer + pBetHeader = (TMPQBetHeader *)pbLinearTable; + memcpy(pBetHeader, &BetHeader, sizeof(TMPQBetHeader)); + pbTrgData = (LPBYTE)(pBetHeader + 1); // Save the bit-based block table - pBitArray = CreateBitArray(BetHeader.dwFileCount * BetHeader.dwTableEntrySize, 0); + pBitArray = CreateBitArray(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0); if(pBitArray != NULL) { - TFileEntry * pFileEntry = ha->pFileTable; DWORD dwFlagIndex = 0; DWORD nBitOffset = 0; - // Construct the array of flag values and bit-based file table - for(i = 0; i < BetHeader.dwFileCount; i++, pFileEntry++) + // Construct the bit-based file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // - // Note: Blizzard MPQs contain valid values even for non-existant files - // (FilePos, FileSize, CmpSize and FlagIndex) - // Note: If flags is zero, it must be in the flag table too !!! + // Note: Missing files must be included as well // - + // Save the byte offset SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FilePos, BetHeader.dwBitCount_FilePos, @@ -1349,33 +1512,22 @@ TMPQExtTable * TranslateBetTable( STORM_FREE(pBitArray); } - // Create bit array for BET hashes - pBitArray = CreateBitArray(BetHeader.dwBetHashSizeTotal * BetHeader.dwFileCount, 0); + // Create bit array for name hashes + pBitArray = CreateBitArray(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0); if(pBitArray != NULL) { - TFileEntry * pFileEntry = ha->pFileTable; - ULONGLONG AndMask64 = ha->pHetTable->AndMask64; - ULONGLONG OrMask64 = ha->pHetTable->OrMask64; + DWORD dwFileIndex = 0; - for(i = 0; i < BetHeader.dwFileCount; i++) + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - ULONGLONG FileNameHash = 0; - - // Calculate 64-bit hash of the file name - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) - { - FileNameHash = (HashStringJenkins(pFileEntry->szFileName) & AndMask64) | OrMask64; - FileNameHash = FileNameHash & (AndMask64 >> 0x08); - } - // Insert the name hash to the bit array - SetBits(pBitArray, BetHeader.dwBetHashSizeTotal * i, - BetHeader.dwBetHashSize, - &FileNameHash, + SetBits(pBitArray, BetHeader.dwBitTotal_NameHash2 * dwFileIndex, + BetHeader.dwBitCount_NameHash2, + &pFileEntry->FileNameHash, 8); - - // Move to the next file entry - pFileEntry++; + + assert(dwFileIndex < BetHeader.dwEntryCount); + dwFileIndex++; } // Write the array of BET hashes @@ -1390,11 +1542,11 @@ TMPQExtTable * TranslateBetTable( // Write the size of the BET table in the MPQ if(pcbBetTable != NULL) { - *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtTable) + BetTableSize); + *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + BetHeader.dwTableSize); } } - return pExtTable; + return &pBetHeader->ExtHdr; } void FreeBetTable(TMPQBetTable * pBetTable) @@ -1405,8 +1557,8 @@ void FreeBetTable(TMPQBetTable * pBetTable) STORM_FREE(pBetTable->pFileTable); if(pBetTable->pFileFlags != NULL) STORM_FREE(pBetTable->pFileFlags); - if(pBetTable->pBetHashes != NULL) - STORM_FREE(pBetTable->pBetHashes); + if(pBetTable->pNameHashes != NULL) + STORM_FREE(pBetTable->pNameHashes); STORM_FREE(pBetTable); } @@ -1498,7 +1650,7 @@ TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex) return NULL; } -void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) +void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName) { // Sanity check assert(pFileEntry != NULL); @@ -1518,129 +1670,104 @@ void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) if(pFileEntry->szFileName != NULL) strcpy(pFileEntry->szFileName, szFileName); } -} + // We also need to create the file name hash + if(ha->pHetTable != NULL) + { + ULONGLONG AndMask64 = ha->pHetTable->AndMask64; + ULONGLONG OrMask64 = ha->pHetTable->OrMask64; + + pFileEntry->FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64; + } +} -// Finds a free file entry. Does NOT increment table size. -TFileEntry * FindFreeFileEntry(TMPQArchive * ha) +TFileEntry * FindDeletedFileEntry(TMPQArchive * ha) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFreeEntry = NULL; TFileEntry * pFileEntry; - // Try to find a free entry + // Go through the entire file table and try to find a deleted entry for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - // If that entry is free, we reuse it - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - pFreeEntry = pFileEntry; - break; - } + // Return the entry that is within the current file table (i.e. a deleted file) + if(!(pFileEntry->dwFlags & MPQ_FILE_EXISTS)) + return pFileEntry; // // Note: Files with "delete marker" are not deleted. - // Don't consider them free entries + // Don't treat them as available // } - // Do we have a deleted entry? - if(pFreeEntry != NULL) - { - ClearFileEntry(ha, pFreeEntry); - return pFreeEntry; - } - - // If no file entry within the existing file table is free, - // we try the reserve space after current file table - if(ha->dwFileTableSize < ha->dwMaxFileCount) - return ha->pFileTable + ha->dwFileTableSize; - - // If we reached maximum file count, we cannot add more files to the MPQ - assert(ha->dwFileTableSize == ha->dwMaxFileCount); + // No deleted entries found return NULL; } - TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale) { TFileEntry * pFileEntry = NULL; TMPQHash * pHash; - DWORD dwHashIndex; - DWORD dwFileIndex; - bool bHashEntryExists = false; - bool bHetEntryExists = false; - // If the archive has classic hash table, we try to - // find the file in the hash table - if(ha->pHashTable != NULL) + // The entry in the hash table should not be there. + // This case must be handled by the caller + assert(ha->pHashTable == NULL || GetHashEntryExact(ha, szFileName, lcLocale) == NULL); + assert(ha->pHetTable == NULL || GetFileIndex_Het(ha, szFileName) == HASH_ENTRY_FREE); + + // If we are in the middle of saving listfile, we need to allocate + // the file entry at the end of the file table + if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0) { - // If the hash entry is already there, we reuse the file entry - pHash = GetHashEntryExact(ha, szFileName, lcLocale); - if(pHash != NULL) + // Attempt to find a deleted file entry in the file table. + // If it suceeds, we reuse that entry + pFileEntry = FindDeletedFileEntry(ha); + if(pFileEntry == NULL) { - pFileEntry = ha->pFileTable + pHash->dwBlockIndex; - bHashEntryExists = true; - } - } + // If there is no space in the file table, we are sorry + if((ha->dwFileTableSize + ha->dwReservedFiles) >= ha->dwMaxFileCount) + return NULL; - // If the archive has HET table, try to use it for - // finding the file - if(ha->pHetTable != NULL) - { - dwFileIndex = GetFileIndex_Het(ha, szFileName); - if(dwFileIndex != HASH_ENTRY_FREE) + // Invalidate the internal files. Then we need to search for a deleted entry again, + // because the previous call to InvalidateInternalFiles might have created some + InvalidateInternalFiles(ha); + + // Re-check for deleted entries + pFileEntry = FindDeletedFileEntry(ha); + if(pFileEntry == NULL) + { + // If there is still no deleted entry, we allocate an entry at the end of the file table + assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount); + pFileEntry = ha->pFileTable + ha->dwFileTableSize++; + } + } + else { - pFileEntry = ha->pFileTable + dwFileIndex; - bHetEntryExists = true; + // Invalidate the internal files + InvalidateInternalFiles(ha); } } - - // If still haven't found the file entry, we allocate new one - if(pFileEntry == NULL) + else { - pFileEntry = FindFreeFileEntry(ha); - if(pFileEntry == NULL) - return NULL; + // There should be at least one entry for that internal file + assert((ha->dwFileTableSize + ha->dwReservedFiles) < ha->dwMaxFileCount); + pFileEntry = ha->pFileTable + ha->dwFileTableSize++; } - // Fill the rest of the file entry - pFileEntry->ByteOffset = 0; - pFileEntry->FileTime = 0; - pFileEntry->dwFileSize = 0; - pFileEntry->dwCmpSize = 0; - pFileEntry->dwFlags = 0; - pFileEntry->lcLocale = (USHORT)lcLocale; - pFileEntry->wPlatform = 0; - pFileEntry->dwCrc32 = 0; - memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE); - - // Allocate space for file name, if it's not there yet - AllocateFileName(pFileEntry, szFileName); - - // If the free file entry is at the end of the file table, - // we have to increment file table size - if(pFileEntry == ha->pFileTable + ha->dwFileTableSize) + // Did we find an usable file entry? + if(pFileEntry != NULL) { - assert(ha->dwFileTableSize < ha->dwMaxFileCount); - ha->pHeader->dwBlockTableSize++; - ha->dwFileTableSize++; - } + // Make sure that the entry is properly initialized + memset(pFileEntry, 0, sizeof(TFileEntry)); + pFileEntry->lcLocale = (USHORT)lcLocale; + AllocateFileName(ha, pFileEntry, szFileName); - // If the MPQ has hash table, we have to insert the new entry into the hash table - if(ha->pHashTable != NULL && bHashEntryExists == false) - { - pHash = AllocateHashEntry(ha, pFileEntry); - assert(pHash != NULL); - } - - // If the MPQ has HET table, we have to insert it to the HET table as well - if(ha->pHetTable != NULL && bHetEntryExists == false) - { - // TODO: Does HET table even support locales? - // Most probably, Blizzard gave up that silly idea long ago. - dwHashIndex = AllocateHetEntry(ha, pFileEntry); - assert(dwHashIndex != HASH_ENTRY_FREE); + // If the MPQ has hash table, we have to insert the new entry into the hash table + // We expect it to succeed because there must be a free hash entry if there is a free file entry + // Note: Don't bother with the HET table. It will be rebuilt anyway + if(ha->pHashTable != NULL) + { + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); + } } // Return the file entry @@ -1653,10 +1780,8 @@ int RenameFileEntry( const char * szNewFileName) { TMPQHash * pHash; - DWORD dwFileIndex; - int nError = ERROR_SUCCESS; - // If the MPQ has classic hash table, clear the entry there + // Mark the entry as deleted in the hash table if(ha->pHashTable != NULL) { assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); @@ -1666,23 +1791,10 @@ int RenameFileEntry( pHash->dwBlockIndex = HASH_ENTRY_DELETED; } - // If the MPQ has HET table, clear the entry there as well - if(ha->pHetTable != NULL) - { - TMPQHetTable * pHetTable = ha->pHetTable; - DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1; - - assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize); - - // Clear the entry in the HET hash array - pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED; - - // Set the BET index to invalid index - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex, - pHetTable->dwIndexSize, - &dwInvalidFileIndex, - 4); - } + // + // Note: Don't bother with the HET table. + // It will be rebuilt from scratch anyway + // // Free the old file name if(pFileEntry->szFileName != NULL) @@ -1690,157 +1802,131 @@ int RenameFileEntry( pFileEntry->szFileName = NULL; // Allocate new file name - AllocateFileName(pFileEntry, szNewFileName); + AllocateFileName(ha, pFileEntry, szNewFileName); // Now find a hash entry for the new file name if(ha->pHashTable != NULL) { // Try to find the hash table entry for the new file name - // Note: If this fails, we leave the MPQ in a corrupt state + // Note: Since we deleted one hash entry, this will always succeed pHash = AllocateHashEntry(ha, pFileEntry); - if(pHash == NULL) - nError = ERROR_FILE_CORRUPT; - } - - // If the archive has HET table, we have to allocate HET table for the file as well - // finding the file - if(ha->pHetTable != NULL) - { - dwFileIndex = AllocateHetEntry(ha, pFileEntry); - if(dwFileIndex == HASH_ENTRY_FREE) - nError = ERROR_FILE_CORRUPT; + assert(pHash != NULL); } // Invalidate the entries for (listfile) and (attributes) // After we are done with MPQ changes, we need to re-create them InvalidateInternalFiles(ha); - return nError; + return ERROR_SUCCESS; } -void ClearFileEntry( +void DeleteFileEntry( TMPQArchive * ha, TFileEntry * pFileEntry) { - TMPQHash * pHash = NULL; + TMPQHash * pHash; // If the MPQ has classic hash table, clear the entry there if(ha->pHashTable != NULL) { - assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); - - pHash = ha->pHashTable + pFileEntry->dwHashIndex; - memset(pHash, 0xFF, sizeof(TMPQHash)); - pHash->dwBlockIndex = HASH_ENTRY_DELETED; - } - - // If the MPQ has HET table, clear the entry there as well - if(ha->pHetTable != NULL) - { - TMPQHetTable * pHetTable = ha->pHetTable; - DWORD dwInvalidFileIndex = (1 << pHetTable->dwIndexSizeTotal) - 1; - - assert(pFileEntry->dwHetIndex < pHetTable->dwHashTableSize); - - // Clear the entry in the HET hash array - pHetTable->pHetHashes[pFileEntry->dwHetIndex] = HET_ENTRY_DELETED; - - // Set the BET index to invalid index - SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * pFileEntry->dwHetIndex, - pHetTable->dwIndexSize, - &dwInvalidFileIndex, - 4); + // Only if the file entry is still an existing one + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + // We expect dwHashIndex to be within the hash table + pHash = ha->pHashTable + pFileEntry->dwHashIndex; + assert(pFileEntry->dwHashIndex < ha->pHeader->dwHashTableSize); + + // Set the hash table entry as deleted + pHash->dwName1 = 0xFFFFFFFF; + pHash->dwName2 = 0xFFFFFFFF; + pHash->lcLocale = 0xFFFF; + pHash->wPlatform = 0xFFFF; + pHash->dwBlockIndex = HASH_ENTRY_DELETED; + } } // Free the file name, and set the file entry as deleted if(pFileEntry->szFileName != NULL) STORM_FREE(pFileEntry->szFileName); + pFileEntry->szFileName = NULL; + + // + // Don't modify the HET table, because it gets recreated from scratch at every modification operation + // Don't decrement the number of entries in the file table + // Keep Byte Offset, file size and compressed size in the file table + // Also keep the CRC32 and MD5 in the file attributes + // Clear the file name hash + // Clear the MPQ_FILE_EXISTS bit. + // - // Invalidate the file entry - memset(pFileEntry, 0, sizeof(TFileEntry)); + pFileEntry->FileNameHash = 0; + pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; } -int FreeFileEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry) +void InvalidateInternalFiles(TMPQArchive * ha) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pTempEntry; - int nError = ERROR_SUCCESS; - - // - // If we have HET table, we cannot just get rid of the file - // Doing so would lead to empty gaps in the HET table - // We have to keep BET hash, hash index, HET index, locale, platform and file name - // + TFileEntry * pFileEntry; + DWORD dwReservedFiles = 0; - if(ha->pHetTable == NULL) + // Do nothing if we are in the middle of saving internal files + if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES)) { - TFileEntry * pLastFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; - TFileEntry * pLastUsedEntry = pLastFileEntry; - - // Zero the file entry - ClearFileEntry(ha, pFileEntry); + // + // We clear the file entries of (listfile) and (attributes) + // For each internal file cleared, we increment the number + // of reserved entries in the file table. + // - // Now there is a chance that we created a chunk of free - // file entries at the end of the file table. We check this - // and eventually free all deleted file entries at the end - for(pTempEntry = ha->pFileTable; pTempEntry < pFileTableEnd; pTempEntry++) + // Invalidate the (listfile), if not done yet + if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)) { - // Is that an occupied file entry? - if(pTempEntry->dwFlags & MPQ_FILE_EXISTS) - pLastUsedEntry = pTempEntry; - } + pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL); + if(pFileEntry != NULL) + { + DeleteFileEntry(ha, pFileEntry); + dwReservedFiles++; + } - // Can we free some entries at the end? - if(pLastUsedEntry < pLastFileEntry) - { - // Fix the size of the file table entry - ha->dwFileTableSize = (DWORD)(pLastUsedEntry - ha->pFileTable) + 1; - ha->pHeader->dwBlockTableSize = ha->dwFileTableSize; + ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID; } - } - else - { - // Note: Deleted entries in Blizzard MPQs version 4.0 - // normally contain valid byte offset and length - pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; - nError = ERROR_SUCCESS; - } - return nError; -} + // Invalidate the (attributes), if not done yet + if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)) + { + pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); + if(pFileEntry != NULL) + { + DeleteFileEntry(ha, pFileEntry); + dwReservedFiles++; + } -void InvalidateInternalFiles(TMPQArchive * ha) -{ - TFileEntry * pFileEntry; + ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID; + } - // - // Note: We set the size of both (listfile) and (attributes) to zero. - // This causes allocating space for newly added files straight over - // (listfile)/(attributes), if these were the last ones in the MPQ - // + // If the internal files are at the end of the file table (they usually are), + // we want to free these 2 entries, so when new files are added, they get + // added to the freed entries and the internal files get added after that + if(ha->dwFileTableSize > 0 && dwReservedFiles != 0) + { + // Go backwards while there are free entries + for(pFileEntry = ha->pFileTable + ha->dwFileTableSize - 1; pFileEntry >= ha->pFileTable; pFileEntry--) + { + // Stop searching if a file is present + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry++; + break; + } + } - // Invalidate the (listfile), if not done yet - if(!(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID)) - { - pFileEntry = GetFileEntryExact(ha, LISTFILE_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0; - ha->dwFlags |= MPQ_FLAG_LISTFILE_INVALID; - } + // Calculate the new file table size + ha->dwFileTableSize = (DWORD)(pFileEntry - ha->pFileTable); + ha->dwReservedFiles = dwReservedFiles; + } - // Invalidate the (attributes), if not done yet - if(!(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_INVALID)) - { - pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - pFileEntry->dwFileSize = pFileEntry->dwCmpSize = 0; - ha->dwFlags |= MPQ_FLAG_ATTRIBUTES_INVALID; + // Remember that the MPQ has been changed and it will be necessary + // to update the tables + ha->dwFlags |= MPQ_FLAG_CHANGED; } - - // Remember that the MPQ has been changed and it will be necessary - // to update the tables - ha->dwFlags |= MPQ_FLAG_CHANGED; } //----------------------------------------------------------------------------- @@ -1853,7 +1939,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl ULONGLONG BitmapOffset; ULONGLONG EndOfMpq; DWORD DataBlockCount = 0; - DWORD BitmapByteSize; + DWORD BitmapByteSize = 0; DWORD WholeByteCount; DWORD ExtraBitsCount; @@ -1865,7 +1951,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl { // Calculate the number of extra bytes for data bitmap DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / ha->pHeader->dwRawChunkSize) + 1); - BitmapByteSize = ((DataBlockCount - 1) / 8) + 1; + BitmapByteSize = ((DataBlockCount + 7) / 8); BitmapOffset = EndOfMpq + BitmapByteSize; // Try to load the data bitmap from the end of the file @@ -1876,7 +1962,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE) { // Several sanity checks to ensure integrity of the bitmap - assert(MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); + assert(ha->MpqPos + MAKE_OFFSET64(DataBitmap.dwMapOffsetHi, DataBitmap.dwMapOffsetLo) == EndOfMpq); assert(ha->pHeader->dwRawChunkSize == DataBitmap.dwBlockSize); // Allocate space for the data bitmap @@ -1928,6 +2014,7 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsCompl *pbFileIsComplete = bFileIsComplete; } + ha->dwBitmapSize = sizeof(TMPQBitmap) + BitmapByteSize; ha->pBitmap = pBitmap; return ERROR_SUCCESS; } @@ -1943,6 +2030,10 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) assert((dwHashTableSize & (dwHashTableSize - 1)) == 0); assert(ha->pHashTable == NULL); + // If the required hash table size is zero, don't create anything + if(dwHashTableSize == 0) + dwHashTableSize = HASH_TABLE_SIZE_DEFAULT; + // Create the hash table pHashTable = STORM_ALLOC(TMPQHash, dwHashTableSize); if(pHashTable == NULL) @@ -1950,11 +2041,8 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) // Fill it memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash)); + ha->dwMaxFileCount = dwHashTableSize; ha->pHashTable = pHashTable; - - // Set the max file count, if needed - if(ha->pHetTable == NULL) - ha->dwMaxFileCount = dwHashTableSize; return ERROR_SUCCESS; } @@ -2004,53 +2092,124 @@ TMPQHash * LoadHashTable(TMPQArchive * ha) break; } - // Calculate mask value that will serve for calculating - // the hash table index from the MPQ_HASH_TABLE_INDEX hash - ha->dwHashIndexMask = ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0; - // Return the hash table return pHashTable; } +// This function fixes the scenario then dwBlockTableSize +// is greater and goes into a MPQ file static void FixBlockTableSize( TMPQArchive * ha, - TMPQBlock * pBlockTable, - DWORD dwClaimedSize) + TMPQBlock * pBlockTable) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG BlockTableStart; ULONGLONG BlockTableEnd; ULONGLONG FileDataStart; + DWORD dwFixedTableSize = pHeader->dwBlockTableSize; + + // Only perform this check on MPQs version 1.0 + assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1); + + // Calculate claimed block table begin and end + BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + + for(DWORD i = 0; i < dwFixedTableSize; i++) + { + // If the block table end goes into that file, fix the block table end + FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; + if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) + { + dwFixedTableSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); + BlockTableEnd = FileDataStart; + ha->dwFlags |= MPQ_FLAG_PROTECTED; + } + } + + // If we found a mismatch in the block table size + if(dwFixedTableSize < pHeader->dwBlockTableSize) + { + // Fill the additional space with zeros + memset(pBlockTable + dwFixedTableSize, 0, (pHeader->dwBlockTableSize - dwFixedTableSize) * sizeof(TMPQBlock)); + + // Fix the block table size + pHeader->dwBlockTableSize = dwFixedTableSize; + pHeader->BlockTableSize64 = dwFixedTableSize * sizeof(TMPQBlock); + + // + // Note: We should recalculate the archive size in the header, + // (it might be invalid as well) but we don't care about it anyway + // + + // In theory, a MPQ could have bigger block table than hash table + assert(pHeader->dwBlockTableSize <= ha->dwMaxFileCount); + } +} + +// This function fixes the scenario then dwBlockTableSize +// is greater and goes into a MPQ file +static void FixCompressedFileSize( + TMPQArchive * ha, + TMPQBlock * pBlockTable) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPQBlock ** SortTable; + TMPQBlock * pBlockTableEnd = pBlockTable + pHeader->dwBlockTableSize; + TMPQBlock * pBlock; + size_t nElements = 0; + size_t nIndex; + DWORD dwArchiveSize; // Only perform this check on MPQs version 1.0 - if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + assert(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1); + + // Allocate sort table for all entries + SortTable = STORM_ALLOC(TMPQBlock*, pHeader->dwBlockTableSize); + if(SortTable != NULL) { - // Calculate claimed block table begin and end - BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + // Calculate the end of the archive + dwArchiveSize = GetArchiveSize32(ha, pBlockTable, pHeader->dwBlockTableSize); + + // Put all blocks to a sort table + for(pBlock = pBlockTable; pBlock < pBlockTableEnd; pBlock++) + { + if(pBlock->dwFlags & MPQ_FILE_EXISTS) + SortTable[nElements++] = pBlock; + } - for(DWORD i = 0; i < dwClaimedSize; i++) + // Have we found at least one compressed + if(nElements > 0) { - // If the block table end goes into that file, fix the block table end - FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; - if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) + // Sort the table + qsort(SortTable, nElements, sizeof(TMPQBlock *), CompareFilePositions); + + // Walk the table and set all compressed sizes to they + // match the difference (dwFilePos1 - dwFilePos0) + for(nIndex = 0; nIndex < nElements - 1; nIndex++) { - dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); - BlockTableEnd = FileDataStart; + TMPQBlock * pBlock1 = SortTable[nIndex + 1]; + TMPQBlock * pBlock0 = SortTable[nIndex]; + + pBlock0->dwCSize = (pBlock->dwFlags & MPQ_FILE_COMPRESS_MASK) ? (pBlock1->dwFilePos - pBlock0->dwFilePos) : pBlock0->dwFSize; } + + // Fix the last entry + pBlock = SortTable[nElements - 1]; + pBlock->dwCSize = dwArchiveSize - pBlock->dwFilePos; } - } - // Fix the block table size - pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); - pHeader->dwBlockTableSize = dwClaimedSize; + STORM_FREE(SortTable); + } } -TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) + +TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries) { TMPQHeader * pHeader = ha->pHeader; TMPQBlock * pBlockTable = NULL; ULONGLONG ByteOffset; + ULONGLONG FileSize; DWORD dwTableSize; DWORD dwCmpSize; @@ -2067,22 +2226,20 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) { case MPQ_SUBTYPE_MPQ: - // Sanity check, enforced by LoadAnyHashTable - assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); - // Calculate sizes of both tables ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); dwCmpSize = (DWORD)pHeader->BlockTableSize64; - // I found a MPQ which claimed 0x200 entries in the block table, - // but the file was cut and there was only 0x1A0 entries. - // We will handle this case properly. + // Handle case when either the MPQ is cut in the middle of the block table + // or the MPQ is malformed so that block table size is greater than should be + FileStream_GetSize(ha->pStream, &FileSize); if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize) { - pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock)); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + pHeader->BlockTableSize64 = FileSize - ByteOffset; + pHeader->dwBlockTableSize = (DWORD)(pHeader->BlockTableSize64 / sizeof(TMPQBlock)); + dwTableSize = dwCmpSize = (DWORD)pHeader->BlockTableSize64; + ha->dwFlags |= MPQ_FLAG_PROTECTED; } // @@ -2094,9 +2251,17 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) // If failed to load the block table, delete it pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - // Defense against MPQs that claim block table to be bigger than it really is - if(pBlockTable != NULL) - FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); + // De-protecting maps for Warcraft III + if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) + { + // Some protectors set the block table size bigger than really is + if(pBlockTable != NULL) + FixBlockTableSize(ha, pBlockTable); + + // Defense against protectors that set invalid compressed size + if((ha->dwFlags & MPQ_FLAG_PROTECTED) && (bDontFixEntries == false)) + FixCompressedFileSize(ha, pBlockTable); + } break; case MPQ_SUBTYPE_SQP: @@ -2115,8 +2280,8 @@ TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) TMPQHetTable * LoadHetTable(TMPQArchive * ha) { + TMPQExtHeader * pExtTable; TMPQHetTable * pHetTable = NULL; - TMPQExtTable * pExtTable; TMPQHeader * pHeader = ha->pHeader; // If the HET table position is not NULL, we expect @@ -2128,7 +2293,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) if(pExtTable != NULL) { // If loading HET table fails, we ignore the result. - pHetTable = TranslateHetTable(pExtTable); + pHetTable = TranslateHetTable((TMPQHetHeader *)pExtTable); STORM_FREE(pExtTable); } } @@ -2138,7 +2303,7 @@ TMPQHetTable * LoadHetTable(TMPQArchive * ha) TMPQBetTable * LoadBetTable(TMPQArchive * ha) { - TMPQExtTable * pExtTable; + TMPQExtHeader * pExtTable; TMPQBetTable * pBetTable = NULL; TMPQHeader * pHeader = ha->pHeader; @@ -2152,7 +2317,7 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha) { // If succeeded, we translate the BET table // to more readable form - pBetTable = TranslateBetTable(ha, pExtTable); + pBetTable = TranslateBetTable(ha, (TMPQBetHeader *)pExtTable); STORM_FREE(pExtTable); } } @@ -2174,26 +2339,16 @@ int LoadAnyHashTable(TMPQArchive * ha) if(ha->pHetTable == NULL && ha->pHashTable == NULL) return ERROR_FILE_CORRUPT; - // Set the maximum file count - if(ha->pHetTable != NULL && ha->pHashTable != NULL) - ha->dwMaxFileCount = STORMLIB_MIN(ha->pHetTable->dwHashTableSize, pHeader->dwHashTableSize); - else - ha->dwMaxFileCount = (ha->pHetTable != NULL) ? ha->pHetTable->dwHashTableSize : pHeader->dwHashTableSize; - - // In theory, a MPQ could have bigger block table than hash table - if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount) - { - ha->dwMaxFileCount = ha->pHeader->dwBlockTableSize; - ha->dwFlags |= MPQ_FLAG_READ_ONLY; - } - + // Set the maximum file count to the size of the hash table. + // Note: We don't care about HET table limits, because HET table is rebuilt + // after each file add/rename/delete. + ha->dwMaxFileCount = (ha->pHashTable != NULL) ? pHeader->dwHashTableSize : HASH_TABLE_SIZE_MAX; return ERROR_SUCCESS; } int BuildFileTable_Classic( TMPQArchive * ha, - TFileEntry * pFileTable, - ULONGLONG FileSize) + TFileEntry * pFileTable) { TFileEntry * pFileEntry; TMPQHeader * pHeader = ha->pHeader; @@ -2203,33 +2358,23 @@ int BuildFileTable_Classic( // Sanity checks assert(ha->pHashTable != NULL); + // If the MPQ has no block table, do nothing + if(ha->pHeader->dwBlockTableSize == 0) + return ERROR_SUCCESS; + // Load the block table - pBlockTable = (TMPQBlock *)LoadBlockTable(ha, FileSize); + pBlockTable = (TMPQBlock *)LoadBlockTable(ha); if(pBlockTable != NULL) { - TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; - TMPQHash * pHash; - // If we don't have HET table, we build the file entries from the hash&block tables + // If we do, we only update it from the hash table if(ha->pHetTable == NULL) { - nError = ConvertMpqBlockTable(ha, pFileTable, pBlockTable); + nError = BuildFileTableFromBlockTable(ha, pFileTable, pBlockTable); } else { - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) - { - if(pHash->dwBlockIndex < ha->dwFileTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - } - } + nError = UpdateFileTableFromHashTable(ha, pFileTable); } // Free the block table @@ -2260,13 +2405,13 @@ int BuildFileTable_Classic( // Now merge the hi-block table to the file table if(nError == ERROR_SUCCESS) { + BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, dwTableSize); pFileEntry = pFileTable; // Add the high file offset to the base file offset. - // We also need to swap it during the process. for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++) { - pFileEntry->ByteOffset |= ((ULONGLONG)BSWAP_INT16_UNSIGNED(pHiBlockTable[i]) << 32); + pFileEntry->ByteOffset = MAKE_OFFSET64(pHiBlockTable[i], pFileEntry->ByteOffset); pFileEntry++; } } @@ -2301,13 +2446,18 @@ int BuildFileTable_HetBet( pBetTable = LoadBetTable(ha); if(pBetTable != NULL) { - // Step one: Fill the indexes to the HET table - for(i = 0; i < pHetTable->dwHashTableSize; i++) + // Verify the size of NameHash2 in the BET table. + // It has to be 8 bits less than the information in HET table + if((pBetTable->dwBitCount_NameHash2 + 8) != pHetTable->dwNameHashBitSize) + return ERROR_FILE_CORRUPT; + + // Step one: Fill the name indexes + for(i = 0; i < pHetTable->dwEntryCount; i++) { DWORD dwFileIndex = 0; // Is the entry in the HET table occupied? - if(pHetTable->pHetHashes[i] != 0) + if(pHetTable->pNameHashes[i] != 0) { // Load the index to the BET table GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i, @@ -2315,17 +2465,20 @@ int BuildFileTable_HetBet( &dwFileIndex, 4); // Overflow test - if(dwFileIndex < pBetTable->dwFileCount) + if(dwFileIndex < pBetTable->dwEntryCount) { - // Get the file entry and save HET index - pFileEntry = pFileTable + dwFileIndex; - pFileEntry->dwHetIndex = i; + ULONGLONG NameHash1 = pHetTable->pNameHashes[i]; + ULONGLONG NameHash2 = 0; // Load the BET hash - GetBits(pBetTable->pBetHashes, pBetTable->dwBetHashSizeTotal * dwFileIndex, - pBetTable->dwBetHashSize, - &pFileEntry->BetHash, - 8); + GetBits(pBetTable->pNameHashes, pBetTable->dwBitTotal_NameHash2 * dwFileIndex, + pBetTable->dwBitCount_NameHash2, + &NameHash2, + 8); + + // Combine both part of the name hash and put it to the file table + pFileEntry = pFileTable + dwFileIndex; + pFileEntry->FileNameHash = (NameHash1 << pBetTable->dwBitCount_NameHash2) | NameHash2; } } } @@ -2333,7 +2486,7 @@ int BuildFileTable_HetBet( // Go through the entire BET table and convert it to the file table. pFileEntry = pFileTable; pBitArray = pBetTable->pFileTable; - for(i = 0; i < pBetTable->dwFileCount; i++) + for(i = 0; i < pBetTable->dwEntryCount; i++) { DWORD dwFlagIndex = 0; @@ -2363,7 +2516,6 @@ int BuildFileTable_HetBet( pBetTable->dwBitCount_FlagIndex, &dwFlagIndex, 4); - pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex]; } @@ -2377,7 +2529,7 @@ int BuildFileTable_HetBet( } // Set the current size of the file table - ha->dwFileTableSize = pBetTable->dwFileCount; + ha->dwFileTableSize = pBetTable->dwEntryCount; FreeBetTable(pBetTable); nError = ERROR_SUCCESS; } @@ -2389,7 +2541,7 @@ int BuildFileTable_HetBet( return nError; } -int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) +int BuildFileTable(TMPQArchive * ha) { TFileEntry * pFileTable; bool bFileTableCreated = false; @@ -2420,7 +2572,7 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) // Note: If block table is corrupt or missing, we set the archive as read only if(ha->pHashTable != NULL) { - if(BuildFileTable_Classic(ha, pFileTable, FileSize) != ERROR_SUCCESS) + if(BuildFileTable_Classic(ha, pFileTable) != ERROR_SUCCESS) ha->dwFlags |= MPQ_FLAG_READ_ONLY; else bFileTableCreated = true; @@ -2438,11 +2590,128 @@ int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize) return ERROR_SUCCESS; } +// Rebuilds the HET table from scratch based on the file table +// Used after a modifying operation (add, rename, delete) +int RebuildHetTable(TMPQArchive * ha) +{ + TMPQHetTable * pOldHetTable = ha->pHetTable; + ULONGLONG FileNameHash; + DWORD i; + int nError = ERROR_SUCCESS; + + // Create new HET table based on the total number of entries in the file table + // Note that if we fail to create it, we just stop using HET table + ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL); + if(ha->pHetTable != NULL) + { + // Go through the file table again and insert all existing files + for(i = 0; i < ha->dwFileTableSize; i++) + { + if(ha->pFileTable[i].dwFlags & MPQ_FILE_EXISTS) + { + // Get the high + FileNameHash = ha->pFileTable[i].FileNameHash; + nError = InsertHetEntry(ha->pHetTable, FileNameHash, i); + if(nError != ERROR_SUCCESS) + break; + } + } + } + + // Free the old HET table + FreeHetTable(pOldHetTable); + return nError; +} + +// Rebuilds the file table, removing all deleted file entries. +// Used when compacting the archive +int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount) +{ + TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + TFileEntry * pOldFileTable = ha->pFileTable; + TFileEntry * pFileTable; + TFileEntry * pFileEntry; + TFileEntry * pOldEntry; + TMPQHash * pOldHashTable = ha->pHashTable; + TMPQHash * pHashTable = NULL; + TMPQHash * pHash; + int nError = ERROR_SUCCESS; + + // The new hash table size must be greater or equal to the current hash table size + assert(dwNewHashTableSize >= ha->pHeader->dwHashTableSize); + assert(dwNewMaxFileCount >= ha->dwFileTableSize); + + // The new hash table size must be a power of two + assert((dwNewHashTableSize & (dwNewHashTableSize - 1)) == 0); + + // Allocate the new file table + pFileTable = pFileEntry = STORM_ALLOC(TFileEntry, dwNewMaxFileCount); + if(pFileTable == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; + + // Allocate new hash table + if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) + { + pHashTable = STORM_ALLOC(TMPQHash, dwNewHashTableSize); + if(pHashTable == NULL) + nError = ERROR_NOT_ENOUGH_MEMORY; + } + + // If both succeeded, we need to rebuild the file table + if(nError == ERROR_SUCCESS) + { + // Make sure that the hash table is properly filled + memset(pFileTable, 0x00, sizeof(TFileEntry) * dwNewMaxFileCount); + memset(pHashTable, 0xFF, sizeof(TMPQHash) * dwNewHashTableSize); + + // Set the new tables to the MPQ archive + ha->pFileTable = pFileTable; + ha->pHashTable = pHashTable; + + // Set the new limits to the MPQ archive + ha->pHeader->dwHashTableSize = dwNewHashTableSize; + ha->dwMaxFileCount = dwNewMaxFileCount; + + // Now copy all the file entries + for(pOldEntry = pOldFileTable; pOldEntry < pOldFileTableEnd; pOldEntry++) + { + // If the file entry exists, we copy it to the new table + // Otherwise, we skip it + if(pOldEntry->dwFlags & MPQ_FILE_EXISTS) + { + // Copy the file entry + *pFileEntry = *pOldEntry; + + // Create hash entry for it + if(ha->pHashTable != NULL) + { + pHash = AllocateHashEntry(ha, pFileEntry); + assert(pHash != NULL); + } + + // Move the file entry by one + *pFileEntry++; + } + } + + // Update the file table size + ha->dwFileTableSize = (DWORD)(pFileEntry - pFileTable); + ha->dwFlags |= MPQ_FLAG_CHANGED; + } + + // Now free the remaining entries + if(pOldFileTable != NULL) + STORM_FREE(pOldFileTable); + if(pOldHashTable != NULL) + STORM_FREE(pOldHashTable); + return nError; +} + // Saves MPQ header, hash table, block table and hi-block table. int SaveMPQTables(TMPQArchive * ha) { - TMPQExtTable * pHetTable = NULL; - TMPQExtTable * pBetTable = NULL; + TMPQExtHeader * pHetTable = NULL; + TMPQExtHeader * pBetTable = NULL; TMPQHeader * pHeader = ha->pHeader; TMPQBlock * pBlockTable = NULL; TMPQHash * pHashTable = NULL; @@ -2488,7 +2757,7 @@ int SaveMPQTables(TMPQArchive * ha) } // Create block table - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) + if(nError == ERROR_SUCCESS && ha->pFileTable != NULL) { pBlockTable = TranslateBlockTable(ha, &BlockTableSize64, &bNeedHiBlockTable); if(pBlockTable == NULL) @@ -2581,6 +2850,7 @@ int SaveMPQTables(TMPQArchive * ha) // Write the MPQ header to the file memcpy(&SaveMpqHeader, pHeader, pHeader->dwHeaderSize); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize)) diff --git a/src/SBaseSubTypes.cpp b/src/SBaseSubTypes.cpp index 5c78004..0aae9f5 100644 --- a/src/SBaseSubTypes.cpp +++ b/src/SBaseSubTypes.cpp @@ -439,6 +439,42 @@ int ConvertMpkHeaderToFormat4( return ERROR_FILE_CORRUPT; } +// Attempts to search a free hash entry in the hash table being converted. +// The created hash table must always be of nonzero size, +// should have no duplicated items and no deleted entries +TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2) +{ + TMPQHash * pHash; + DWORD dwIndex; + + // Set the initial index + dwStartIndex = dwIndex = (dwStartIndex & (dwHashTableSize - 1)); + assert(dwHashTableSize != 0); + + // Search the hash table and return the found entries in the following priority: + for(;;) + { + // We are not expecting to find matching entry in the hash table being built + // We are not expecting to find deleted entry either + pHash = pHashTable + dwIndex; + assert(pHash->dwName1 != dwName1 || pHash->dwName2 != dwName2); + + // If we found a free entry, we need to stop searching + if(pHash->dwBlockIndex == HASH_ENTRY_FREE) + return pHash; + + // Move to the next hash entry. + // If we reached the starting entry, it's failure. + dwIndex = (dwIndex + 1) & (dwHashTableSize - 1); + if(dwIndex == dwStartIndex) + break; + } + + // We haven't found anything + assert(false); + return NULL; +} + void DecryptMpkTable(void * pvMpkTable, size_t cbSize) { LPBYTE pbMpkTable = (LPBYTE)pvMpkTable; @@ -477,6 +513,7 @@ void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize) TMPQHash * LoadMpkHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; + TMPQHash * pHashTable = NULL; TMPKHash * pMpkHash; TMPQHash * pHash = NULL; DWORD dwHashTableSize = pHeader->dwHashTableSize; @@ -484,7 +521,7 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) // MPKs use different hash table searching. // Instead of using MPQ_HASH_TABLE_INDEX hash as index, // they store the value directly in the hash table. - // Also, for faster searching, the hash table is sorted ascending by the value + // Also for faster searching, the hash table is sorted ascending by the value // Load and decrypt the MPK hash table. pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash)); @@ -493,25 +530,21 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) // Calculate the hash table size as if it was real MPQ hash table pHeader->dwHashTableSize = GetHashTableSizeForFileCount(dwHashTableSize); pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash); - ha->dwHashIndexMask = pHeader->dwHashTableSize ? (pHeader->dwHashTableSize - 1) : 0; // Now allocate table that will serve like a true MPQ hash table, // so we translate the MPK hash table to MPQ hash table - ha->pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); - if(ha->pHashTable != NULL) + pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); + if(pHashTable != NULL) { // Set the entire hash table to free - memset(ha->pHashTable, 0xFF, (size_t)pHeader->HashTableSize64); + memset(pHashTable, 0xFF, (size_t)pHeader->HashTableSize64); // Copy the MPK hash table into MPQ hash table for(DWORD i = 0; i < dwHashTableSize; i++) { // Finds the free hash entry in the hash table - pHash = FindFreeHashEntry(ha, pMpkHash[i].dwName1, pMpkHash[i].dwName2, pMpkHash[i].dwName3, 0); - if(pHash == NULL) - break; - - // Sanity check + // We don;t expect any errors here, because we are putting files to empty hash table + pHash = FindFreeHashEntry(pHashTable, pHeader->dwHashTableSize, pMpkHash[i].dwName1, pMpkHash[i].dwName2, pMpkHash[i].dwName3); assert(pHash->dwBlockIndex == HASH_ENTRY_FREE); // Copy the MPK hash entry to the hash table @@ -521,21 +554,13 @@ TMPQHash * LoadMpkHashTable(TMPQArchive * ha) pHash->dwName1 = pMpkHash[i].dwName2; pHash->dwName2 = pMpkHash[i].dwName3; } - - // If an error was found during conversion, - // free the hash table - if(pHash == NULL) - { - STORM_FREE(ha->pHashTable); - ha->pHashTable = NULL; - } } // Free the temporary hash table STORM_FREE(pMpkHash); } - return ha->pHashTable; + return pHashTable; } static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags) diff --git a/src/SCompression.cpp b/src/SCompression.cpp index 933dc61..5402771 100644 --- a/src/SCompression.cpp +++ b/src/SCompression.cpp @@ -425,7 +425,7 @@ static void LZMA_Callback_Free(void *p, void *address) // the data compressed by StormLib. // -/*static */ void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) +static void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel) { ICompressProgress Progress; CLzmaEncProps props; @@ -1076,6 +1076,16 @@ int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInB // is not supported by newer MPQs. // + case (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN): + pfnDecompress1 = Decompress_huff; + pfnDecompress2 = Decompress_ADPCM_mono; + break; + + case (MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN): + pfnDecompress1 = Decompress_huff; + pfnDecompress2 = Decompress_ADPCM_stereo; + break; + default: SetLastError(ERROR_FILE_CORRUPT); return 0; diff --git a/src/SFileAddFile.cpp b/src/SFileAddFile.cpp index 775a969..bb8f4f1 100644 --- a/src/SFileAddFile.cpp +++ b/src/SFileAddFile.cpp @@ -85,23 +85,17 @@ static int WriteDataToMpqFile( { TFileEntry * pFileEntry = hf->pFileEntry; ULONGLONG ByteOffset; - LPBYTE pbCompressed = NULL; // Compressed (target) data - LPBYTE pbToWrite = NULL; // Data to write to the file - int nCompressionLevel = -1; // ADPCM compression level (only used for wave files) + LPBYTE pbCompressed = NULL; // Compressed (target) data + LPBYTE pbToWrite = hf->pbFileSector; // Data to write to the file + int nCompressionLevel; // ADPCM compression level (only used for wave files) int nError = ERROR_SUCCESS; - // If the caller wants ADPCM compression, we will set wave compression level to 4, - // which corresponds to medium quality - if(dwCompression & LOSSY_COMPRESSION_MASK) - nCompressionLevel = 4; - // Make sure that the caller won't overrun the previously initiated file size assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize); assert(hf->dwSectorCount != 0); assert(hf->pbFileSector != NULL); if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize) return ERROR_DISK_FULL; - pbToWrite = hf->pbFileSector; // Now write all data to the file sector buffer if(nError == ERROR_SUCCESS) @@ -159,8 +153,8 @@ static int WriteDataToMpqFile( } // - // Note that both SCompImplode and SCompCompress give original buffer, - // if they are unable to comperss the data. + // Note that both SCompImplode and SCompCompress copy data as-is, + // if they are unable to compress the data. // if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) @@ -170,6 +164,21 @@ static int WriteDataToMpqFile( if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { + // If this is the first sector, we need to override the given compression + // by the first sector compression. This is because the entire sector must + // be compressed by the same compression. + // + // Test case: + // + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_PKWARE) // Write 0x10 bytes (sector 0) + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) + // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) + dwCompression = (dwSectorIndex == 0) ? hf->dwCompression0 : dwCompression; + + // If the caller wants ADPCM compression, we will set wave compression level to 4, + // which corresponds to medium quality + nCompressionLevel = (dwCompression & LOSSY_COMPRESSION_MASK) ? 4 : -1; SCompCompress(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel); } @@ -236,8 +245,8 @@ static int RecryptFileData( assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED); // File decryption key is calculated from the plain name - szNewFileName = GetPlainFileNameA(szNewFileName); - szFileName = GetPlainFileNameA(szFileName); + szNewFileName = GetPlainFileName(szNewFileName); + szFileName = GetPlainFileName(szFileName); // Calculate both file keys dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); @@ -382,7 +391,7 @@ int SFileAddFile_Init( if(hf == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; - // Find a free space in the MPQ, as well as free block table entry + // Find a free space in the MPQ and verify if it's not over 4 GB on MPQs v1 if(nError == ERROR_SUCCESS) { // Find the position where the file will be stored @@ -390,9 +399,6 @@ int SFileAddFile_Init( hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; hf->bIsWriteHandle = true; - // Sanity check: The MPQ must be marked as changed at this point - assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0); - // When format V1, the size of the archive cannot exceed 4 GB if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) { @@ -418,38 +424,26 @@ int SFileAddFile_Init( } else { - // Only if the file really exists - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // If the file exists and "replace existing" is not set, fail it - if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0) - nError = ERROR_ALREADY_EXISTS; - - // If the file entry already contains a file - // and it is a pseudo-name, replace it - if(nError == ERROR_SUCCESS) - { - AllocateFileName(pFileEntry, szFileName); - } - } + // If the caller didn't set MPQ_FILE_REPLACEEXISTING, fail it + if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0) + nError = ERROR_ALREADY_EXISTS; } } - // - // At this point, the file name in file entry must be non-NULL - // - - // Create key for file encryption - if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED)) - { - hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags); - } - + // Fill the file entry and TMPQFile structure if(nError == ERROR_SUCCESS) { + // At this point, the file name in the file entry must be set + assert(pFileEntry->szFileName != NULL); + assert(_stricmp(pFileEntry->szFileName, szFileName) == 0); + // Initialize the hash entry for the file hf->pFileEntry = pFileEntry; hf->dwDataSize = dwFileSize; + + // Decrypt the file key + if(dwFlags & MPQ_FILE_ENCRYPTED) + hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags); // Initialize the block table entry for the file pFileEntry->ByteOffset = hf->MpqFilePos; @@ -467,14 +461,17 @@ int SFileAddFile_Init( // If the caller gave us a file time, use it. pFileEntry->FileTime = FileTime; + // Mark the archive as modified + ha->dwFlags |= MPQ_FLAG_CHANGED; + // Call the callback, if needed if(ha->pfnAddFileCB != NULL) ha->pfnAddFileCB(ha->pvAddFileUserData, 0, hf->dwDataSize, false); } - // If an error occured, remember it - if(nError != ERROR_SUCCESS && hf != NULL) - hf->bErrorOccured = true; + // Store the error code from Add File operation + if(hf != NULL) + hf->nAddFileError = nError; *phf = hf; return nError; } @@ -499,12 +496,9 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d ULONGLONG RawFilePos = hf->RawFilePos; // Allocate buffer for file sector - nError = AllocateSectorBuffer(hf); + hf->nAddFileError = nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } // Allocate patch info, if the data is patch if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize)) @@ -513,34 +507,25 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE; // Allocate the patch info - nError = AllocatePatchInfo(hf, false); + hf->nAddFileError = nError = AllocatePatchInfo(hf, false); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } } // Allocate sector offsets if(hf->SectorOffsets == NULL) { - nError = AllocateSectorOffsets(hf, false); + hf->nAddFileError = nError = AllocateSectorOffsets(hf, false); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } } // Create array of sector checksums if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)) { - nError = AllocateSectorChecksums(hf, false); + hf->nAddFileError = nError = AllocateSectorChecksums(hf, false); if(nError != ERROR_SUCCESS) - { - hf->bErrorOccured = true; return nError; - } } // Pre-save the patch info, if any @@ -568,7 +553,16 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d // Write the MPQ data to the file if(nError == ERROR_SUCCESS) + { + // Save the first sector compression to the file structure + // Note that the entire first file sector will be compressed + // by compression that was passed to the first call of SFileAddFile_Write + if(hf->dwFilePos == 0) + hf->dwCompression0 = dwCompression; + + // Write the data to the MPQ nError = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression); + } // If it succeeded and we wrote all the file data, // we need to re-save sector offset table @@ -586,8 +580,6 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d if(hf->SectorChksums != NULL) { nError = WriteSectorChecksums(hf); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } // Now write patch info @@ -597,16 +589,12 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d hf->pPatchInfo->dwDataSize = hf->pFileEntry->dwFileSize; hf->pFileEntry->dwFileSize = hf->dwPatchedFileSize; nError = WritePatchInfo(hf); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } // Now write sector offsets to the file if(hf->SectorOffsets != NULL) { nError = WriteSectorOffsets(hf); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } // Write the MD5 hashes of each file chunk, if required @@ -616,16 +604,12 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d ha->MpqPos + hf->pFileEntry->ByteOffset, hf->pFileEntry->dwCmpSize, ha->pHeader->dwRawChunkSize); - if(nError != ERROR_SUCCESS) - hf->bErrorOccured = true; } } } - else - { - hf->bErrorOccured = true; - } + // Store the error code from the Write File operation + hf->nAddFileError = nError; return nError; } @@ -633,45 +617,43 @@ int SFileAddFile_Finish(TMPQFile * hf) { TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; - int nError = ERROR_SUCCESS; + int nError = hf->nAddFileError; // If all previous operations succeeded, we can update the MPQ - if(!hf->bErrorOccured) + if(nError == ERROR_SUCCESS) { // Verify if the caller wrote the file properly if(hf->pPatchInfo == NULL) { assert(pFileEntry != NULL); if(hf->dwFilePos != pFileEntry->dwFileSize) - { nError = ERROR_CAN_NOT_COMPLETE; - hf->bErrorOccured = true; - } } else { if(hf->dwFilePos != hf->pPatchInfo->dwDataSize) - { nError = ERROR_CAN_NOT_COMPLETE; - hf->bErrorOccured = true; - } } } - if(!hf->bErrorOccured) + // Now we need to recreate the HET table, if exists + if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) + { + nError = RebuildHetTable(ha); + } + + // Update the block table size + if(nError == ERROR_SUCCESS) { // Call the user callback, if any if(ha->pfnAddFileCB != NULL) ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwDataSize, hf->dwDataSize, true); - - // Update the size of the block table - ha->pHeader->dwBlockTableSize = ha->dwFileTableSize; } else { // Free the file entry in MPQ tables if(pFileEntry != NULL) - FreeFileEntry(ha, pFileEntry); + DeleteFileEntry(ha, pFileEntry); } // Clear the add file callback @@ -695,7 +677,7 @@ bool WINAPI SFileCreateFile( int nError = ERROR_SUCCESS; // Check valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szArchivedName == NULL || *szArchivedName == 0) nError = ERROR_INVALID_PARAMETER; @@ -725,16 +707,9 @@ bool WINAPI SFileCreateFile( nError = ERROR_INVALID_PARAMETER; } - // Create the file in MPQ + // Initiate the add file operation if(nError == ERROR_SUCCESS) - { - // Invalidate the entries for (listfile) and (attributes) - // After we are done with MPQ changes, we need to re-create them anyway - InvalidateInternalFiles(ha); - - // Initiate the add file operation nError = SFileAddFile_Init(ha, szArchivedName, FileTime, dwFileSize, lcLocale, dwFlags, (TMPQFile **)phFile); - } // Deal with the errors if(nError != ERROR_SUCCESS) @@ -752,7 +727,7 @@ bool WINAPI SFileWriteFile( int nError = ERROR_SUCCESS; // Check the proper parameters - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) nError = ERROR_INVALID_HANDLE; if(hf->bIsWriteHandle == false) nError = ERROR_INVALID_HANDLE; @@ -794,7 +769,7 @@ bool WINAPI SFileFinishFile(HANDLE hFile) int nError = ERROR_SUCCESS; // Check the proper parameters - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) nError = ERROR_INVALID_HANDLE; if(hf->bIsWriteHandle == false) nError = ERROR_INVALID_HANDLE; @@ -881,6 +856,8 @@ bool WINAPI SFileAddFileEx( if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) dwCompression = MPQ_COMPRESSION_PKWARE; + // Remove both flag mono and stereo flags. + // They will be re-added according to WAVE type dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO); bIsAdpcmCompression = true; } @@ -908,7 +885,7 @@ bool WINAPI SFileAddFileEx( // If the file being added is a WAVE file, we check number of channels if(bIsFirstSector && bIsAdpcmCompression) { - // The file must really be a wave file, otherwise it's data corruption + // The file must really be a wave file, otherwise it will corrupt the file if(!IsWaveFile(pbFileData, dwBytesToRead, &dwChannels)) { nError = ERROR_BAD_FORMAT; @@ -1024,7 +1001,7 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch // Check the parameters if(nError == ERROR_SUCCESS) { - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) nError = ERROR_INVALID_PARAMETER; @@ -1067,8 +1044,12 @@ bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearch // After we are done with MPQ changes, we need to re-create them anyway InvalidateInternalFiles(ha); - // Mark the file entry as free - nError = FreeFileEntry(ha, pFileEntry); + // Delete the file entry in the file table and defragment the file table + DeleteFileEntry(ha, pFileEntry); + + // We also need to rebuild the HET table, if present + if(ha->pHetTable != NULL) + nError = RebuildHetTable(ha); } // Resolve error and exit @@ -1089,7 +1070,7 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s // Test the valid parameters if(nError == ERROR_SUCCESS) { - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0) nError = ERROR_INVALID_PARAMETER; @@ -1136,6 +1117,10 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s nError = RenameFileEntry(ha, pFileEntry, szNewFileName); } + // Now we need to recreate the HET table, if we have one + if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) + nError = RebuildHetTable(ha); + // Now we copy the existing file entry to the new one if(nError == ERROR_SUCCESS) { @@ -1170,9 +1155,8 @@ bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * s } } - // // Note: MPQ_FLAG_CHANGED is set by RenameFileEntry - // + // assert((ha->dwFlags & MPQ_FLAG_CHANGED) != 0); // Resolve error and return if(nError != ERROR_SUCCESS) @@ -1207,7 +1191,7 @@ bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale) TMPQFile * hf = (TMPQFile *)hFile; // Invalid handle => do nothing - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return false; @@ -1274,7 +1258,7 @@ bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileC { TMPQArchive * ha = (TMPQArchive *) hMpq; - if (!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_HANDLE); return false; diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp index 995e5b1..bca1f66 100644 --- a/src/SFileAttributes.cpp +++ b/src/SFileAttributes.cpp @@ -27,343 +27,352 @@ typedef struct _MPQ_ATTRIBUTES_HEADER } MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER; //----------------------------------------------------------------------------- -// Public functions (internal use by StormLib) +// Local functions -int SAttrLoadAttributes(TMPQArchive * ha) +static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwFileTableSize) { - MPQ_ATTRIBUTES_HEADER AttrHeader; - HANDLE hFile = NULL; + DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER); + + // Calculate size of the (attributes) file + if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32) + cbAttrFile += dwFileTableSize * sizeof(DWORD); + if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + cbAttrFile += dwFileTableSize * sizeof(ULONGLONG); + if(dwAttrFlags & MPQ_ATTRIBUTE_MD5) + cbAttrFile += dwFileTableSize * MD5_DIGEST_SIZE; + + // Weird: When there's 1 extra bit in the patch bit array, it's ignored + // wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes + if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) + cbAttrFile += (dwFileTableSize + 6) / 8; + + return cbAttrFile; +} + +static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile) +{ + LPBYTE pbAttrFileEnd = pbAttrFile + cbAttrFile; + LPBYTE pbAttrPtr = pbAttrFile; DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize; - DWORD dwArraySize; - DWORD dwBytesRead; DWORD i; - int nError = ERROR_SUCCESS; - // File table must be initialized - assert(ha->pFileTable != NULL); + // Load and verify the header + if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd) + { + PMPQ_ATTRIBUTES_HEADER pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr; + + BSWAP_ARRAY32_UNSIGNED(pAttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER)); + if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1) + return ERROR_BAD_FORMAT; + + // Verify the flags and size of the file + assert((pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL) == 0); + assert(GetSizeOfAttributesFile(pAttrHeader->dwFlags, dwBlockTableSize) == cbAttrFile); + + ha->dwAttrFlags = pAttrHeader->dwFlags; + pbAttrPtr = (LPBYTE)(pAttrHeader + 1); + } - // Attempt to open the "(attributes)" file. - // If it's not there, then the archive doesn't support attributes - if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile)) + // Load the CRC32 (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32) { - // Load the content of the attributes file - SFileReadFile(hFile, &AttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER), &dwBytesRead, NULL); - if(dwBytesRead != sizeof(MPQ_ATTRIBUTES_HEADER)) - nError = ERROR_FILE_CORRUPT; + LPDWORD ArrayCRC32 = (LPDWORD)pbAttrPtr; + DWORD cbArraySize = dwBlockTableSize * sizeof(DWORD); - // Verify the header of the (attributes) file - if(nError == ERROR_SUCCESS) - { - AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(AttrHeader.dwVersion); - AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED(AttrHeader.dwFlags); - ha->dwAttrFlags = AttrHeader.dwFlags; - if(dwBytesRead != sizeof(MPQ_ATTRIBUTES_HEADER)) - nError = ERROR_FILE_CORRUPT; - } + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; - // Verify format of the attributes - if(nError == ERROR_SUCCESS) - { - if(AttrHeader.dwVersion > MPQ_ATTRIBUTES_V1) - nError = ERROR_BAD_FORMAT; - } + BSWAP_ARRAY32_UNSIGNED(ArrayCRC32, cbCRC32Size); + for(i = 0; i < dwBlockTableSize; i++) + ha->pFileTable[i].dwCrc32 = ArrayCRC32[i]; + pbAttrPtr += cbArraySize; + } - // Load the CRC32 (if any) - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_CRC32)) - { - LPDWORD pArrayCRC32 = STORM_ALLOC(DWORD, dwBlockTableSize); + // Load the FILETIME (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + { + ULONGLONG * ArrayFileTime = (ULONGLONG *)pbAttrPtr; + DWORD cbArraySize = dwBlockTableSize * sizeof(ULONGLONG); - if(pArrayCRC32 != NULL) - { - dwArraySize = dwBlockTableSize * sizeof(DWORD); - SFileReadFile(hFile, pArrayCRC32, dwArraySize, &dwBytesRead, NULL); - if(dwBytesRead == dwArraySize) - { - for(i = 0; i < dwBlockTableSize; i++) - ha->pFileTable[i].dwCrc32 = BSWAP_INT32_UNSIGNED(pArrayCRC32[i]); - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pArrayCRC32); - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; - // Read the array of file times - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_FILETIME)) - { - ULONGLONG * pArrayFileTime = STORM_ALLOC(ULONGLONG, dwBlockTableSize); + BSWAP_ARRAY64_UNSIGNED(ArrayFileTime, cbFileTimeSize); + for(i = 0; i < dwBlockTableSize; i++) + ha->pFileTable[i].FileTime = ArrayFileTime[i]; + pbAttrPtr += cbArraySize; + } - if(pArrayFileTime != NULL) - { - dwArraySize = dwBlockTableSize * sizeof(ULONGLONG); - SFileReadFile(hFile, pArrayFileTime, dwArraySize, &dwBytesRead, NULL); - if(dwBytesRead == dwArraySize) - { - for(i = 0; i < dwBlockTableSize; i++) - ha->pFileTable[i].FileTime = BSWAP_INT64_UNSIGNED(pArrayFileTime[i]); - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pArrayFileTime); - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } + // Load the MD5 (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) + { + LPBYTE ArrayMd5 = pbAttrPtr; + DWORD cbArraySize = dwBlockTableSize * MD5_DIGEST_SIZE; - // Read the MD5 (if any) - // Note: MD5 array can be incomplete, if it's the last array in the (attributes) - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_MD5)) - { - unsigned char * pArrayMD5 = STORM_ALLOC(unsigned char, (dwBlockTableSize * MD5_DIGEST_SIZE)); - unsigned char * md5; + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; - if(pArrayMD5 != NULL) - { - dwArraySize = dwBlockTableSize * MD5_DIGEST_SIZE; - SFileReadFile(hFile, pArrayMD5, dwArraySize, &dwBytesRead, NULL); - if(dwBytesRead == dwArraySize) - { - md5 = pArrayMD5; - for(i = 0; i < dwBlockTableSize; i++) - { - memcpy(ha->pFileTable[i].md5, md5, MD5_DIGEST_SIZE); - md5 += MD5_DIGEST_SIZE; - } - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pArrayMD5); - } - else - nError = ERROR_NOT_ENOUGH_MEMORY; + for(i = 0; i < dwBlockTableSize; i++) + { + memcpy(ha->pFileTable[i].md5, ArrayMd5, MD5_DIGEST_SIZE); + ArrayMd5 += MD5_DIGEST_SIZE; } + pbAttrPtr += cbArraySize; + } - // Read the patch bit for each file - if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_PATCH_BIT)) - { - LPBYTE pbBitArray; - DWORD dwByteSize = ((dwBlockTableSize - 1) / 8) + 1; + // Read the patch bit for each file (if present) + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) + { + LPBYTE pbBitArray = pbAttrPtr; + DWORD cbArraySize = (dwBlockTableSize + 7) / 8; + DWORD dwByteIndex = 0; + DWORD dwBitMask = 0x80; - pbBitArray = STORM_ALLOC(BYTE, dwByteSize); - if(pbBitArray != NULL) - { - SFileReadFile(hFile, pbBitArray, dwByteSize, &dwBytesRead, NULL); - if(dwBytesRead == dwByteSize) - { - for(i = 0; i < dwBlockTableSize; i++) - { - DWORD dwByteIndex = i / 8; - DWORD dwBitMask = 0x80 >> (i & 7); - - // Is the appropriate bit set? - if(pbBitArray[dwByteIndex] & dwBitMask) - { - // At the moment, we assume that the patch bit is present - // in both file table and (attributes) - assert((ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) != 0); - ha->pFileTable[i].dwFlags |= MPQ_FILE_PATCH_FILE; - } - } - } - else - nError = ERROR_FILE_CORRUPT; - - STORM_FREE(pbBitArray); - } + // Verify if there's enough data + if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) + return ERROR_FILE_CORRUPT; + + for(i = 0; i < dwBlockTableSize; i++) + { + ha->pFileTable[i].dwFlags |= (pbBitArray[dwByteIndex] & dwBitMask) ? MPQ_FILE_PATCH_FILE : 0; + dwByteIndex += (dwBitMask & 0x01); + dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01); } - // - // Note: Version 7.00 of StormLib saved the (attributes) incorrectly. - // Sometimes, number of entries in the (attributes) was 1 item less - // than block table size. - // If we encounter such table, we will zero all three arrays - // + pbAttrPtr += cbArraySize; + } - if(nError != ERROR_SUCCESS) - ha->dwAttrFlags = 0; + // + // Note: Version 7.00 of StormLib saved the (attributes) incorrectly. + // Sometimes, number of entries in the (attributes) was 1 item less + // than block table size. + // If we encounter such table, we will zero all three arrays + // - // Cleanup & exit - SFileCloseFile(hFile); - } - return nError; + if(pbAttrPtr != pbAttrFileEnd) + ha->dwAttrFlags = 0; + return ERROR_SUCCESS; } -int SAttrFileSaveToMpq(TMPQArchive * ha) +static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile) { - MPQ_ATTRIBUTES_HEADER AttrHeader; + PMPQ_ATTRIBUTES_HEADER pAttrHeader; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; - TMPQFile * hf = NULL; - DWORD dwFinalBlockTableSize = ha->dwFileTableSize; - DWORD dwFileSize = 0; - DWORD dwToWrite; - DWORD i; - int nError = ERROR_SUCCESS; + LPBYTE pbAttrFile; + LPBYTE pbAttrPtr; + size_t cbAttrFile; + DWORD dwFinalEntries = ha->dwFileTableSize + ha->dwReservedFiles; - // Now we have to check if we need patch bits in the (attributes) - if(nError == ERROR_SUCCESS) + // Check if we need patch bits in the (attributes) file + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - for(i = 0; i < ha->dwFileTableSize; i++) + if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { - if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) - { - ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; - break; - } + ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; + break; } } - // If the (attributes) is not in the file table yet, - // we have to increase the final block table size - pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - { - // If "(attributes)" file exists, and it's set to 0, then remove it - if(ha->dwAttrFlags == 0) - { - FreeFileEntry(ha, pFileEntry); - return ERROR_SUCCESS; - } - } - else + // Allocate the buffer for holding the entire (attributes) + // Allodate 1 byte more (See GetSizeOfAttributesFile for more info) + cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, dwFinalEntries); + pbAttrFile = pbAttrPtr = STORM_ALLOC(BYTE, cbAttrFile + 1); + if(pbAttrFile != NULL) { - // If we don't want to create file atributes, do nothing - if(ha->dwAttrFlags == 0) - return ERROR_SUCCESS; - - // Check where the file entry is going to be allocated. - // If at the end of the file table, we have to increment - // the expected size of the (attributes) file. - pFileEntry = FindFreeFileEntry(ha); - if(pFileEntry == ha->pFileTable + ha->dwFileTableSize) - dwFinalBlockTableSize++; - } + // Make sure it's all zeroed + memset(pbAttrFile, 0, cbAttrFile + 1); - // Calculate the size of the attributes file - if(nError == ERROR_SUCCESS) - { - dwFileSize = sizeof(MPQ_ATTRIBUTES_HEADER); // Header + // Write the header of the (attributes) file + pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr; + pAttrHeader->dwVersion = BSWAP_INT32_UNSIGNED(100); + pAttrHeader->dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL)); + pbAttrPtr = (LPBYTE)(pAttrHeader + 1); + + // Write the array of CRC32, if present if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32) - dwFileSize += dwFinalBlockTableSize * sizeof(DWORD); - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) - dwFileSize += dwFinalBlockTableSize * sizeof(ULONGLONG); - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) - dwFileSize += dwFinalBlockTableSize * MD5_DIGEST_SIZE; - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) - dwFileSize += ((dwFinalBlockTableSize - 1)) / 8 + 1; - } + { + LPDWORD pArrayCRC32 = (LPDWORD)pbAttrPtr; - // Determine the flags for (attributes) - if(ha->dwFileFlags2 == 0) - ha->dwFileFlags2 = GetDefaultSpecialFileFlags(ha, dwFileSize); + // Copy from file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + *pArrayCRC32++ = BSWAP_INT32_UNSIGNED(pFileEntry->dwCrc32); - // Create the attributes file in the MPQ - nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME, - 0, - dwFileSize, - LANG_NEUTRAL, - ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING, - &hf); + // Skip the reserved entries + pbAttrPtr = (LPBYTE)(pArrayCRC32 + ha->dwReservedFiles); + } - // Write all parts of the (attributes) file - if(nError == ERROR_SUCCESS) - { - assert(ha->dwFileTableSize == dwFinalBlockTableSize); + // Write the array of file time + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) + { + ULONGLONG * pArrayFileTime = (ULONGLONG *)pbAttrPtr; - // Note that we don't know what the new bit (0x08) means. - AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(100); - AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL)); - dwToWrite = sizeof(MPQ_ATTRIBUTES_HEADER); - nError = SFileAddFile_Write(hf, &AttrHeader, dwToWrite, MPQ_COMPRESSION_ZLIB); - } + // Copy from file table + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + *pArrayFileTime++ = BSWAP_INT64_UNSIGNED(pFileEntry->FileTime); - // Write the array of CRC32 - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32)) - { - LPDWORD pArrayCRC32 = STORM_ALLOC(DWORD, dwFinalBlockTableSize); + // Skip the reserved entries + pbAttrPtr = (LPBYTE)(pArrayFileTime + ha->dwReservedFiles); + } - if(pArrayCRC32 != NULL) + // Write the array of MD5s + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) { + LPBYTE pbArrayMD5 = pbAttrPtr; + // Copy from file table - for(i = 0; i < ha->dwFileTableSize; i++) - pArrayCRC32[i] = BSWAP_INT32_UNSIGNED(ha->pFileTable[i].dwCrc32); + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + memcpy(pbArrayMD5, pFileEntry->md5, MD5_DIGEST_SIZE); + pbArrayMD5 += MD5_DIGEST_SIZE; + } - dwToWrite = ha->dwFileTableSize * sizeof(DWORD); - nError = SFileAddFile_Write(hf, pArrayCRC32, dwToWrite, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pArrayCRC32); + // Skip the reserved items + pbAttrPtr = pbArrayMD5 + (ha->dwReservedFiles * MD5_DIGEST_SIZE); } - } - - // Write the array of file time - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)) - { - ULONGLONG * pArrayFileTime = STORM_ALLOC(ULONGLONG, ha->dwFileTableSize); - if(pArrayFileTime != NULL) + // Write the array of patch bits + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) { + LPBYTE pbBitArray = pbAttrPtr; + DWORD dwByteSize = (dwFinalEntries + 7) / 8; + DWORD dwByteIndex = 0; + DWORD dwBitMask = 0x80; + // Copy from file table - for(i = 0; i < ha->dwFileTableSize; i++) - pArrayFileTime[i] = BSWAP_INT64_UNSIGNED(ha->pFileTable[i].FileTime); + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + // Set the bit, if needed + if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) + pbBitArray[dwByteIndex] |= dwBitMask; + + // Update bit index and bit mask + dwByteIndex += (dwBitMask & 0x01); + dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01); + } - dwToWrite = ha->dwFileTableSize * sizeof(ULONGLONG); - nError = SFileAddFile_Write(hf, pArrayFileTime, dwToWrite, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pArrayFileTime); + // Move past the bit array + pbAttrPtr = (pbBitArray + dwByteSize); } + + // Now we expect that current position matches the estimated size + // Note that if there is 1 extra bit above the byte size, + // the table is actually 1 byte shorted in Blizzard MPQs. See GetSizeOfAttributesFile + assert((size_t)(pbAttrPtr - pbAttrFile) == cbAttrFile + ((dwFinalEntries & 0x07) == 1) ? 1 : 0); } - // Write the array of MD5s - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)) - { - char * pArrayMD5 = STORM_ALLOC(char, ha->dwFileTableSize * MD5_DIGEST_SIZE); + // Give away the attributes file + if(pcbAttrFile != NULL) + *pcbAttrFile = (DWORD)cbAttrFile; + return pbAttrFile; +} - if(pArrayMD5 != NULL) - { - // Copy from file table - for(i = 0; i < ha->dwFileTableSize; i++) - memcpy(&pArrayMD5[i * MD5_DIGEST_SIZE], ha->pFileTable[i].md5, MD5_DIGEST_SIZE); +//----------------------------------------------------------------------------- +// Public functions (internal use by StormLib) - dwToWrite = ha->dwFileTableSize * MD5_DIGEST_SIZE; - nError = SFileAddFile_Write(hf, pArrayMD5, dwToWrite, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pArrayMD5); - } - } +int SAttrLoadAttributes(TMPQArchive * ha) +{ + HANDLE hFile = NULL; + LPBYTE pbAttrFile; + DWORD dwBytesRead; + DWORD cbAttrFile = 0; + int nError = ERROR_FILE_CORRUPT; + + // File table must be initialized + assert(ha->pFileTable != NULL); - // Write the array of patch bits - if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)) + // Attempt to open the "(attributes)" file. + // If it's not there, then the archive doesn't support attributes + if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile)) { - LPBYTE pbBitArray; - DWORD dwByteSize = ((ha->dwFileTableSize - 1) / 8) + 1; + // Retrieve and check size of the (attributes) file + cbAttrFile = SFileGetFileSize(hFile, NULL); - pbBitArray = STORM_ALLOC(BYTE, dwByteSize); - if(pbBitArray != NULL) + // Size of the (attributes) might be 1 byte less than expected + // See GetSizeOfAttributesFile for more info + pbAttrFile = STORM_ALLOC(BYTE, cbAttrFile + 1); + if(pbAttrFile != NULL) { - memset(pbBitArray, 0, dwByteSize); - for(i = 0; i < ha->dwFileTableSize; i++) - { - DWORD dwByteIndex = i / 8; - DWORD dwBitMask = 0x80 >> (i & 7); + // Set the last byte to 0 in case the size should be 1 byte greater + pbAttrFile[cbAttrFile] = 0; - if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) - pbBitArray[dwByteIndex] |= dwBitMask; - } + // Load the entire file to memory + SFileReadFile(hFile, pbAttrFile, cbAttrFile, &dwBytesRead, NULL); + if(dwBytesRead == cbAttrFile) + nError = LoadAttributesFile(ha, pbAttrFile, cbAttrFile); - nError = SFileAddFile_Write(hf, pbBitArray, dwByteSize, MPQ_COMPRESSION_ZLIB); - STORM_FREE(pbBitArray); + // Free the buffer + STORM_FREE(pbAttrFile); } + + // Close the attributes file + SFileCloseFile(hFile); } - // Finalize the file in the archive - if(hf != NULL) + return nError; +} + +// Saves the (attributes) to the MPQ +int SAttrFileSaveToMpq(TMPQArchive * ha) +{ + TMPQFile * hf = NULL; + LPBYTE pbAttrFile; + DWORD cbAttrFile = 0; + int nError = ERROR_SUCCESS; + + // If there are no file flags for (attributes) file, do nothing + if(ha->dwFileFlags2 == 0 || ha->dwMaxFileCount == 0) + return ERROR_SUCCESS; + + // We expect at least one reserved entry to be there + assert(ha->dwReservedFiles >= 1); + + // Create the raw data that is to be written to (attributes) + // Note: Blizzard MPQs have entries for (listfile) and (attributes), + // but they are filled empty + pbAttrFile = CreateAttributesFile(ha, &cbAttrFile); + + // Now we decrement the number of reserved files. + // This frees one slot in the file table, so the subsequent file create operation should succeed + // This must happen even if CreateAttributesFile failed + ha->dwReservedFiles--; + + // If we created something, write the attributes to the MPQ + if(pbAttrFile != NULL) { - SFileAddFile_Finish(hf); - } + // We expect it to be nonzero size + assert(cbAttrFile != 0); + + // Determine the real flags for (attributes) + if(ha->dwFileFlags2 == MPQ_FILE_EXISTS) + ha->dwFileFlags2 = GetDefaultSpecialFileFlags(cbAttrFile, ha->pHeader->wFormatVersion); + + // Create the attributes file in the MPQ + nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME, + 0, + cbAttrFile, + LANG_NEUTRAL, + ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING, + &hf); + + // Write the attributes file raw data to it + if(nError == ERROR_SUCCESS) + { + // Write the content of the attributes file to the MPQ + nError = SFileAddFile_Write(hf, pbAttrFile, cbAttrFile, MPQ_COMPRESSION_ZLIB); + SFileAddFile_Finish(hf); + + // Clear the invalidate flag + ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID; + } - if(nError == ERROR_SUCCESS) - ha->dwFlags &= ~MPQ_FLAG_ATTRIBUTES_INVALID; + // Free the attributes buffer + STORM_FREE(pbAttrFile); + } + return nError; } @@ -375,7 +384,7 @@ DWORD WINAPI SFileGetAttributes(HANDLE hMpq) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify the parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_PARAMETER); return SFILE_INVALID_ATTRIBUTES; @@ -389,7 +398,7 @@ bool WINAPI SFileSetAttributes(HANDLE hMpq, DWORD dwFlags) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify the parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_PARAMETER); return false; @@ -439,7 +448,7 @@ bool WINAPI SFileUpdateFileAttributes(HANDLE hMpq, const char * szFileName) // Get the file size hf = (TMPQFile *)hFile; - SFileGetFileInfo(hFile, SFILE_INFO_FILE_SIZE, &dwTotalBytes, sizeof(DWORD), NULL); + dwTotalBytes = hf->pFileEntry->dwFileSize; // Initialize the CRC32 and MD5 contexts md5_init(&md5_state); diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp index a726d43..64c599d 100644 --- a/src/SFileCompactArchive.cpp +++ b/src/SFileCompactArchive.cpp @@ -441,7 +441,8 @@ bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK pfnCompa { TMPQArchive * ha = (TMPQArchive *) hMpq; - if (!IsValidMpqHandle(ha)) { + if (!IsValidMpqHandle(hMpq)) + { SetLastError(ERROR_INVALID_HANDLE); return false; } @@ -466,7 +467,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR int nError = ERROR_SUCCESS; // Test the valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; @@ -544,6 +545,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // Write the MPQ header to the file memcpy(&SaveMpqHeader, ha->pHeader, ha->pHeader->dwHeaderSize); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize)) @@ -555,9 +557,21 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // Now copy all files if(nError == ERROR_SUCCESS) - { nError = CopyMpqFiles(ha, pFileKeys, pTempStream); - ha->dwFlags |= MPQ_FLAG_CHANGED; + + // Defragment the file table + if(nError == ERROR_SUCCESS) + nError = RebuildFileTable(ha, ha->pHeader->dwHashTableSize, ha->dwMaxFileCount); + + // We also need to rebuild the HET table, if any + if(nError == ERROR_SUCCESS) + { + // Invalidate (listfile) and (attributes) + InvalidateInternalFiles(ha); + + // Rebuild the HET table, if we have any + if(ha->pHetTable != NULL) + nError = RebuildHetTable(ha); } // If succeeded, switch the streams @@ -608,25 +622,16 @@ DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq) bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount) { - TMPQHetTable * pOldHetTable = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; - TFileEntry * pOldFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pOldFileTable = NULL; - TFileEntry * pOldFileEntry; - TFileEntry * pFileEntry; - TMPQHash * pOldHashTable = NULL; - DWORD dwOldHashTableSize = 0; - DWORD dwOldFileTableSize = 0; + DWORD dwNewHashTableSize = 0; int nError = ERROR_SUCCESS; // Test the valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; - - // The new limit must be greater than the current file table size - if(nError == ERROR_SUCCESS && ha->dwFileTableSize > dwMaxFileCount) + if(dwMaxFileCount < ha->dwFileTableSize) nError = ERROR_DISK_FULL; // ALL file names must be known in order to be able @@ -637,126 +642,28 @@ bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount) } // If the MPQ has a hash table, then we relocate the hash table - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) - { - // Save parameters for the current hash table - dwOldHashTableSize = ha->pHeader->dwHashTableSize; - pOldHashTable = ha->pHashTable; - - // Allocate new hash table - ha->pHeader->dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); - ha->pHashTable = STORM_ALLOC(TMPQHash, ha->pHeader->dwHashTableSize); - if(ha->pHashTable != NULL) - memset(ha->pHashTable, 0xFF, ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // If the MPQ has HET table, allocate new one as well - if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) - { - // Save the original HET table - pOldHetTable = ha->pHetTable; - - // Create new one - ha->pHetTable = CreateHetTable(0, dwMaxFileCount, 0x40, true); - if(ha->pHetTable == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Now reallocate the file table - if(nError == ERROR_SUCCESS) - { - // Save the current file table - dwOldFileTableSize = ha->dwFileTableSize; - pOldFileTable = ha->pFileTable; - - // Create new one - ha->pFileTable = STORM_ALLOC(TFileEntry, dwMaxFileCount); - if(ha->pFileTable != NULL) - memset(ha->pFileTable, 0, dwMaxFileCount * sizeof(TFileEntry)); - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Now we have to build both classic hash table and HET table. if(nError == ERROR_SUCCESS) { - DWORD dwFileIndex = 0; - DWORD dwHashIndex = 0; + // Calculate the hash table size for the new file limit + dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); - // Create new hash and HET entry for each file - pFileEntry = ha->pFileTable; - for(pOldFileEntry = pOldFileTable; pOldFileEntry < pOldFileTableEnd; pOldFileEntry++) - { - if(pOldFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // Copy the old file entry to the new one - memcpy(pFileEntry, pOldFileEntry, sizeof(TFileEntry)); - assert(pFileEntry->szFileName != NULL); - - // Create new entry in the hash table - if(ha->pHashTable != NULL) - { - if(AllocateHashEntry(ha, pFileEntry) == NULL) - { - nError = ERROR_CAN_NOT_COMPLETE; - break; - } - } - - // Create new entry in the HET table, if needed - if(ha->pHetTable != NULL) - { - dwHashIndex = AllocateHetEntry(ha, pFileEntry); - if(dwHashIndex == HASH_ENTRY_FREE) - { - nError = ERROR_CAN_NOT_COMPLETE; - break; - } - } - - // Move to the next file entry in the new table - pFileEntry++; - dwFileIndex++; - } - } + // Rebuild both file tables + nError = RebuildFileTable(ha, dwNewHashTableSize, dwMaxFileCount); } - // Mark the archive as changed - // Note: We always have to rebuild the (attributes) file due to file table change + // We always have to rebuild the (attributes) file due to file table change if(nError == ERROR_SUCCESS) { - ha->dwMaxFileCount = dwMaxFileCount; + // Invalidate (listfile) and (attributes) InvalidateInternalFiles(ha); - } - else - { - // Revert the hash table - if(ha->pHashTable != NULL && pOldHashTable != NULL) - { - STORM_FREE(ha->pHashTable); - ha->pHeader->dwHashTableSize = dwOldHashTableSize; - ha->pHashTable = pOldHashTable; - } - - // Revert the HET table - if(ha->pHetTable != NULL && pOldHetTable != NULL) - { - FreeHetTable(ha->pHetTable); - ha->pHetTable = pOldHetTable; - } - // Revert the file table - if(pOldFileTable != NULL) - { - STORM_FREE(ha->pFileTable); - ha->pFileTable = pOldFileTable; - } - - SetLastError(nError); + // Rebuild the HET table, if we have any + if(ha->pHetTable != NULL) + nError = RebuildHetTable(ha); } - // Return the result + // Return the error + if(nError != ERROR_SUCCESS) + SetLastError(nError); return (nError == ERROR_SUCCESS); } diff --git a/src/SFileCreateArchive.cpp b/src/SFileCreateArchive.cpp index 78ddd71..2b51efa 100644 --- a/src/SFileCreateArchive.cpp +++ b/src/SFileCreateArchive.cpp @@ -57,6 +57,7 @@ static int WriteNakedMPQHeader(TMPQArchive * ha) // Write it to the file BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_1); + BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_2); BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_3); BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_4); if(!FileStream_Write(ha->pStream, &ha->MpqPos, &Header, dwBytesToWrite)) @@ -68,19 +69,27 @@ static int WriteNakedMPQHeader(TMPQArchive * ha) //----------------------------------------------------------------------------- // Creates a new MPQ archive. -bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq) +bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq) { SFILE_CREATE_MPQ CreateInfo; // Fill the create structure memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ)); CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ); - CreateInfo.dwMpqVersion = (dwFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT; + CreateInfo.dwMpqVersion = (dwCreateFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT; CreateInfo.dwStreamFlags = STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE; - CreateInfo.dwAttrFlags = (dwFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0; + CreateInfo.dwFileFlags1 = (dwCreateFlags & MPQ_CREATE_LISTFILE) ? MPQ_FILE_EXISTS : 0; + CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_EXISTS : 0; + CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0; CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000; CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0; CreateInfo.dwMaxFileCount = dwMaxFileCount; + + // Backward compatibility: SFileCreateArchive always used to add (listfile) + // We would break loads of applications if we change that + CreateInfo.dwFileFlags1 = MPQ_FILE_EXISTS; + + // Let the main function create the archive return SFileCreateArchive2(szMpqName, &CreateInfo, phMpq); } @@ -93,7 +102,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea HANDLE hMpq = NULL; DWORD dwBlockTableSize = 0; // Initial block table size DWORD dwHashTableSize = 0; - DWORD dwMaxFileCount; + DWORD dwReservedFiles = 0; // Number of reserved file entries int nError = ERROR_SUCCESS; // Check the parameters, if they are valid @@ -109,8 +118,7 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea (pCreateInfo->pvUserData != NULL || pCreateInfo->cbUserData != 0) || (pCreateInfo->dwAttrFlags & ~MPQ_ATTRIBUTE_ALL) || (pCreateInfo->dwSectorSize & (pCreateInfo->dwSectorSize - 1)) || - (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)) || - (pCreateInfo->dwMaxFileCount < 4)) + (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1))) { SetLastError(ERROR_INVALID_PARAMETER); return false; @@ -142,15 +150,14 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea return false; } - // Increment the maximum amount of files to have space - // for listfile and attributes file - dwMaxFileCount = pCreateInfo->dwMaxFileCount; - if(pCreateInfo->dwAttrFlags != 0) - dwMaxFileCount++; - dwMaxFileCount++; + // Increment the maximum amount of files to have space for (listfile) and (attributes) + if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags1) + dwReservedFiles++; + if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags2 && pCreateInfo->dwAttrFlags) + dwReservedFiles++; // If file count is not zero, initialize the hash table size - dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); + dwHashTableSize = GetHashTableSizeForFileCount(pCreateInfo->dwMaxFileCount + dwReservedFiles); // Retrieve the file size and round it up to 0x200 bytes FileStream_GetSize(pStream, &MpqPos); @@ -180,14 +187,13 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea ha->UserDataPos = MpqPos; ha->MpqPos = MpqPos; ha->pHeader = pHeader = (TMPQHeader *)ha->HeaderData; - ha->dwMaxFileCount = dwMaxFileCount; + ha->dwMaxFileCount = dwHashTableSize; ha->dwFileTableSize = 0; + ha->dwReservedFiles = dwReservedFiles; ha->dwFileFlags1 = pCreateInfo->dwFileFlags1; ha->dwFileFlags2 = pCreateInfo->dwFileFlags2; - ha->dwFlags = 0; - - // Setup the attributes ha->dwAttrFlags = pCreateInfo->dwAttrFlags; + ha->dwFlags = 0; pStream = NULL; // Fill the MPQ header @@ -215,21 +221,21 @@ bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCrea } // Create initial HET table, if the caller required an MPQ format 3.0 or newer - if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3) + if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3 && pCreateInfo->dwMaxFileCount != 0) { - ha->pHetTable = CreateHetTable(0, ha->dwFileTableSize, 0x40, true); + ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0x40, NULL); if(ha->pHetTable == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Create initial hash table - if(nError == ERROR_SUCCESS) + if(nError == ERROR_SUCCESS && dwHashTableSize != 0) { nError = CreateHashTable(ha, dwHashTableSize); } // Create initial file table - if(nError == ERROR_SUCCESS) + if(nError == ERROR_SUCCESS && ha->dwMaxFileCount != 0) { ha->pFileTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount); if(ha->pFileTable != NULL) diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp index af7e6bc..21c9499 100644 --- a/src/SFileFindFile.cpp +++ b/src/SFileFindFile.cpp @@ -37,12 +37,14 @@ struct TMPQSearch //----------------------------------------------------------------------------- // Local functions -static bool IsValidSearchHandle(TMPQSearch * hs) +static TMPQSearch * IsValidSearchHandle(HANDLE hFind) { - if(hs == NULL) - return false; + TMPQSearch * hs = (TMPQSearch *)hFind; - return IsValidMpqHandle(hs->ha); + if(hs != NULL && IsValidMpqHandle(hs->ha)) + return hs; + + return NULL; } bool CheckWildCard(const char * szString, const char * szWildCard) @@ -140,7 +142,7 @@ static DWORD GetSearchTableItems(TMPQArchive * ha) while(ha != NULL) { // Append the number of files - dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwFileCount + dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount : ha->pHeader->dwBlockTableSize; // Move to the patched archive ha = ha->haPatch; @@ -295,25 +297,29 @@ static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) } } - // Check the file name against the wildcard - if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) + // If the file name is still NULL, we cannot include the file to the search + if(szFileName != NULL) { - // Fill the found entry - lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex; - lpFindFileData->dwBlockIndex = dwBlockIndex; - lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; - lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; - lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; - lpFindFileData->lcLocale = pPatchEntry->lcLocale; - - // Fill the filetime - lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); - lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); - - // Fill the file name and plain file name - strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength); - lpFindFileData->szPlainName = (char *)GetPlainFileNameA(lpFindFileData->cFileName); - return ERROR_SUCCESS; + // Check the file name against the wildcard + if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) + { + // Fill the found entry + lpFindFileData->dwHashIndex = pPatchEntry->dwHashIndex; + lpFindFileData->dwBlockIndex = dwBlockIndex; + lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; + lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; + lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; + lpFindFileData->lcLocale = pPatchEntry->lcLocale; + + // Fill the filetime + lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); + lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); + + // Fill the file name and plain file name + strcpy(lpFindFileData->cFileName, szFileName + nPrefixLength); + lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName); + return ERROR_SUCCESS; + } } } } @@ -352,7 +358,7 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA int nError = ERROR_SUCCESS; // Check for the valid parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szMask == NULL || lpFindFileData == NULL) nError = ERROR_INVALID_PARAMETER; @@ -412,11 +418,11 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) { - TMPQSearch * hs = (TMPQSearch *)hFind; + TMPQSearch * hs = IsValidSearchHandle(hFind); int nError = ERROR_SUCCESS; // Check the parameters - if(!IsValidSearchHandle(hs)) + if(hs == NULL) nError = ERROR_INVALID_HANDLE; if(lpFindFileData == NULL) nError = ERROR_INVALID_PARAMETER; @@ -431,10 +437,10 @@ bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) bool WINAPI SFileFindClose(HANDLE hFind) { - TMPQSearch * hs = (TMPQSearch *)hFind; + TMPQSearch * hs = IsValidSearchHandle(hFind); // Check the parameters - if(!IsValidSearchHandle(hs)) + if(hs == NULL) { SetLastError(ERROR_INVALID_HANDLE); return false; diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp index 6a85bd9..06c8d6a 100644 --- a/src/SFileGetFileInfo.cpp +++ b/src/SFileGetFileInfo.cpp @@ -1,12 +1,11 @@ /*****************************************************************************/ -/* SFileReadFile.cpp Copyright (c) Ladislav Zezula 2003 */ +/* SFileGetFileInfo.cpp Copyright (c) Ladislav Zezula 2013 */ /*---------------------------------------------------------------------------*/ -/* Description : */ +/* Description: */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ -/* xx.xx.99 1.00 Lad The first version of SFileReadFile.cpp */ -/* 24.03.99 1.00 Lad Added the SFileGetFileInfo function */ +/* 30.11.13 1.00 Lad The first version of SFileGetFileInfo.cpp */ /*****************************************************************************/ #define __STORMLIB_SELF__ @@ -14,8 +13,34 @@ #include "StormCommon.h" //----------------------------------------------------------------------------- +// Local defines + +// Information types for SFileGetFileInfo +#define SFILE_INFO_TYPE_UNKNOWN 0 +#define SFILE_INFO_TYPE_DIRECT_POINTER 1 +#define SFILE_INFO_TYPE_ALLOCATED 2 +#define SFILE_INFO_TYPE_READ_FROM_FILE 3 +#define SFILE_INFO_TYPE_TABLE_POINTER 4 +#define SFILE_INFO_TYPE_FILE_ENTRY 5 + +//----------------------------------------------------------------------------- // Local functions +static void ConvertFileEntryToSelfRelative(TFileEntry * pFileEntry, TFileEntry * pSrcFileEntry) +{ + // Copy the file entry itself + memcpy(pFileEntry, pSrcFileEntry, sizeof(TFileEntry)); + + // If source is NULL, leave it NULL + if(pSrcFileEntry->szFileName != NULL) + { + // Set the file name pointer after the file entry + pFileEntry->szFileName = (char *)(pFileEntry + 1); + strcpy(pFileEntry->szFileName, pSrcFileEntry->szFileName); + } +} + + static DWORD GetMpqFileCount(TMPQArchive * ha) { TFileEntry * pFileTableEnd; @@ -43,1245 +68,680 @@ static DWORD GetMpqFileCount(TMPQArchive * ha) return dwFileCount; } -static TCHAR * GetFilePatchChain(TMPQFile * hf, DWORD * pcbChainLength) +static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, DWORD * pcbLengthNeeded) { TMPQFile * hfTemp; - TCHAR * szPatchChain = NULL; - TCHAR * szPatchItem = NULL; - TCHAR * szFileName; + TCHAR * szFileInfo = (TCHAR *)pvFileInfo; size_t cchCharsNeeded = 1; + size_t cchFileInfo = (cbFileInfo / sizeof(TCHAR)); size_t nLength; // Patch chain is only supported on MPQ files. - if(hf->pStream == NULL) + if(hf->pStream != NULL) { - // Calculate the necessary length of the multi-string - for(hfTemp = hf; hfTemp != NULL; hfTemp->hfPatchFile) - cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - - // Allocate space for the multi-string - szPatchChain = szPatchItem = STORM_ALLOC(TCHAR, cchCharsNeeded); - if(szPatchChain != NULL) - { - // Fill-in all the names - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - { - szFileName = FileStream_GetFileName(hfTemp->ha->pStream); - nLength = _tcslen(szFileName) + 1; - - memcpy(szPatchItem, szFileName, nLength * sizeof(TCHAR)); - szPatchItem += nLength; - } - - // Terminate the multi-string - *szPatchItem++ = 0; - } - - // The length must match - assert((size_t)(szPatchItem - szPatchChain) == cchCharsNeeded); + SetLastError(ERROR_INVALID_PARAMETER); + return false; } - // Give the length of the patch chain, in bytes - if(pcbChainLength != NULL) - pcbChainLength[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - return szPatchChain; -} - -// hf - MPQ File handle. -// pbBuffer - Pointer to target buffer to store sectors. -// dwByteOffset - Position of sector in the file (relative to file begin) -// dwBytesToRead - Number of bytes to read. Must be multiplier of sector size. -// pdwBytesRead - Stored number of bytes loaded -static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG RawFilePos; - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbRawSector = NULL; - LPBYTE pbOutSector = pbBuffer; - LPBYTE pbInSector = pbBuffer; - DWORD dwRawBytesToRead; - DWORD dwRawSectorOffset = dwByteOffset; - DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize; - DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize; - DWORD dwSectorsDone = 0; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Note that dwByteOffset must be aligned to size of one sector - // Note that dwBytesToRead must be a multiplier of one sector size - // This is local function, so we won't check if that's true. - // Note that files stored in single units are processed by a separate function + // Calculate the necessary length of the multi-string + for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch) + cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - // If there is not enough bytes remaining, cut dwBytesToRead - if((dwByteOffset + dwBytesToRead) > hf->dwDataSize) - dwBytesToRead = hf->dwDataSize - dwByteOffset; - dwRawBytesToRead = dwBytesToRead; + // Give the caller the needed length + if(pcbLengthNeeded != NULL) + pcbLengthNeeded[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - // Perform all necessary work to do with compressed files - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) + // If the caller gave both buffer pointer and data length, + // try to copy the patch chain + if(szFileInfo != NULL && cchFileInfo != 0) { - // If the sector positions are not loaded yet, do it - if(hf->SectorOffsets == NULL) + // If there is enough space in the buffer, copy the patch chain + if(cchCharsNeeded > cchFileInfo) { - nError = AllocateSectorOffsets(hf, true); - if(nError != ERROR_SUCCESS) - return nError; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return false; } - // If the sector checksums are not loaded yet, load them now. - if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->bLoadedSectorCRCs == false) + // Copy each patch + for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch) { - // - // Sector CRCs is plain crap feature. It is almost never present, - // often it's empty, or the end offset of sector CRCs is zero. - // We only try to load sector CRCs once, and regardless if it fails - // or not, we won't try that again for the given file. - // - - AllocateSectorChecksums(hf, true); - hf->bLoadedSectorCRCs = true; + // Get the file name and its length + const TCHAR * szFileName = FileStream_GetFileName(hfTemp->ha->pStream); + nLength = _tcslen(szFileName) + 1; + + // Copy the file name + memcpy(szFileInfo, szFileName, nLength * sizeof(TCHAR)); + szFileInfo += nLength; } - // TODO: If the raw data MD5s are not loaded yet, load them now - // Only do it if the MPQ is of format 4.0 -// if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0) -// { -// nError = AllocateRawMD5s(hf, true); -// if(nError != ERROR_SUCCESS) -// return nError; -// } - - // If the file is compressed, also allocate secondary buffer - pbInSector = pbRawSector = STORM_ALLOC(BYTE, dwBytesToRead); - if(pbRawSector == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Assign the temporary buffer as target for read operation - dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex]; - dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset; + // Make it multi-string + szFileInfo[0] = 0; } - // Calculate raw file offset where the sector(s) are stored. - CalculateRawSectorOffset(RawFilePos, hf, dwRawSectorOffset); + return true; +} - // Set file pointer and read all required sectors - if(!FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead)) - return GetLastError(); - dwBytesRead = 0; +//----------------------------------------------------------------------------- +// Retrieves an information about an archive or about a file within the archive +// +// hMpqOrFile - Handle to an MPQ archive or to a file +// InfoClass - Information to obtain +// pvFileInfo - Pointer to buffer to store the information +// cbFileInfo - Size of the buffer pointed by pvFileInfo +// pcbLengthNeeded - Receives number of bytes necessary to store the information - // Now we have to decrypt and decompress all file sectors that have been loaded - for(DWORD i = 0; i < dwSectorsToRead; i++) - { - DWORD dwRawBytesInThisSector = ha->dwSectorSize; - DWORD dwBytesInThisSector = ha->dwSectorSize; - DWORD dwIndex = dwSectorIndex + i; - - // If there is not enough bytes in the last sector, - // cut the number of bytes in this sector - if(dwRawBytesInThisSector > dwBytesToRead) - dwRawBytesInThisSector = dwBytesToRead; - if(dwBytesInThisSector > dwBytesToRead) - dwBytesInThisSector = dwBytesToRead; - - // If the file is compressed, we have to adjust the raw sector size - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex]; - - // If the file is encrypted, we have to decrypt the sector - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); +bool WINAPI SFileGetFileInfo( + HANDLE hMpqOrFile, + SFileInfoClass InfoClass, + void * pvFileInfo, + DWORD cbFileInfo, + LPDWORD pcbLengthNeeded) +{ + MPQ_SIGNATURE_INFO SignatureInfo; + TMPQArchive * ha = NULL; + TFileEntry * pFileEntry = NULL; + ULONGLONG Int64Value = 0; + ULONGLONG ByteOffset = 0; + TMPQFile * hf = NULL; + void * pvSrcFileInfo = NULL; + DWORD cbSrcFileInfo = 0; + DWORD dwInt32Value = 0; + int nInfoType = SFILE_INFO_TYPE_UNKNOWN; + int nError = ERROR_INVALID_PARAMETER; - // If we don't know the key, try to detect it by file content - if(hf->dwFileKey == 0) + switch(InfoClass) + { + case SFileMpqFileName: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector); - if(hf->dwFileKey == 0) - { - nError = ERROR_UNKNOWN_FILE_KEY; - break; - } + pvSrcFileInfo = (void *)FileStream_GetFileName(ha->pStream); + cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } + break; - DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex); - BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); - } - - // If the file has sector CRC check turned on, perform it - if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL) - { - DWORD dwAdlerExpected = hf->SectorChksums[dwIndex]; - DWORD dwAdlerValue = 0; - - // We can only check sector CRC when it's not zero - // Neither can we check it if it's 0xFFFFFFFF. - if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF) + case SFileMpqUserDataOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && ha->pUserData != NULL) { - dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector); - if(dwAdlerValue != dwAdlerExpected) - { - nError = ERROR_CHECKSUM_ERROR; - break; - } + pvSrcFileInfo = &ha->UserDataPos; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - } - - // If the sector is really compressed, decompress it. - // WARNING : Some sectors may not be compressed, it can be determined only - // by comparing uncompressed and compressed size !!! - if(dwRawBytesInThisSector < dwBytesInThisSector) - { - int cbOutSector = dwBytesInThisSector; - int cbInSector = dwRawBytesInThisSector; - int nResult = 0; + break; - // Is the file compressed by Blizzard's multiple compression ? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) + case SFileMpqUserDataHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && ha->pUserData != NULL) { - if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) - nResult = SCompDecompress2(pbOutSector, &cbOutSector, pbInSector, cbInSector); - else - nResult = SCompDecompress(pbOutSector, &cbOutSector, pbInSector, cbInSector); + ByteOffset = ha->UserDataPos; + cbSrcFileInfo = sizeof(TMPQUserData); + nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; } + break; - // Is the file compressed by PKWARE Data Compression Library ? - else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) + case SFileMpqUserData: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && ha->pUserData != NULL) { - nResult = SCompExplode(pbOutSector, &cbOutSector, pbInSector, cbInSector); + ByteOffset = ha->UserDataPos + sizeof(TMPQUserData); + cbSrcFileInfo = ha->pUserData->dwHeaderOffs - sizeof(TMPQUserData); + nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; } + break; - // Did the decompression fail ? - if(nResult == 0) + case SFileMpqHeaderOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - nError = ERROR_FILE_CORRUPT; - break; + pvSrcFileInfo = &ha->MpqPos; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - } - else - { - if(pbOutSector != pbInSector) - memcpy(pbOutSector, pbInSector, dwBytesInThisSector); - } - - // Move pointers - dwBytesToRead -= dwBytesInThisSector; - dwByteOffset += dwBytesInThisSector; - dwBytesRead += dwBytesInThisSector; - pbOutSector += dwBytesInThisSector; - pbInSector += dwRawBytesInThisSector; - dwSectorsDone++; - } - - // Free all used buffers - if(pbRawSector != NULL) - STORM_FREE(pbRawSector); - - // Give the caller thenumber of bytes read - *pdwBytesRead = dwBytesRead; - return nError; -} - -static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG RawFilePos = hf->RawFilePos; - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbCompressed = NULL; - LPBYTE pbRawData = NULL; - int nError = ERROR_SUCCESS; - - // If the file buffer is not allocated yet, do it. - if(hf->pbFileSector == NULL) - { - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - pbRawData = hf->pbFileSector; - } - - // If the file is a patch file, adjust raw data offset - if(hf->pPatchInfo != NULL) - RawFilePos += hf->pPatchInfo->dwLength; - - // If the file sector is not loaded yet, do it - if(hf->dwSectorOffs != 0) - { - // Is the file compressed? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - // Allocate space for compressed data - pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - pbRawData = pbCompressed; - } - - // Load the raw (compressed, encrypted) data - if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) - { - STORM_FREE(pbCompressed); - return GetLastError(); - } - - // If the file is encrypted, we have to decrypt the data first - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); - DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); - BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); - } + break; - // If the file is compressed, we have to decompress it now - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - int cbOutBuffer = (int)hf->dwDataSize; - int cbInBuffer = (int)pFileEntry->dwCmpSize; - int nResult = 0; - - // - // If the file is an incremental patch, the size of compressed data - // is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo) - // - // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: - // - // File CmprSize DcmpSize DataSize Compressed? - // -------------------------------------- ---------- -------- -------- --------------- - // esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes - // deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No - // - - if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) - cbInBuffer = cbInBuffer - sizeof(TPatchInfo); - - // Is the file compressed by Blizzard's multiple compression ? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) + case SFileMpqHeaderSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) - nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); - else - nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); + pvSrcFileInfo = &ha->pHeader->dwHeaderSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } + break; - // Is the file compressed by PKWARE Data Compression Library ? - // Note: Single unit files compressed with IMPLODE are not supported by Blizzard - else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) - nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); - - nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; - } - else - { - if(pbRawData != hf->pbFileSector) - memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); - } - - // Free the decompression buffer. - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - - // The file sector is now properly loaded - hf->dwSectorOffs = 0; - } - - // At this moment, we have the file loaded into the file buffer. - // Copy as much as the caller wants - if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) - { - // File position is greater or equal to file size ? - if(dwFilePos >= hf->dwDataSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // If not enough bytes remaining in the file, cut them - if((hf->dwDataSize - dwFilePos) < dwToRead) - dwToRead = (hf->dwDataSize - dwFilePos); - - // Copy the bytes - memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); - - // Give the number of bytes read - *pdwBytesRead = dwToRead; - return ERROR_SUCCESS; - } - - // An error, sorry - return ERROR_CAN_NOT_COMPLETE; -} - -static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C) - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbCompressed = NULL; - LPBYTE pbRawData = hf->pbFileSector; - int nError = ERROR_SUCCESS; - - // We do not support patch files in MPK archives - assert(hf->pPatchInfo == NULL); - - // If the file buffer is not allocated yet, do it. - if(hf->pbFileSector == NULL) - { - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - - // Is the file compressed? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - // Allocate space for compressed data - pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - pbRawData = pbCompressed; - } - - // Load the raw (compressed, encrypted) data - if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) - { - STORM_FREE(pbCompressed); - return GetLastError(); - } - - // If the file is encrypted, we have to decrypt the data first - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - DecryptMpkTable(pbRawData, pFileEntry->dwCmpSize); - } - - // If the file is compressed, we have to decompress it now - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - int cbOutBuffer = (int)hf->dwDataSize; - - if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, pbRawData, (int)pFileEntry->dwCmpSize)) - nError = ERROR_FILE_CORRUPT; - } - else - { - if(pbRawData != hf->pbFileSector) - memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); - } - - // Free the decompression buffer. - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - - // The file sector is now properly loaded - hf->dwSectorOffs = 0; - } - - // At this moment, we have the file loaded into the file buffer. - // Copy as much as the caller wants - if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) - { - // File position is greater or equal to file size ? - if(dwFilePos >= hf->dwDataSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // If not enough bytes remaining in the file, cut them - if((hf->dwDataSize - dwFilePos) < dwToRead) - dwToRead = (hf->dwDataSize - dwFilePos); - - // Copy the bytes - memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); - - // Give the number of bytes read - *pdwBytesRead = dwToRead; - return ERROR_SUCCESS; - } - - // An error, sorry - return ERROR_CAN_NOT_COMPLETE; -} - - -static int ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead) -{ - TMPQArchive * ha = hf->ha; - LPBYTE pbBuffer = (BYTE *)pvBuffer; - DWORD dwTotalBytesRead = 0; // Total bytes read in all three parts - DWORD dwSectorSizeMask = ha->dwSectorSize - 1; // Mask for block size, usually 0x0FFF - DWORD dwFileSectorPos; // File offset of the loaded sector - DWORD dwBytesRead; // Number of bytes read (temporary variable) - int nError; - - // If the file position is at or beyond end of file, do nothing - if(dwFilePos >= hf->dwDataSize) - { - *pdwBytesRead = 0; - return ERROR_SUCCESS; - } - - // If not enough bytes in the file remaining, cut them - if(dwBytesToRead > (hf->dwDataSize - dwFilePos)) - dwBytesToRead = (hf->dwDataSize - dwFilePos); - - // Compute sector position in the file - dwFileSectorPos = dwFilePos & ~dwSectorSizeMask; // Position in the block - - // If the file sector buffer is not allocated yet, do it now - if(hf->pbFileSector == NULL) - { - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - } - - // Load the first (incomplete) file sector - if(dwFilePos & dwSectorSizeMask) - { - DWORD dwBytesInSector = ha->dwSectorSize; - DWORD dwBufferOffs = dwFilePos & dwSectorSizeMask; - DWORD dwToCopy; - - // Is the file sector already loaded ? - if(hf->dwSectorOffs != dwFileSectorPos) - { - // Load one MPQ sector into archive buffer - nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesInSector); - if(nError != ERROR_SUCCESS) - return nError; - - // Remember that the data loaded to the sector have new file offset - hf->dwSectorOffs = dwFileSectorPos; - } - else - { - if((dwFileSectorPos + dwBytesInSector) > hf->dwDataSize) - dwBytesInSector = hf->dwDataSize - dwFileSectorPos; - } - - // Copy the data from the offset in the loaded sector to the end of the sector - dwToCopy = dwBytesInSector - dwBufferOffs; - if(dwToCopy > dwBytesToRead) - dwToCopy = dwBytesToRead; - - // Copy data from sector buffer into target buffer - memcpy(pbBuffer, hf->pbFileSector + dwBufferOffs, dwToCopy); - - // Update pointers and byte counts - dwTotalBytesRead += dwToCopy; - dwFileSectorPos += dwBytesInSector; - pbBuffer += dwToCopy; - dwBytesToRead -= dwToCopy; - } - - // Load the whole ("middle") sectors only if there is at least one full sector to be read - if(dwBytesToRead >= ha->dwSectorSize) - { - DWORD dwBlockBytes = dwBytesToRead & ~dwSectorSizeMask; - - // Load all sectors to the output buffer - nError = ReadMpqSectors(hf, pbBuffer, dwFileSectorPos, dwBlockBytes, &dwBytesRead); - if(nError != ERROR_SUCCESS) - return nError; - - // Update pointers - dwTotalBytesRead += dwBytesRead; - dwFileSectorPos += dwBytesRead; - pbBuffer += dwBytesRead; - dwBytesToRead -= dwBytesRead; - } - - // Read the terminating sector - if(dwBytesToRead > 0) - { - DWORD dwToCopy = ha->dwSectorSize; - - // Is the file sector already loaded ? - if(hf->dwSectorOffs != dwFileSectorPos) - { - // Load one MPQ sector into archive buffer - nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesRead); - if(nError != ERROR_SUCCESS) - return nError; - - // Remember that the data loaded to the sector have new file offset - hf->dwSectorOffs = dwFileSectorPos; - } - - // Check number of bytes read - if(dwToCopy > dwBytesToRead) - dwToCopy = dwBytesToRead; - - // Copy the data from the cached last sector to the caller's buffer - memcpy(pbBuffer, hf->pbFileSector, dwToCopy); - - // Update pointers - dwTotalBytesRead += dwToCopy; - } - - // Store total number of bytes read to the caller - *pdwBytesRead = dwTotalBytesRead; - return ERROR_SUCCESS; -} - -static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - DWORD dwBytesToRead = dwToRead; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Make sure that the patch file is loaded completely - if(hf->pbFileData == NULL) - { - // Load the original file and store its content to "pbOldData" - hf->pbFileData = STORM_ALLOC(BYTE, hf->pFileEntry->dwFileSize); - hf->cbFileData = hf->pFileEntry->dwFileSize; - if(hf->pbFileData == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Read the file data - if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) - nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); - else - nError = ReadMpqFileSectorFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); - - // Fix error code - if(nError == ERROR_SUCCESS && dwBytesRead != hf->cbFileData) - nError = ERROR_FILE_CORRUPT; - - // Patch the file data - if(nError == ERROR_SUCCESS) - nError = PatchFileData(hf); - - // Reset number of bytes read to zero - dwBytesRead = 0; - } - - // If there is something to read, do it - if(nError == ERROR_SUCCESS) - { - if(dwFilePos < hf->cbFileData) - { - // Make sure we don't copy more than file size - if((dwFilePos + dwToRead) > hf->cbFileData) - dwToRead = hf->cbFileData - dwFilePos; - - // Copy the appropriate amount of the file data to the caller's buffer - memcpy(pvBuffer, hf->pbFileData + dwFilePos, dwToRead); - dwBytesRead = dwToRead; - } - - // Set the proper error code - nError = (dwBytesRead == dwBytesToRead) ? ERROR_SUCCESS : ERROR_HANDLE_EOF; - } - - // Give the result to the caller - if(pdwBytesRead != NULL) - *pdwBytesRead = dwBytesRead; - return nError; -} - -static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) -{ - ULONGLONG FilePosition1 = dwFilePos; - ULONGLONG FilePosition2; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - assert(hf->pStream != NULL); - - // Because stream I/O functions are designed to read - // "all or nothing", we compare file position before and after, - // and if they differ, we assume that number of bytes read - // is the difference between them - - if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead)) - { - // If not all bytes have been read, then return the number of bytes read - if((nError = GetLastError()) == ERROR_HANDLE_EOF) - { - FileStream_GetPos(hf->pStream, &FilePosition2); - dwBytesRead = (DWORD)(FilePosition2 - FilePosition1); - } - } - else - { - dwBytesRead = dwToRead; - } - - *pdwBytesRead = dwBytesRead; - return nError; -} - -//----------------------------------------------------------------------------- -// SFileReadFile - -bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped) -{ - TMPQFile * hf = (TMPQFile *)hFile; - DWORD dwBytesRead = 0; // Number of bytes read - int nError = ERROR_SUCCESS; - - // Keep compilers happy - lpOverlapped = lpOverlapped; - - // Check valid parameters - if(!IsValidFileHandle(hFile)) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - if(pvBuffer == NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // If the file is local file, read the data directly from the stream - if(hf->pStream != NULL) - { - nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // If the file is a patch file, we have to read it special way - else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - { - nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // If the archive is a MPK archive, we need special way to read the file - else if(hf->ha->dwSubType == MPQ_SUBTYPE_MPK) - { - nError = ReadMpkFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // If the file is single unit file, redirect it to read file - else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) - { - nError = ReadMpqFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // Otherwise read it as sector based MPQ file - else - { - nError = ReadMpqFileSectorFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); - } - - // Increment the file position - hf->dwFilePos += dwBytesRead; - - // Give the caller the number of bytes read - if(pdwRead != NULL) - *pdwRead = dwBytesRead; - - // If the read operation succeeded, but not full number of bytes was read, - // set the last error to ERROR_HANDLE_EOF - if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead)) - nError = ERROR_HANDLE_EOF; - - // If something failed, set the last error value - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// SFileGetFileSize - -DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) -{ - ULONGLONG FileSize; - TMPQFile * hf = (TMPQFile *)hFile; - - // Validate the file handle before we go on - if(IsValidFileHandle(hFile)) - { - // Make sure that the variable is initialized - FileSize = 0; - - // If the file is patched file, we have to get the size of the last version - if(hf->hfPatchFile != NULL) - { - // Walk through the entire patch chain, take the last version - while(hf != NULL) + case SFileMpqHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - // Get the size of the currently pointed version - FileSize = hf->pFileEntry->dwFileSize; - - // Move to the next patch file in the hierarchy - hf = hf->hfPatchFile; + ByteOffset = ha->MpqPos; + cbSrcFileInfo = ha->pHeader->dwHeaderSize; + nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; } - } - else - { - // Is it a local file ? - if(hf->pStream != NULL) + break; + + case SFileMpqHetTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileStream_GetSize(hf->pStream, &FileSize); + pvSrcFileInfo = &ha->pHeader->HetTablePos64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - else + break; + + case SFileMpqHetTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileSize = hf->dwDataSize; + pvSrcFileInfo = &ha->pHeader->HetTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - } - - // If opened from archive, return file size - if(pdwFileSizeHigh != NULL) - *pdwFileSizeHigh = (DWORD)(FileSize >> 32); - return (DWORD)FileSize; - } - - SetLastError(ERROR_INVALID_HANDLE); - return SFILE_INVALID_SIZE; -} - -DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) -{ - TMPQFile * hf = (TMPQFile *)hFile; - ULONGLONG FilePosition; - ULONGLONG MoveOffset; - DWORD dwFilePosHi; - - // If the hFile is not a valid file handle, return an error. - if(!IsValidFileHandle(hFile)) - { - SetLastError(ERROR_INVALID_HANDLE); - return SFILE_INVALID_POS; - } - - // Get the relative point where to move from - switch(dwMoveMethod) - { - case FILE_BEGIN: - FilePosition = 0; break; - case FILE_CURRENT: - if(hf->pStream != NULL) + case SFileMpqHetHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileStream_GetPos(hf->pStream, &FilePosition); + pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->HetTablePos64, (size_t)ha->pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE); + cbSrcFileInfo = sizeof(TMPQHetHeader); + nInfoType = SFILE_INFO_TYPE_ALLOCATED; } - else + break; + + case SFileMpqHetTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FilePosition = hf->dwFilePos; + pvSrcFileInfo = LoadHetTable(ha); + cbSrcFileInfo = sizeof(void *); + nInfoType = SFILE_INFO_TYPE_TABLE_POINTER; } break; - case FILE_END: - if(hf->pStream != NULL) + case SFileMpqBetTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FileStream_GetSize(hf->pStream, &FilePosition); + pvSrcFileInfo = &ha->pHeader->BetTablePos64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - else + break; + + case SFileMpqBetTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - FilePosition = SFileGetFileSize(hFile, NULL); + pvSrcFileInfo = &ha->pHeader->BetTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - default: - SetLastError(ERROR_INVALID_PARAMETER); - return SFILE_INVALID_POS; - } - - // Now get the move offset. Note that both values form - // a signed 64-bit value (a file pointer can be moved backwards) - if(plFilePosHigh != NULL) - dwFilePosHi = *plFilePosHigh; - else - dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0; - MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos); - - // Now calculate the new file pointer - // Do not allow the file pointer to go before the begin of the file - FilePosition += MoveOffset; - if(FilePosition < 0) - FilePosition = 0; - - // Now apply the file pointer to the file - if(hf->pStream != NULL) - { - // Apply the new file position - if(!FileStream_Read(hf->pStream, &FilePosition, NULL, 0)) - return SFILE_INVALID_POS; - - // Return the new file position - if(plFilePosHigh != NULL) - *plFilePosHigh = (LONG)(FilePosition >> 32); - return (DWORD)FilePosition; - } - else - { - // Files in MPQ can't be bigger than 4 GB. - // We don't allow to go past 4 GB - if(FilePosition >> 32) - { - SetLastError(ERROR_INVALID_PARAMETER); - return SFILE_INVALID_POS; - } - - // Change the file position - hf->dwFilePos = (DWORD)FilePosition; - - // Return the new file position - if(plFilePosHigh != NULL) - *plFilePosHigh = 0; - return (DWORD)FilePosition; - } -} - -//----------------------------------------------------------------------------- -// Tries to retrieve the file name + case SFileMpqBetHeader: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->BetTablePos64, (size_t)ha->pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE); + if(pvSrcFileInfo != NULL) + { + // It is allowed for the caller to only require BET header. + cbSrcFileInfo = sizeof(TMPQBetHeader) + ((TMPQBetHeader *)pvSrcFileInfo)->dwFlagCount * sizeof(DWORD); + if(cbFileInfo == sizeof(TMPQBetHeader)) + cbSrcFileInfo = sizeof(TMPQBetHeader); + nInfoType = SFILE_INFO_TYPE_ALLOCATED; + } + } + break; -struct TFileHeader2Ext -{ - DWORD dwOffset00Data; // Required data at offset 00 (32-bits) - DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored - DWORD dwOffset04Data; // Required data at offset 04 (32-bits) - DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored - const char * szExt; // Supplied extension, if the condition is true -}; + case SFileMpqBetTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = LoadBetTable(ha); + cbSrcFileInfo = sizeof(void *); + nInfoType = SFILE_INFO_TYPE_TABLE_POINTER; + } + break; -static TFileHeader2Ext data2ext[] = -{ - {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files - {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files - {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') - {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' - {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' - {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) - {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I - {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II - {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' - {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM - {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files - {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures - {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) - {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files - {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps - {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' - {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 - {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc - {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders - {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image - {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension - {0, 0, 0, 0, NULL} // Terminator -}; + case SFileMpqHashTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + Int64Value = MAKE_OFFSET64(ha->pHeader->wHashTablePosHi, ha->pHeader->dwHashTablePos); + pvSrcFileInfo = &Int64Value; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; -static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file - DWORD dwBytesRead = 0; - DWORD dwFilePos; // Saved file position + case SFileMpqHashTableSize64: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->HashTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Read the first 2 DWORDs bytes from the file - dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); - SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); - SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); + case SFileMpqHashTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwHashTableSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // If we read at least 8 bytes - if(dwBytesRead == sizeof(FirstBytes)) - { - // Make sure that the array is properly BSWAP-ed - BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); + case SFileMpqHashTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); + pvSrcFileInfo = ha->pHashTable; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Try to guess file extension from those 2 DWORDs - for(size_t i = 0; data2ext[i].szExt != NULL; i++) - { - if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && - (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) + case SFileMpqBlockTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) { - char szPseudoName[20] = ""; + Int64Value = MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos); + pvSrcFileInfo = &Int64Value; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Format the pseudo-name - sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); + case SFileMpqBlockTableSize64: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->BlockTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Save the pseudo-name in the file entry as well - AllocateFileName(hf->ha, pFileEntry, szPseudoName); + case SFileMpqBlockTableSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // If the caller wants to copy the file name, do it - if(szFileName != NULL) - strcpy(szFileName, szPseudoName); - return ERROR_SUCCESS; + case SFileMpqBlockTable: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + cbSrcFileInfo = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); + if(cbFileInfo >= cbSrcFileInfo) + pvSrcFileInfo = LoadBlockTable(ha, true); + nInfoType = SFILE_INFO_TYPE_ALLOCATED; } - } - } + break; - return ERROR_NOT_SUPPORTED; -} + case SFileMpqHiBlockTableOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->HiBlockTablePos64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; -bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - TCHAR * szFileNameT; - int nError = ERROR_INVALID_HANDLE; + case SFileMpqHiBlockTableSize64: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->HiBlockTableSize64; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // Pre-zero the output buffer - if(szFileName != NULL) - *szFileName = 0; + case SFileMpqHiBlockTable: + assert(false); + break; - // Check valid parameters - if(IsValidFileHandle(hFile)) - { - TFileEntry * pFileEntry = hf->pFileEntry; + case SFileMpqSignatures: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) + { + pvSrcFileInfo = &SignatureInfo.SignatureTypes; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; - // For MPQ files, retrieve the file name from the file entry - if(hf->pStream == NULL) - { - if(pFileEntry != NULL) + case SFileMpqStrongSignatureOffset: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) { - // If the file name is not there yet, create a pseudo name - if(pFileEntry->szFileName == NULL) + // Is a strong signature present? + if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG) { - nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); + pvSrcFileInfo = &SignatureInfo.EndMpqData; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } - else + } + break; + + case SFileMpqStrongSignatureSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) + { + // Is a strong signature present? + if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG) { - if(szFileName != NULL) - strcpy(szFileName, pFileEntry->szFileName); - nError = ERROR_SUCCESS; + dwInt32Value = MPQ_STRONG_SIGNATURE_SIZE + 4; + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } } - } + break; - // For local files, copy the file name from the stream - else - { - if(szFileName != NULL) + case SFileMpqStrongSignature: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) { - szFileNameT = FileStream_GetFileName(hf->pStream); - CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT)); + // Is a strong signature present? + if(SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG) + { + pvSrcFileInfo = SignatureInfo.Signature; + cbSrcFileInfo = MPQ_STRONG_SIGNATURE_SIZE + 4; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } } - nError = ERROR_SUCCESS; - } - } - - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Retrieves an information about an archive or about a file within the archive -// -// hMpqOrFile - Handle to an MPQ archive or to a file -// dwInfoType - Information to obtain - -bool WINAPI SFileGetFileInfo( - HANDLE hMpqOrFile, - DWORD dwInfoType, - void * pvFileInfo, - DWORD cbFileInfo, - LPDWORD pcbLengthNeeded) -{ - TMPQArchive * ha = NULL; - TMPQBlock * pBlockTable = NULL; - ULONGLONG Int64Value = 0; - TMPQFile * hf = NULL; - TCHAR * szPatchChain = NULL; - void * pvSrcFileInfo = NULL; - DWORD cbSrcFileInfo = 0; - DWORD dwInt32Value = 0; - int nError = ERROR_INVALID_PARAMETER; + break; - switch(dwInfoType) - { - case SFILE_INFO_ARCHIVE_NAME: + case SFileMpqBitmapOffset: ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) + if(ha != NULL && ha->pBitmap != NULL) { - pvSrcFileInfo = FileStream_GetFileName(ha->pStream); - cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); + Int64Value = MAKE_OFFSET64(ha->pBitmap->dwMapOffsetHi, ha->pBitmap->dwMapOffsetLo); + pvSrcFileInfo = &Int64Value; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive + case SFileMpqBitmapSize: ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) + if(ha != NULL && ha->pBitmap != NULL) { - pvSrcFileInfo = &ha->pHeader->dwArchiveSize; + pvSrcFileInfo = &ha->dwBitmapSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ + case SFileMpqBitmap: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->dwMaxFileCount; - cbSrcFileInfo = sizeof(DWORD); + pvSrcFileInfo = ha->pBitmap; + cbSrcFileInfo = ha->dwBitmapSize; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table + case SFileMpqArchiveSize64: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->pHeader->dwHashTableSize; + pvSrcFileInfo = &ha->pHeader->ArchiveSize64; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table + case SFileMpqArchiveSize: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; + pvSrcFileInfo = &ha->pHeader->dwArchiveSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_SECTOR_SIZE: + case SFileMpqMaxFileCount: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = &ha->dwSectorSize; + pvSrcFileInfo = &ha->dwMaxFileCount; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_HASH_TABLE: + case SFileMpqFileTableSize: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = ha->pHashTable; - cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); + pvSrcFileInfo = &ha->dwFileTableSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_BLOCK_TABLE: + case SFileMpqSectorSize: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { - pvSrcFileInfo = pBlockTable = TranslateBlockTable(ha, &Int64Value, NULL); - cbSrcFileInfo = (DWORD)(Int64Value / sizeof(TMPQBlock)); + pvSrcFileInfo = &ha->dwSectorSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_NUM_FILES: + case SFileMpqNumberOfFiles: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); dwInt32Value = GetMpqFileCount(ha); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileMpqRawChunkSize: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwRawChunkSize; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_STREAM_FLAGS: + case SFileMpqStreamFlags: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { FileStream_GetFlags(ha->pStream, &dwInt32Value); pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_IS_READ_ONLY: + case SFileMpqIsReadOnly: ha = IsValidMpqHandle(hMpqOrFile); if(ha != NULL) { + dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_HASH_INDEX: + case SFileInfoPatchChain: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + return GetFilePatchChain(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded); + break; + + case SFileInfoFileEntry: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->pFileEntry != NULL) + { + pvSrcFileInfo = pFileEntry = hf->pFileEntry; + cbSrcFileInfo = sizeof(TFileEntry); + if(pFileEntry->szFileName != NULL) + cbSrcFileInfo += (DWORD)strlen(pFileEntry->szFileName) + 1; + nInfoType = SFILE_INFO_TYPE_FILE_ENTRY; + } + break; + + case SFileInfoHashEntry: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) + { + pvSrcFileInfo = hf->ha->pHashTable + hf->pFileEntry->dwHashIndex; + cbSrcFileInfo = sizeof(TMPQHash); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileInfoHashIndex: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_CODENAME1: + case SFileInfoNameHash1: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) { pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_CODENAME2: + case SFileInfoNameHash2: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) { pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileInfoNameHash3: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->pFileEntry != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->FileNameHash; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_LOCALEID: + case SFileInfoLocale: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { + dwInt32Value = hf->pFileEntry->lcLocale; pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = hf->pFileEntry->lcLocale; + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_BLOCKINDEX: + case SFileInfoFileIndex: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL && hf->ha != NULL) { + dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; + } + break; + + case SFileInfoByteOffset: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->pFileEntry != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->ByteOffset; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_FILE_SIZE: + case SFileInfoFileTime: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->dwFileSize; - cbSrcFileInfo = sizeof(DWORD); + pvSrcFileInfo = &hf->pFileEntry->FileTime; + cbSrcFileInfo = sizeof(ULONGLONG); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_COMPRESSED_SIZE: + case SFileInfoFileSize: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; + pvSrcFileInfo = &hf->pFileEntry->dwFileSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_FLAGS: + case SFileInfoCompressedSize: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->dwFlags; + pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_POSITION: + case SFileInfoFlags: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { - pvSrcFileInfo = &hf->pFileEntry->ByteOffset; - cbSrcFileInfo = sizeof(ULONGLONG); + pvSrcFileInfo = &hf->pFileEntry->dwFlags; + cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_KEY: + case SFileInfoEncryptionKey: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { pvSrcFileInfo = &hf->dwFileKey; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_KEY_UNFIXED: + case SFileInfoEncryptionKeyRaw: hf = IsValidFileHandle(hMpqOrFile); if(hf != NULL) { @@ -1290,52 +750,222 @@ bool WINAPI SFileGetFileInfo( dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos; pvSrcFileInfo = &dwInt32Value; cbSrcFileInfo = sizeof(DWORD); + nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; } break; - case SFILE_INFO_FILETIME: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->FileTime; - cbSrcFileInfo = sizeof(ULONGLONG); - } - break; - - case SFILE_INFO_PATCH_CHAIN: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL) - pvSrcFileInfo = szPatchChain = GetFilePatchChain(hf, &cbSrcFileInfo); - break; + default: // Invalid info class + SetLastError(ERROR_INVALID_PARAMETER); + return false; } - // Check if one of the cases case yielded a result - if(pvSrcFileInfo != NULL && pvFileInfo != NULL) + // If we validated the handle and info class, give as much info as possible + if(nInfoType != SFILE_INFO_TYPE_UNKNOWN) { - // Give the length needed + // Give the length needed, if wanted if(pcbLengthNeeded != NULL) pcbLengthNeeded[0] = cbSrcFileInfo; - // Verify if we have enough space in the output buffer - if(cbSrcFileInfo <= cbFileInfo) + // If the caller entered an output buffer, the output size must also be entered + if(pvFileInfo != NULL && cbFileInfo != 0) { - memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo); - nError = ERROR_SUCCESS; + // Check if there is enough space in the output buffer + if(cbSrcFileInfo <= cbFileInfo) + { + switch(nInfoType) + { + case SFILE_INFO_TYPE_DIRECT_POINTER: + case SFILE_INFO_TYPE_ALLOCATED: + memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo); + nError = ERROR_SUCCESS; + break; + + case SFILE_INFO_TYPE_READ_FROM_FILE: + if(FileStream_Read(ha->pStream, &ByteOffset, pvFileInfo, cbSrcFileInfo)) + nError = ERROR_SUCCESS; + break; + + case SFILE_INFO_TYPE_TABLE_POINTER: + *(void **)pvFileInfo = pvSrcFileInfo; + pvSrcFileInfo = NULL; + nError = ERROR_SUCCESS; + break; + + case SFILE_INFO_TYPE_FILE_ENTRY: + assert(pFileEntry != NULL); + ConvertFileEntryToSelfRelative((TFileEntry *)pvFileInfo, pFileEntry); + nError = ERROR_SUCCESS; + break; + } + } + else + { + nError = ERROR_INSUFFICIENT_BUFFER; + } } else { - nError = ERROR_INSUFFICIENT_BUFFER; + nError = ERROR_SUCCESS; } - } - // Free the allocated buffers, if any - if(szPatchChain != NULL) - STORM_FREE(szPatchChain); - if(pBlockTable != NULL) - STORM_FREE(pBlockTable); + // Free the file info if needed + if(nInfoType == SFILE_INFO_TYPE_ALLOCATED && pvSrcFileInfo != NULL) + STORM_FREE(pvSrcFileInfo); + if(nInfoType == SFILE_INFO_TYPE_TABLE_POINTER && pvSrcFileInfo != NULL) + SFileFreeFileInfo(pvSrcFileInfo, InfoClass); + } // Set the last error value, if needed if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); } + +bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass) +{ + switch(InfoClass) + { + case SFileMpqHetTable: + FreeHetTable((TMPQHetTable *)pvFileInfo); + return true; + + case SFileMpqBetTable: + FreeBetTable((TMPQBetTable *)pvFileInfo); + return true; + } + + SetLastError(ERROR_INVALID_PARAMETER); + return false; +} + +//----------------------------------------------------------------------------- +// Tries to retrieve the file name + +struct TFileHeader2Ext +{ + DWORD dwOffset00Data; // Required data at offset 00 (32-bits) + DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored + DWORD dwOffset04Data; // Required data at offset 04 (32-bits) + DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored + const char * szExt; // Supplied extension, if the condition is true +}; + +static TFileHeader2Ext data2ext[] = +{ + {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files + {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files + {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') + {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' + {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' + {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) + {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I + {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II + {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' + {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM + {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files + {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures + {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) + {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files + {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps + {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' + {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 + {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc + {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders + {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image + {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension + {0, 0, 0, 0, NULL} // Terminator +}; + +static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) +{ + TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle + DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file + DWORD dwBytesRead = 0; + DWORD dwFilePos; // Saved file position + + // Read the first 2 DWORDs bytes from the file + dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); + SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); + SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); + + // If we read at least 8 bytes + if(dwBytesRead == sizeof(FirstBytes)) + { + // Make sure that the array is properly BSWAP-ed + BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); + + // Try to guess file extension from those 2 DWORDs + for(size_t i = 0; data2ext[i].szExt != NULL; i++) + { + if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && + (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) + { + char szPseudoName[20] = ""; + + // Format the pseudo-name + sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); + + // Save the pseudo-name in the file entry as well + AllocateFileName(hf->ha, pFileEntry, szPseudoName); + + // If the caller wants to copy the file name, do it + if(szFileName != NULL) + strcpy(szFileName, szPseudoName); + return ERROR_SUCCESS; + } + } + } + + return ERROR_NOT_SUPPORTED; +} + +bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) +{ + TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle + int nError = ERROR_INVALID_HANDLE; + + // Pre-zero the output buffer + if(szFileName != NULL) + *szFileName = 0; + + // Check valid parameters + if(IsValidFileHandle(hFile)) + { + TFileEntry * pFileEntry = hf->pFileEntry; + + // For MPQ files, retrieve the file name from the file entry + if(hf->pStream == NULL) + { + if(pFileEntry != NULL) + { + // If the file name is not there yet, create a pseudo name + if(pFileEntry->szFileName == NULL) + { + nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); + } + else + { + if(szFileName != NULL) + strcpy(szFileName, pFileEntry->szFileName); + nError = ERROR_SUCCESS; + } + } + } + + // For local files, copy the file name from the stream + else + { + if(szFileName != NULL) + { + const TCHAR * szStreamName = FileStream_GetFileName(hf->pStream); + CopyFileName(szFileName, szStreamName, _tcslen(szStreamName)); + } + nError = ERROR_SUCCESS; + } + } + + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return (nError == ERROR_SUCCESS); +} + diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp index 2044125..f98c92b 100644 --- a/src/SFileListFile.cpp +++ b/src/SFileListFile.cpp @@ -35,6 +35,18 @@ struct TListFileCache //----------------------------------------------------------------------------- // Local functions (cache) +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 FreeListFileCache(TListFileCache * pCache) { // Valid parameter check @@ -216,19 +228,91 @@ static int CompareFileNodes(const void * p1, const void * p2) return _stricmp(szFileName1, szFileName2); } -static int WriteListFileLine( - TMPQFile * hf, - const char * szLine) +static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile) { - char szNewLine[2] = {0x0D, 0x0A}; - size_t nLength = strlen(szLine); - int nError; + 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; - nError = SFileAddFile_Write(hf, szLine, (DWORD)nLength, MPQ_COMPRESSION_ZLIB); - if(nError != ERROR_SUCCESS) - return nError; + // 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)) + { + 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); + } + } + + // Free the sort table + STORM_FREE(SortTable); - return SFileAddFile_Write(hf, szNewLine, sizeof(szNewLine), MPQ_COMPRESSION_ZLIB); + // Give away the listfile + if(pcbListFile != NULL) + *pcbListFile = (DWORD)cbListFile; + return (LPBYTE)szListFile; } //----------------------------------------------------------------------------- @@ -252,7 +336,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil if(pFileEntry != NULL) { // Allocate file name for the file entry - AllocateFileName(pFileEntry, szFileName); + AllocateFileName(ha, pFileEntry, szFileName); bNameEntryCreated = true; } @@ -272,7 +356,7 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { // Allocate file name for the file entry - AllocateFileName(ha->pFileTable + pHash->dwBlockIndex, szFileName); + AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName); bNameEntryCreated = true; } @@ -284,123 +368,65 @@ static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFil return ERROR_CAN_NOT_COMPLETE; } -// Saves the whole listfile into the MPQ. +// Saves the whole listfile to the MPQ int SListFileSaveToMpq(TMPQArchive * ha) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; TMPQFile * hf = NULL; - char * szPrevItem; - char ** SortTable = NULL; - DWORD dwFileSize = 0; - size_t nFileNodes = 0; - size_t i; + LPBYTE pbListFile; + DWORD cbListFile = 0; int nError = ERROR_SUCCESS; - // Allocate the table for sorting listfile - SortTable = STORM_ALLOC(char*, ha->dwFileTableSize); - if(SortTable == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + // Only save (listfile) if we should do so + if(ha->dwFileFlags1 == 0 || ha->dwMaxFileCount == 0) + return ERROR_SUCCESS; - // Construct the sort table - // Note: in MPQs with multiple locale versions of the same file, - // this code causes adding multiple listfile entries. - // Since those MPQs were last time used in Starcraft, - // we leave it as it is. - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Only take existing items - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) - { - // Ignore pseudo-names - if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName)) - { - SortTable[nFileNodes++] = pFileEntry->szFileName; - } - } - } + // At this point, we expect to have at least one reserved entry in the file table + assert(ha->dwReservedFiles >= 1); + + // 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); - // Sort the table - qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes); + // Now we decrement the number of reserved files. + // This frees one slot in the file table, so the subsequent file create operation should succeed + // This must happen even if the listfile cannot be created + ha->dwReservedFiles--; - // Now parse the table of file names again - remove duplicates - // and count file size. - if(nFileNodes != 0) + // If the listfile create succeeded, we write it to the MPQ + if(pbListFile != NULL) { - // Count the 0-th item - dwFileSize += (DWORD)strlen(SortTable[0]) + 2; - szPrevItem = SortTable[0]; - - // Count all next items - for(i = 1; i < nFileNodes; i++) - { - // If the item is different from the previous one, include its size to the file size - if(_stricmp(SortTable[i], szPrevItem)) - { - dwFileSize += (DWORD)strlen(SortTable[i]) + 2; - szPrevItem = SortTable[i]; - } - } + // We expect it to be nonzero size + assert(cbListFile != 0); - // Determine the flags for (listfile) - if(ha->dwFileFlags1 == 0) - ha->dwFileFlags1 = GetDefaultSpecialFileFlags(ha, dwFileSize); + // Determine the real flags for (listfile) + if(ha->dwFileFlags1 == MPQ_FILE_EXISTS) + ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion); // Create the listfile in the MPQ nError = SFileAddFile_Init(ha, LISTFILE_NAME, 0, - dwFileSize, + cbListFile, LANG_NEUTRAL, ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING, &hf); - // Add all file names + + // Write the listfile raw data to it if(nError == ERROR_SUCCESS) { - // Each name is followed by newline ("\x0D\x0A") - szPrevItem = SortTable[0]; - nError = WriteListFileLine(hf, SortTable[0]); + // Write the content of the listfile to the MPQ + nError = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB); + SFileAddFile_Finish(hf); - // Count all next items - for(i = 1; i < nFileNodes; i++) - { - // If the item is the same like the last one, skip it - if(_stricmp(SortTable[i], szPrevItem)) - { - WriteListFileLine(hf, SortTable[i]); - szPrevItem = SortTable[i]; - } - } + // Clear the invalidate flag + ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID; } - } - else - { - // Create the listfile in the MPQ - dwFileSize = (DWORD)strlen(LISTFILE_NAME) + 2; - nError = SFileAddFile_Init(ha, LISTFILE_NAME, - 0, - dwFileSize, - LANG_NEUTRAL, - MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING, - &hf); - // Just add "(listfile)" there - if(nError == ERROR_SUCCESS) - { - WriteListFileLine(hf, LISTFILE_NAME); - } + // Free the listfile buffer + STORM_FREE(pbListFile); } - // Finalize the file in the MPQ - if(hf != NULL) - { - SFileAddFile_Finish(hf); - } - - // Free buffers - if(nError == ERROR_SUCCESS) - ha->dwFlags &= ~MPQ_FLAG_LISTFILE_INVALID; - if(SortTable != NULL) - STORM_FREE(SortTable); return nError; } diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp index bf8d19e..b05cff3 100644 --- a/src/SFileOpenArchive.cpp +++ b/src/SFileOpenArchive.cpp @@ -32,6 +32,33 @@ static bool IsAviFile(void * pvFileBegin) return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C); } +static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData) +{ + TMPQUserData * pUserData; + + // BSWAP the source data and copy them to our buffer + BSWAP_ARRAY32_UNSIGNED(&pvUserData, sizeof(TMPQUserData)); + pUserData = (TMPQUserData *)pvUserData; + + // Check the sizes + if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs) + { + // Move to the position given by the userdata + ByteOffset += pUserData->dwHeaderOffs; + + // The MPQ header should be within range of the file size + if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize) + { + // Note: We should verify if there is the MPQ header. + // However, the header could be at any position below that + // that is multiplier of 0x200 + return (TMPQUserData *)pvUserData; + } + } + + return NULL; +} + static TFileBitmap * CreateFileBitmap(TMPQArchive * ha, TMPQBitmap * pMpqBitmap, bool bFileIsComplete) { TFileBitmap * pBitmap; @@ -144,6 +171,7 @@ bool WINAPI SFileOpenArchive( DWORD dwFlags, HANDLE * phMpq) { + TMPQUserData * pUserData; TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle TFileEntry * pFileEntry; @@ -166,11 +194,18 @@ bool WINAPI SFileOpenArchive( if(pStream == NULL) nError = GetLastError(); } - - // Allocate the MPQhandle + + // Check the file size. There must be at least 0x20 bytes if(nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, &FileSize); + if(FileSize < MPQ_HEADER_SIZE_V1) + nError = ERROR_FILE_CORRUPT; + } + + // Allocate the MPQhandle + if(nError == ERROR_SUCCESS) + { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } @@ -219,18 +254,18 @@ bool WINAPI SFileOpenArchive( // If there is the MPQ user data signature, process it dwHeaderID = BSWAP_INT32_UNSIGNED(*(LPDWORD)ha->HeaderData); - if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL) + if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { - // Ignore the MPQ user data completely if the caller wants to open the MPQ as V1.0 - if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) + // Verify if this looks like a valid user data + pUserData = IsValidMpqUserData(SearchPos, FileSize, ha->HeaderData); + if(pUserData != NULL) { // Fill the user data header + ha->UserDataPos = SearchPos; ha->pUserData = &ha->UserData; - memcpy(ha->pUserData, ha->HeaderData, sizeof(TMPQUserData)); - BSWAP_TMPQUSERDATA(ha->pUserData); + memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); - // Remember the position of the user data and continue search - ha->UserDataPos = SearchPos; + // Continue searching from that position SearchPos += ha->pUserData->dwHeaderOffs; continue; } @@ -266,9 +301,11 @@ bool WINAPI SFileOpenArchive( // Did we identify one of the supported headers? if(nError == ERROR_SUCCESS) { - // Set the position of user data, header and file offset of the header + // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) ha->UserDataPos = SearchPos; + + // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchPos; @@ -285,7 +322,7 @@ bool WINAPI SFileOpenArchive( // DumpMpqHeader(ha->pHeader); // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, - // and probably ignores the MPQ format version as well. The trick is to + // and ignores the MPQ format version as well. The trick is to // fake MPQ format 2, with an improper hi-word position of hash table and block table // We can overcome such protectors by forcing opening the archive as MPQ v 1.0 if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1) @@ -336,13 +373,13 @@ bool WINAPI SFileOpenArchive( // the block table, BET table, hi-block table, (attributes) and (listfile). if(nError == ERROR_SUCCESS) { - nError = BuildFileTable(ha, FileSize); + nError = BuildFileTable(ha); } // Verify the file table, if no kind of protection was detected if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0) { - TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize; + TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; ULONGLONG RawFilePos; // Parse all file entries @@ -379,10 +416,11 @@ bool WINAPI SFileOpenArchive( // Save the flags for (listfile) pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) + { + // Ignore result of the operation. (listfile) is optional. + SFileAddListFile((HANDLE)ha, NULL); ha->dwFileFlags1 = pFileEntry->dwFlags; - - // Ignore result of the operation. (listfile) is optional. - SFileAddListFile((HANDLE)ha, NULL); + } } // Load the "(attributes)" file and merge it to the file table @@ -391,10 +429,11 @@ bool WINAPI SFileOpenArchive( // Save the flags for (attributes) pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); if(pFileEntry != NULL) + { + // Ignore result of the operation. (attributes) is optional. + SAttrLoadAttributes(ha); ha->dwFileFlags2 = pFileEntry->dwFlags; - - // Ignore result of the operation. (attributes) is optional. - SAttrLoadAttributes(ha); + } } // Cleanup and exit @@ -436,12 +475,15 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq) int nError; // Do nothing if 'hMpq' is bad parameter - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { SetLastError(ERROR_INVALID_HANDLE); return false; } + // Indicate that we are saving MPQ internal structures + ha->dwFlags |= MPQ_FLAG_SAVING_TABLES; + // If the (listfile) has been invalidated, save it if(ha->dwFlags & MPQ_FLAG_LISTFILE_INVALID) { @@ -466,6 +508,9 @@ bool WINAPI SFileFlushArchive(HANDLE hMpq) nResultError = nError; } + // We are no longer saving internal MPQ structures + ha->dwFlags &= ~MPQ_FLAG_SAVING_TABLES; + // Return the error if(nResultError != ERROR_SUCCESS) SetLastError(nResultError); diff --git a/src/SFileOpenFileEx.cpp b/src/SFileOpenFileEx.cpp index 4180cd7..c2cf5d4 100644 --- a/src/SFileOpenFileEx.cpp +++ b/src/SFileOpenFileEx.cpp @@ -16,11 +16,19 @@ /* Local functions */ /*****************************************************************************/ -static const char * GetPrefixedName(TMPQArchive * ha, const char * szFileName, char * szBuffer) +static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer) { if(ha->cchPatchPrefix != 0) { + // Copy the patch prefix memcpy(szBuffer, ha->szPatchPrefix, ha->cchPatchPrefix); + + // 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; + + // Copy the rest of the name strcpy(szBuffer + ha->cchPatchPrefix, szFileName); szFileName = szBuffer; } @@ -61,10 +69,11 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile) bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HANDLE * phFile) { + TMPQArchive * haBase = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; + TFileEntry * pFileEntry; TMPQFile * hfPatch; // Pointer to patch file TMPQFile * hfBase = NULL; // Pointer to base open file - TMPQFile * hfLast = NULL; // The highest file in the chain that is not patch file TMPQFile * hf = NULL; HANDLE hPatchFile; char szPrefixBuffer[MAX_PATH]; @@ -72,66 +81,52 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN // Keep this flag here for future updates dwReserved = dwReserved; - // First of all, try to open the original version of the file in any of the patch chain + // 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) { - // Prepare the file name with a correct prefix - if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) - { - // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE - if((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - { - hf = hfLast = hfBase; - break; - } - - SFileCloseFile((HANDLE)hfBase); - } + // If the file is there, then we remember the archive + pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), 0); + if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + haBase = ha; - // Move to the next file in the patch chain + // Move to the patch archive ha = ha->haPatch; } - // If we couldn't find the file in any of the patches, it doesn't exist - if(hf == NULL) + // If we couldn't find the base file in any of the patches, it doesn't exist + if((ha = haBase) == NULL) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } - // Now keep going in the patch chain and open every patch file that is there - for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) + // Now open the base file + if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) { - // Prepare the file name with a correct prefix - if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) + // 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) { - // Remember the new version - hfPatch = (TMPQFile *)hPatchFile; + // Prepare the file name with a correct prefix + if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) + { + // Remember the new version + hfPatch = (TMPQFile *)hPatchFile; - // If we encountered a full replacement of the file, - // we have to remember the highest full file - if((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - hfLast = hfPatch; + // We should not find patch file + assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0); - // Set current patch to base file and move on - hf->hfPatchFile = hfPatch; - hf = hfPatch; + // Attach the patch to the base file + hf->hfPatch = hfPatch; + hf = hfPatch; + } } } - // Now we need to free all files that are below the highest unpatched version - while(hfBase != hfLast) - { - TMPQFile * hfNext = hfBase->hfPatchFile; - - // Free the file below - hfBase->hfPatchFile = NULL; - FreeMPQFile(hfBase); - - // Move the base to the next file - hfBase = hfNext; - } - // Give the updated base MPQ if(phFile != NULL) *phFile = (HANDLE)hfBase; @@ -163,7 +158,7 @@ int WINAPI SFileEnumLocales( DWORD dwLocales = 0; // Test the parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) return ERROR_INVALID_PARAMETER; @@ -236,7 +231,7 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) bool bIsPseudoName; int nError = ERROR_SUCCESS; - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0) nError = ERROR_INVALID_PARAMETER; @@ -251,7 +246,7 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) while(ha != NULL) { // Verify presence of the file - pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), lcFileLocale) + pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, GetPatchFileName(ha, szFileName, szPrefixBuffer), lcFileLocale) : GetFileEntryByIndex(ha, dwFileIndex); // Verify the file flags if(pFileEntry != NULL && (pFileEntry->dwFlags & dwFlagsToCheck) == MPQ_FILE_EXISTS) @@ -301,7 +296,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch case SFILE_OPEN_FROM_MPQ: case SFILE_OPEN_BASE_FILE: - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) { nError = ERROR_INVALID_HANDLE; break; @@ -369,6 +364,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch if(nError != ERROR_SUCCESS) { SetLastError(nError); + *phFile = NULL; return false; } } @@ -409,7 +405,7 @@ bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearch if(bOpenByIndex == false) { // If there is no file name yet, allocate it - AllocateFileName(pFileEntry, szFileName); + AllocateFileName(ha, pFileEntry, szFileName); // If the file is encrypted, we should detect the file key if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) @@ -454,7 +450,7 @@ bool WINAPI SFileCloseFile(HANDLE hFile) { TMPQFile * hf = (TMPQFile *)hFile; - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return false; diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp index bfb4100..7f67749 100644 --- a/src/SFilePatchArchives.cpp +++ b/src/SFilePatchArchives.cpp @@ -34,7 +34,7 @@ static bool GetDefaultPatchPrefix( const TCHAR * szDash; // Ensure that both names are plain names - szBaseMpqName = GetPlainFileNameT(szBaseMpqName); + szBaseMpqName = GetPlainFileName(szBaseMpqName); // Patch prefix is for the Cataclysm MPQs, whose names // are like "locale-enGB.MPQ" or "speech-enGB.MPQ" @@ -431,7 +431,7 @@ int PatchFileData(TMPQFile * hf) int nError = ERROR_SUCCESS; // Move to the first patch - hf = hf->hfPatchFile; + hf = hf->hfPatch; // Now go through all patches and patch the original data while(hf != NULL) @@ -450,7 +450,7 @@ int PatchFileData(TMPQFile * hf) break; // Move to the next patch - hf = hf->hfPatchFile; + hf = hf->hfPatch; } return nError; @@ -493,7 +493,7 @@ bool WINAPI SFileOpenPatchArchive( dwFlags = dwFlags; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(szPatchMpqName == NULL || *szPatchMpqName == 0) nError = ERROR_INVALID_PARAMETER; @@ -580,7 +580,7 @@ bool WINAPI SFileIsPatchedArchive(HANDLE hMpq) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return false; return (ha->haPatch != NULL); diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp index 6eb0d10..cfc8d2a 100644 --- a/src/SFileReadFile.cpp +++ b/src/SFileReadFile.cpp @@ -14,103 +14,8 @@ #include "StormCommon.h" //----------------------------------------------------------------------------- -// Local structures - -struct TFileHeader2Ext -{ - DWORD dwOffset00Data; // Required data at offset 00 (32-bits) - DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored - DWORD dwOffset04Data; // Required data at offset 04 (32-bits) - DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored - const char * szExt; // Supplied extension, if the condition is true -}; - -//----------------------------------------------------------------------------- // Local functions -static DWORD GetMpqFileCount(TMPQArchive * ha) -{ - TFileEntry * pFileTableEnd; - TFileEntry * pFileEntry; - DWORD dwFileCount = 0; - - // Go through all open MPQs, including patches - while(ha != NULL) - { - // Only count files that are not patch files - pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // If the file is patch file and this is not primary archive, skip it - // BUGBUG: This errorneously counts non-patch files that are in both - // base MPQ and in patches, and increases the number of files by cca 50% - if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS) - dwFileCount++; - } - - // Move to the next patch archive - ha = ha->haPatch; - } - - return dwFileCount; -} - -static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded) -{ - TMPQFile * hfTemp; - TCHAR * szPatchChain = (TCHAR *)pvFileInfo; - TCHAR * szFileName; - size_t cchCharsNeeded = 1; - size_t nLength; - DWORD cbLengthNeeded; - - // Check if the "hf" is a MPQ file - if(hf->pStream != NULL) - { - // Calculate the length needed - szFileName = FileStream_GetFileName(hf->pStream); - cchCharsNeeded += _tcslen(szFileName) + 1; - cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - - // If we have enough space, copy the file name - if(cbFileInfo >= cbLengthNeeded) - { - nLength = _tcslen(szFileName) + 1; - memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR)); - szPatchChain += nLength; - - // Terminate the multi-string - *szPatchChain = 0; - } - } - else - { - // Calculate number of characters needed - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - - // If we have enough space, the copy the patch chain - if(cbFileInfo >= cbLengthNeeded) - { - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - { - szFileName = FileStream_GetFileName(hfTemp->ha->pStream); - nLength = _tcslen(szFileName) + 1; - memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR)); - szPatchChain += nLength; - } - - // Terminate the multi-string - *szPatchChain = 0; - } - } - - // Give result length, terminate multi-string and return - *pcbLengthNeeded = cbLengthNeeded; - return true; -} - // hf - MPQ File handle. // pbBuffer - Pointer to target buffer to store sectors. // dwByteOffset - Position of sector in the file (relative to file begin) @@ -453,7 +358,12 @@ static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; + pbRawData = hf->pbFileSector; + } + // If the file sector is not loaded yet, do it + if(hf->dwSectorOffs != 0) + { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { @@ -750,7 +660,7 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD lpOverlapped = lpOverlapped; // Check valid parameters - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return false; @@ -769,7 +679,7 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD } // If the file is a patch file, we have to read it special way - else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + else if(hf->hfPatch != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) { nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); } @@ -819,13 +729,13 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) TMPQFile * hf = (TMPQFile *)hFile; // Validate the file handle before we go on - if(IsValidFileHandle(hf)) + if(IsValidFileHandle(hFile)) { // Make sure that the variable is initialized FileSize = 0; // If the file is patched file, we have to get the size of the last version - if(hf->hfPatchFile != NULL) + if(hf->hfPatch != NULL) { // Walk through the entire patch chain, take the last version while(hf != NULL) @@ -834,7 +744,7 @@ DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) FileSize = hf->pFileEntry->dwFileSize; // Move to the next patch file in the hierarchy - hf = hf->hfPatchFile; + hf = hf->hfPatch; } } else @@ -868,7 +778,7 @@ DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHi DWORD dwFilePosHi; // If the hFile is not a valid file handle, return an error. - if(!IsValidFileHandle(hf)) + if(!IsValidFileHandle(hFile)) { SetLastError(ERROR_INVALID_HANDLE); return SFILE_INVALID_POS; @@ -954,367 +864,3 @@ DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHi } } -//----------------------------------------------------------------------------- -// Tries to retrieve the file name - -static TFileHeader2Ext data2ext[] = -{ - {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files - {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files - {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') - {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' - {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' - {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) - {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I - {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II - {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' - {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM - {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files - {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures - {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) - {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files - {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps - {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' - {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 - {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc - {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders - {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image - {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension - {0, 0, 0, 0, NULL} // Terminator -}; - -static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file - DWORD dwBytesRead = 0; - DWORD dwFilePos; // Saved file position - - // Read the first 2 DWORDs bytes from the file - dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); - SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); - SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); - - // If we read at least 8 bytes - if(dwBytesRead == sizeof(FirstBytes)) - { - // Make sure that the array is properly BSWAP-ed - BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); - - // Try to guess file extension from those 2 DWORDs - for(size_t i = 0; data2ext[i].szExt != NULL; i++) - { - if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && - (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) - { - char szPseudoName[20] = ""; - - // Format the pseudo-name - sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); - - // Save the pseudo-name in the file entry as well - AllocateFileName(pFileEntry, szPseudoName); - - // If the caller wants to copy the file name, do it - if(szFileName != NULL) - strcpy(szFileName, szPseudoName); - return ERROR_SUCCESS; - } - } - } - - return ERROR_NOT_SUPPORTED; -} - -bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - TCHAR * szFileNameT; - int nError = ERROR_INVALID_HANDLE; - - // Pre-zero the output buffer - if(szFileName != NULL) - *szFileName = 0; - - // Check valid parameters - if(IsValidFileHandle(hf)) - { - TFileEntry * pFileEntry = hf->pFileEntry; - - // For MPQ files, retrieve the file name from the file entry - if(hf->pStream == NULL) - { - if(pFileEntry != NULL) - { - // If the file name is not there yet, create a pseudo name - if(pFileEntry->szFileName == NULL) - { - nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); - } - else - { - if(szFileName != NULL) - strcpy(szFileName, pFileEntry->szFileName); - nError = ERROR_SUCCESS; - } - } - } - - // For local files, copy the file name from the stream - else - { - if(szFileName != NULL) - { - szFileNameT = FileStream_GetFileName(hf->pStream); - CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT)); - } - nError = ERROR_SUCCESS; - } - } - - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Retrieves an information about an archive or about a file within the archive -// -// hMpqOrFile - Handle to an MPQ archive or to a file -// dwInfoType - Information to obtain - -#define VERIFY_MPQ_HANDLE(h) \ - if(!IsValidMpqHandle(h)) \ - { \ - nError = ERROR_INVALID_HANDLE; \ - break; \ - } - -#define VERIFY_FILE_HANDLE(h) \ - if(!IsValidFileHandle(h)) \ - { \ - nError = ERROR_INVALID_HANDLE; \ - break; \ - } - -bool WINAPI SFileGetFileInfo( - HANDLE hMpqOrFile, - DWORD dwInfoType, - void * pvFileInfo, - DWORD cbFileInfo, - LPDWORD pcbLengthNeeded) -{ - TMPQArchive * ha = (TMPQArchive *)hMpqOrFile; - TMPQBlock * pBlock; - TMPQFile * hf = (TMPQFile *)hMpqOrFile; - void * pvSrcFileInfo = NULL; - DWORD cbLengthNeeded = 0; - DWORD dwIsReadOnly; - DWORD dwFileCount = 0; - DWORD dwFileIndex; - DWORD dwFileKey; - DWORD i; - int nError = ERROR_SUCCESS; - - switch(dwInfoType) - { - case SFILE_INFO_ARCHIVE_NAME: - VERIFY_MPQ_HANDLE(ha); - - // pvFileInfo receives the name of the archive, terminated by 0 - pvSrcFileInfo = FileStream_GetFileName(ha->pStream); - cbLengthNeeded = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); - break; - - case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pHeader->dwArchiveSize; - break; - - case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->dwMaxFileCount; - break; - - case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pHeader->dwHashTableSize; - break; - - case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; - break; - - case SFILE_INFO_SECTOR_SIZE: - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->dwSectorSize; - break; - - case SFILE_INFO_HASH_TABLE: - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); - pvSrcFileInfo = ha->pHashTable; - break; - - case SFILE_INFO_BLOCK_TABLE: - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = ha->dwFileTableSize * sizeof(TMPQBlock); - if(cbFileInfo < cbLengthNeeded) - { - nError = ERROR_INSUFFICIENT_BUFFER; - break; - } - - // Construct block table from file table size - pBlock = (TMPQBlock *)pvFileInfo; - for(i = 0; i < ha->dwFileTableSize; i++) - { - pBlock->dwFilePos = (DWORD)ha->pFileTable[i].ByteOffset; - pBlock->dwFSize = ha->pFileTable[i].dwFileSize; - pBlock->dwCSize = ha->pFileTable[i].dwCmpSize; - pBlock->dwFlags = ha->pFileTable[i].dwFlags; - pBlock++; - } - break; - - case SFILE_INFO_NUM_FILES: - VERIFY_MPQ_HANDLE(ha); - dwFileCount = GetMpqFileCount(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileCount; - break; - - case SFILE_INFO_STREAM_FLAGS: - VERIFY_MPQ_HANDLE(ha); - FileStream_GetFlags(ha->pStream, &dwFileKey); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileKey; - break; - - case SFILE_INFO_IS_READ_ONLY: - VERIFY_MPQ_HANDLE(ha); - dwIsReadOnly = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwIsReadOnly; - break; - - case SFILE_INFO_HASH_INDEX: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; - break; - - case SFILE_INFO_CODENAME1: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; - if(ha->pHashTable != NULL) - pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1; - break; - - case SFILE_INFO_CODENAME2: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - if(ha->pHashTable != NULL) - pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2; - break; - - case SFILE_INFO_LOCALEID: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->lcLocale; - break; - - case SFILE_INFO_BLOCKINDEX: - VERIFY_FILE_HANDLE(hf); - dwFileIndex = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileIndex; - break; - - case SFILE_INFO_FILE_SIZE: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwFileSize; - break; - - case SFILE_INFO_COMPRESSED_SIZE: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; - break; - - case SFILE_INFO_FLAGS: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->pFileEntry->dwFlags; - break; - - case SFILE_INFO_POSITION: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(ULONGLONG); - pvSrcFileInfo = &hf->pFileEntry->ByteOffset; - break; - - case SFILE_INFO_KEY: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &hf->dwFileKey; - break; - - case SFILE_INFO_KEY_UNFIXED: - VERIFY_FILE_HANDLE(hf); - dwFileKey = hf->dwFileKey; - if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) - dwFileKey = (dwFileKey ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos; - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &dwFileKey; - break; - - case SFILE_INFO_FILETIME: - VERIFY_FILE_HANDLE(hf); - cbLengthNeeded = sizeof(ULONGLONG); - pvSrcFileInfo = &hf->pFileEntry->FileTime; - break; - - case SFILE_INFO_PATCH_CHAIN: - VERIFY_FILE_HANDLE(hf); - GetFilePatchChain(hf, pvFileInfo, cbFileInfo, &cbLengthNeeded); - break; - - default: - nError = ERROR_INVALID_PARAMETER; - break; - } - - // If everything is OK so far, copy the information - if(nError == ERROR_SUCCESS) - { - // Is the output buffer large enough? - if(cbFileInfo >= cbLengthNeeded) - { - // Copy the data - if(pvSrcFileInfo != NULL) - memcpy(pvFileInfo, pvSrcFileInfo, cbLengthNeeded); - } - else - { - nError = ERROR_INSUFFICIENT_BUFFER; - } - - // Give the size to the caller - if(pcbLengthNeeded != NULL) - *pcbLengthNeeded = cbLengthNeeded; - } - - // Set the last error value, if needed - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp index 4509388..219e187 100644 --- a/src/SFileVerify.cpp +++ b/src/SFileVerify.cpp @@ -20,25 +20,8 @@ //----------------------------------------------------------------------------- // Local defines -#define SIGNATURE_TYPE_NONE 0 -#define SIGNATURE_TYPE_WEAK 1 -#define SIGNATURE_TYPE_STRONG 2 - #define MPQ_DIGEST_UNIT_SIZE 0x10000 -typedef struct _MPQ_SIGNATURE_INFO -{ - ULONGLONG BeginMpqData; // File offset where the hashing starts - ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) - ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) - ULONGLONG EndMpqData; // File offset where the hashing ends - ULONGLONG EndOfFile; // Size of the entire file - BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; - DWORD cbSignatureSize; // Length of the signature - int nSignatureType; // See SIGNATURE_TYPE_XXX - -} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; - //----------------------------------------------------------------------------- // Known Blizzard public keys // Created by Jean-Francois Roy using OpenSSL @@ -156,7 +139,7 @@ static void GetPlainAnsiFileName( const TCHAR * szFileName, char * szPlainName) { - const TCHAR * szPlainNameT = GetPlainFileNameT(szFileName); + const TCHAR * szPlainNameT = GetPlainFileName(szFileName); // Convert the plain name to ANSI while(*szPlainNameT != 0) @@ -186,67 +169,13 @@ static void CalculateArchiveRange( } } - // Get the MPQ data end. This is stored in our MPQ header, - // and it's been already prepared by SFileOpenArchive, + // Get the MPQ data end. This is stored in the MPQ header pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64; // Get the size of the entire file FileStream_GetSize(ha->pStream, &pSI->EndOfFile); } -static bool QueryMpqSignatureInfo( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI) -{ - ULONGLONG ExtraBytes; - TMPQFile * hf; - HANDLE hFile; - DWORD dwFileSize; - - // Calculate the range of the MPQ - CalculateArchiveRange(ha, pSI); - - // If there is "(signature)" file in the MPQ, it has a weak signature - if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile)) - { - // Get the content of the signature - SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL); - - // Verify the size of the signature - hf = (TMPQFile *)hFile; - - // We have to exclude the signature file from the digest - pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset; - pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize; - dwFileSize = hf->dwDataSize; - - // Close the file - SFileCloseFile(hFile); - pSI->nSignatureType = SIGNATURE_TYPE_WEAK; - return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false; - } - - // If there is extra bytes beyond the end of the archive, - // it's the strong signature - ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; - if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) - { - // Read the strong signature - if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) - return false; - - // Check the signature header "NGIS" - if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') - return false; - - pSI->nSignatureType = SIGNATURE_TYPE_STRONG; - return true; - } - - // Succeeded, but no known signature found - return true; -} - static bool CalculateMpqHashMd5( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI, @@ -723,7 +652,7 @@ static DWORD VerifyFile( if(dwTotalBytes == 0) { // Check CRC32 and MD5 only if there is no patches - if(hf->hfPatchFile == NULL) + if(hf->hfPatch == NULL) { // Check if the CRC32 matches. if(dwFlags & SFILE_VERIFY_FILE_CRC) @@ -781,6 +710,63 @@ static DWORD VerifyFile( return dwVerifyResult; } +// Used in SFileGetFileInfo +bool QueryMpqSignatureInfo( + TMPQArchive * ha, + PMPQ_SIGNATURE_INFO pSI) +{ + ULONGLONG ExtraBytes; + TMPQFile * hf; + HANDLE hFile; + DWORD dwFileSize; + + // Make sure it's all zeroed + memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO)); + + // Calculate the range of the MPQ + CalculateArchiveRange(ha, pSI); + + // If there is "(signature)" file in the MPQ, it has a weak signature + if(SFileOpenFileEx((HANDLE)ha, SIGNATURE_NAME, SFILE_OPEN_BASE_FILE, &hFile)) + { + // Get the content of the signature + SFileReadFile(hFile, pSI->Signature, sizeof(pSI->Signature), &pSI->cbSignatureSize, NULL); + + // Verify the size of the signature + hf = (TMPQFile *)hFile; + + // We have to exclude the signature file from the digest + pSI->BeginExclude = ha->MpqPos + hf->pFileEntry->ByteOffset; + pSI->EndExclude = pSI->BeginExclude + hf->pFileEntry->dwCmpSize; + dwFileSize = hf->dwDataSize; + + // Close the file + SFileCloseFile(hFile); + pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK; + return (dwFileSize == (MPQ_WEAK_SIGNATURE_SIZE + 8)) ? true : false; + } + + // If there is extra bytes beyond the end of the archive, + // it's the strong signature + ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; + if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) + { + // Read the strong signature + if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) + return false; + + // Check the signature header "NGIS" + if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') + return false; + + pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG; + return true; + } + + // Succeeded, but no known signature found + return true; +} + //----------------------------------------------------------------------------- // Public (exported) functions @@ -828,7 +814,7 @@ int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * sz TMPQHeader * pHeader; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return ERROR_INVALID_PARAMETER; pHeader = ha->pHeader; @@ -900,7 +886,7 @@ DWORD WINAPI SFileVerifyArchive(HANDLE hMpq) TMPQArchive * ha = (TMPQArchive *)hMpq; // Verify input parameters - if(!IsValidMpqHandle(ha)) + if(!IsValidMpqHandle(hMpq)) return ERROR_VERIFY_FAILED; // Get the MPQ signature and signature type @@ -908,18 +894,20 @@ DWORD WINAPI SFileVerifyArchive(HANDLE hMpq) if(!QueryMpqSignatureInfo(ha, &si)) return ERROR_VERIFY_FAILED; - // Verify the signature - switch(si.nSignatureType) - { - case SIGNATURE_TYPE_NONE: - return ERROR_NO_SIGNATURE; + // If there is no signature + if(si.SignatureTypes == 0) + return ERROR_NO_SIGNATURE; - case SIGNATURE_TYPE_WEAK: - return VerifyWeakSignature(ha, &si); + // We haven't seen a MPQ with both signatures + assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG); - case SIGNATURE_TYPE_STRONG: - return VerifyStrongSignature(ha, &si); - } + // Verify the strong signature, if present + if(si.SignatureTypes & SIGNATURE_TYPE_STRONG) + return VerifyStrongSignature(ha, &si); + + // Verify the weak signature, if present + if(si.SignatureTypes & SIGNATURE_TYPE_WEAK) + return VerifyWeakSignature(ha, &si); - return ERROR_VERIFY_FAILED; + return ERROR_NO_SIGNATURE; } diff --git a/src/StormCommon.h b/src/StormCommon.h index dcc9c7f..bfe99f7 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -61,9 +61,6 @@ #define ID_MPQ_FILE 0x46494c45 // Used internally for checking TMPQFile ('FILE') -#define MPQ_WEAK_SIGNATURE_SIZE 64 -#define MPQ_STRONG_SIGNATURE_SIZE 256 - // Prevent problems with CRT "min" and "max" functions, // as they are not defined on all platforms #define STORMLIB_MIN(a, b) ((a < b) ? a : b) @@ -71,39 +68,50 @@ #define STORMLIB_UNUSED(p) ((void)(p)) // Macro for building 64-bit file offset from two 32-bit -#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | lo) +#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo) + +//----------------------------------------------------------------------------- +// MPQ signature information + +// Size of each signature type +#define MPQ_WEAK_SIGNATURE_SIZE 64 +#define MPQ_STRONG_SIGNATURE_SIZE 256 + +// MPQ signature info +typedef struct _MPQ_SIGNATURE_INFO +{ + ULONGLONG BeginMpqData; // File offset where the hashing starts + ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) + ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) + ULONGLONG EndMpqData; // File offset where the hashing ends + ULONGLONG EndOfFile; // Size of the entire file + BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; + DWORD cbSignatureSize; // Length of the signature + DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX + +} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; //----------------------------------------------------------------------------- // Memory management // // We use our own macros for allocating/freeing memory. If you want -// to redefine them, please keep the following rules +// to redefine them, please keep the following rules: // // - The memory allocation must return NULL if not enough memory // (i.e not to throw exception) -// - It is not necessary to fill the allocated buffer with zeros -// - Memory freeing function doesn't have to test the pointer to NULL. +// - The allocating function does not need to fill the allocated buffer with zeros +// - Memory freeing function doesn't have to test the pointer to NULL // #if defined(_MSC_VER) && defined(_DEBUG) -__inline void * DebugMalloc(char * /* szFile */, int /* nLine */, size_t nSize) -{ -// return new BYTE[nSize]; - return HeapAlloc(GetProcessHeap(), 0, nSize); -} -__inline void DebugFree(void * ptr) -{ -// delete [] ptr; - HeapFree(GetProcessHeap(), 0, ptr); -} +#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type))) +#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr) -#define STORM_ALLOC(type, nitems) (type *)DebugMalloc(__FILE__, __LINE__, (nitems) * sizeof(type)) -#define STORM_FREE(ptr) DebugFree(ptr) #else -#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type)) -#define STORM_FREE(ptr) free(ptr) +#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type)) +#define STORM_FREE(ptr) free(ptr) #endif @@ -137,12 +145,7 @@ DWORD GetHashTableSizeForFileCount(DWORD dwFileCount); bool IsPseudoFileName(const char * szFileName, LPDWORD pdwFileIndex); ULONGLONG HashStringJenkins(const char * szFileName); -void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength); -void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength); - -int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); - -DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize); +DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion); void EncryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey); void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey); @@ -158,17 +161,26 @@ void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_ha //----------------------------------------------------------------------------- // Handle validation functions -bool IsValidMpqHandle(TMPQArchive * ha); -bool IsValidFileHandle(TMPQFile * hf); +TMPQArchive * IsValidMpqHandle(HANDLE hMpq); +TMPQFile * IsValidFileHandle(HANDLE hFile); //----------------------------------------------------------------------------- -// Hash table and block table manipulation +// Support for MPQ file tables + +int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale); TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName); TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash); TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry); -DWORD AllocateHetEntry(TMPQArchive * ha, TFileEntry * pFileEntry); + +TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey); +TMPQHetTable * LoadHetTable(TMPQArchive * ha); +TMPQBetTable * LoadBetTable(TMPQArchive * ha); + +TMPQHash * LoadHashTable(TMPQArchive * ha); +TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries = false); +TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable); ULONGLONG FindFreeMpqSpace(TMPQArchive * ha); @@ -178,10 +190,12 @@ int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComp // Functions that load the HET and BET tables int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize); int LoadAnyHashTable(TMPQArchive * ha); -int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize); +int BuildFileTable(TMPQArchive * ha); +int RebuildHetTable(TMPQArchive * ha); +int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize, DWORD dwNewMaxFileCount); int SaveMPQTables(TMPQArchive * ha); -TMPQHetTable * CreateHetTable(DWORD dwHashTableSize, DWORD dwFileCount, DWORD dwHashBitSize, bool bCreateEmpty); +TMPQHetTable * CreateHetTable(DWORD dwFileCount, DWORD dwHashBitSize, LPBYTE pbSrcData); void FreeHetTable(TMPQHetTable * pHetTable); TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount); @@ -194,18 +208,19 @@ TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID l TFileEntry * GetFileEntryByIndex(TMPQArchive * ha, DWORD dwIndex); // Allocates file name in the file entry -void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName); +void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName); // Allocates new file entry in the MPQ tables. Reuses existing, if possible -TFileEntry * FindFreeFileEntry(TMPQArchive * ha); TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale); int RenameFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szNewFileName); -void ClearFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); -int FreeFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); +void DeleteFileEntry(TMPQArchive * ha, TFileEntry * pFileEntry); // Invalidates entries for (listfile) and (attributes) void InvalidateInternalFiles(TMPQArchive * ha); +// Retrieves information about the strong signature +bool QueryMpqSignatureInfo(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSignatureInfo); + //----------------------------------------------------------------------------- // Support for alternate file formats (SBaseSubTypes.cpp) @@ -245,10 +260,14 @@ void FreeMPQArchive(TMPQArchive *& ha); // Utility functions bool CheckWildCard(const char * szString, const char * szWildCard); -const char * GetPlainFileNameA(const char * szFileName); -const TCHAR * GetPlainFileNameT(const TCHAR * szFileName); bool IsInternalMpqFileName(const char * szFileName); +const TCHAR * GetPlainFileName(const TCHAR * szFileName); +const char * GetPlainFileName(const char * szFileName); + +void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength); +void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength); + //----------------------------------------------------------------------------- // Support for adding files to the MPQ diff --git a/src/StormLib.h b/src/StormLib.h index 3f91478..ce65f7f 100644 --- a/src/StormLib.h +++ b/src/StormLib.h @@ -67,6 +67,7 @@ /* 26.04.12 8.10 Lad Support for data map, added SFileGetArchiveBitmap */ /* 29.05.12 8.20 Lad C-only interface */ /* 14.01.13 8.21 Lad ADPCM and Huffmann (de)compression refactored */ +/* 04.12.13 9.00 Lad Unit tests, bug fixes */ /*****************************************************************************/ #ifndef __STORMLIB_H__ @@ -131,8 +132,8 @@ extern "C" { //----------------------------------------------------------------------------- // Defines -#define STORMLIB_VERSION 0x0817 // Current version of StormLib (8.23) -#define STORMLIB_VERSION_STRING "8.23" // String version of StormLib version +#define STORMLIB_VERSION 0x0900 // Current version of StormLib (9.0) +#define STORMLIB_VERSION_STRING "9.00" // String version of StormLib version #define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A') #define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B') @@ -146,15 +147,15 @@ extern "C" { #define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ // Values for SFileCreateArchive -#define HASH_TABLE_SIZE_MIN 0x00000004 // Minimum acceptable hash table size +#define HASH_TABLE_SIZE_MIN 0x00000004 // Verified: If there is 1 file, hash table size is 4 #define HASH_TABLE_SIZE_DEFAULT 0x00001000 // Default hash table size for empty MPQs #define HASH_TABLE_SIZE_MAX 0x00080000 // Maximum acceptable hash table size #define HASH_ENTRY_DELETED 0xFFFFFFFE // Block index for deleted entry in the hash table #define HASH_ENTRY_FREE 0xFFFFFFFF // Block index for free entry in the hash table -#define HET_ENTRY_DELETED 0x80 // HET hash value for a deleted entry -#define HET_ENTRY_FREE 0x00 // HET hash value for free entry +#define HET_ENTRY_DELETED 0x80 // NameHash1 value for a deleted entry +#define HET_ENTRY_FREE 0x00 // NameHash1 value for free entry #define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure @@ -173,11 +174,11 @@ extern "C" { // Flags for TMPQArchive::dwFlags #define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access #define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed -#define MPQ_FLAG_PROTECTED 0x00000004 // Set on protected MPQs (like W3M maps) +#define MPQ_FLAG_PROTECTED 0x00000004 // Some kind of protector detected (W3M maps) #define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000008 // Checking sector CRC when reading files -#define MPQ_FLAG_NEED_FIX_SIZE 0x00000010 // Used during opening the archive #define MPQ_FLAG_LISTFILE_INVALID 0x00000020 // If set, it means that the (listfile) has been invalidated #define MPQ_FLAG_ATTRIBUTES_INVALID 0x00000040 // If set, it means that the (attributes) has been invalidated +#define MPQ_FLAG_SAVING_TABLES 0x00000080 // If set, we are saving MPQ internal files and MPQ tables // Values for TMPQArchive::dwSubType #define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games) @@ -214,9 +215,6 @@ extern "C" { MPQ_FILE_SECTOR_CRC | \ MPQ_FILE_EXISTS) -// A notification that people should stop using this flag -const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK") unsigned int MPQ_FILE_COMPRESSED = 0x0000FF00; - // Compression types for multiple compressions #define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only) #define MPQ_COMPRESSION_ZLIB 0x02 // ZLIB compression @@ -244,35 +242,6 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK // Block map defines #define MPQ_DATA_BITMAP_SIGNATURE 0x33767470 // Signature of the MPQ data bitmap ('ptv3') -// Constants for SFileGetFileInfo -#define SFILE_INFO_ARCHIVE_NAME 1 // MPQ size (value from header) -#define SFILE_INFO_ARCHIVE_SIZE 2 // MPQ size (value from header) -#define SFILE_INFO_MAX_FILE_COUNT 3 // Max number of files in the MPQ -#define SFILE_INFO_HASH_TABLE_SIZE 4 // Size of hash table, in entries -#define SFILE_INFO_BLOCK_TABLE_SIZE 5 // Number of entries in the block table -#define SFILE_INFO_SECTOR_SIZE 6 // Size of file sector (in bytes) -#define SFILE_INFO_HASH_TABLE 7 // Pointer to Hash table (TMPQHash *) -#define SFILE_INFO_BLOCK_TABLE 8 // Pointer to Block Table (TMPQBlock *) -#define SFILE_INFO_NUM_FILES 9 // Real number of files within archive -#define SFILE_INFO_STREAM_FLAGS 10 // Stream flags for the MPQ. See STREAM_FLAG_XXX -#define SFILE_INFO_IS_READ_ONLY 11 // TRUE of the MPQ was open as read only -//------ -#define SFILE_INFO_HASH_INDEX 100 // Hash index of file in MPQ -#define SFILE_INFO_CODENAME1 101 // The first codename of the file -#define SFILE_INFO_CODENAME2 102 // The second codename of the file -#define SFILE_INFO_LOCALEID 103 // Locale ID of file in MPQ -#define SFILE_INFO_BLOCKINDEX 104 // Index to Block Table -#define SFILE_INFO_FILE_SIZE 105 // Original file size (from the block table) -#define SFILE_INFO_COMPRESSED_SIZE 106 // Compressed file size (from the block table) -#define SFILE_INFO_FLAGS 107 // File flags -#define SFILE_INFO_POSITION 108 // File position within archive - // Note: for current pointer in open MPQ file, - // use SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); -#define SFILE_INFO_KEY 109 // File decryption key -#define SFILE_INFO_KEY_UNFIXED 110 // Decryption key not fixed to file pos and size -#define SFILE_INFO_FILETIME 111 // TMPQFileTime -#define SFILE_INFO_PATCH_CHAIN 112 // Chain of patches - #define LISTFILE_NAME "(listfile)" // Name of internal listfile #define SIGNATURE_NAME "(signature)" // Name of internal signature #define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file @@ -319,7 +288,8 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK #define MPQ_OPEN_ENCRYPTED STREAM_PROVIDER_ENCRYPTED // Flags for SFileCreateArchive -#define MPQ_CREATE_ATTRIBUTES 0x00100000 // Also add the (attributes) file +#define MPQ_CREATE_LISTFILE 0x00100000 // Also add the (listfile) file +#define MPQ_CREATE_ATTRIBUTES 0x00200000 // Also add the (attributes) file #define MPQ_CREATE_ARCHIVE_V1 0x00000000 // Creates archive of version 1 (size up to 4GB) #define MPQ_CREATE_ARCHIVE_V2 0x01000000 // Creates archive of version 2 (larger than 4 GB) #define MPQ_CREATE_ARCHIVE_V3 0x02000000 // Creates archive of version 3 @@ -357,6 +327,11 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK #define SFILE_VERIFY_HIBLOCK_TABLE 0x0006 // Verify raw data of the hi-block table #define SFILE_VERIFY_FILE 0x0007 // Verify raw data of a file +// Signature types +#define SIGNATURE_TYPE_NONE 0x0000 // The archive has no signature in it +#define SIGNATURE_TYPE_WEAK 0x0001 // The archive has weak signature +#define SIGNATURE_TYPE_STRONG 0x0002 // The archive has strong signature + // Return values for SFileVerifyArchive #define ERROR_NO_SIGNATURE 0 // There is no signature in the MPQ #define ERROR_VERIFY_FAILED 1 // There was an error during verifying signature (like no memory) @@ -381,14 +356,114 @@ const STORMLIB_DEPRECATED("This symbol is deprecated. Use MPQ_FILE_COMPRESS_MASK typedef DWORD (*HASH_STRING)(const char * szFileName, DWORD dwHashType); //----------------------------------------------------------------------------- +// File information classes for SFileGetFileInfo and SFileFreeFileInfo + +typedef enum _SFileInfoClass +{ + // Info classes for archives + SFileMpqFileName, // Name of the archive file (TCHAR []) + SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG) + SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData) + SFileMpqUserData, // MPQ USer data, without the header (BYTE []) + SFileMpqHeaderOffset, // Offset of the MPQ header (ULONGLONG) + SFileMpqHeaderSize, // Fixed size of the MPQ header + SFileMpqHeader, // Raw (unfixed) archive header (TMPQHeader) + SFileMpqHetTableOffset, // Offset of the HET table, relative to MPQ header (ULONGLONG) + SFileMpqHetTableSize, // Compressed size of the HET table (ULONGLONG) + SFileMpqHetHeader, // HET table header (TMPQHetHeader) + SFileMpqHetTable, // HET table as pointer. Must be freed using SFileFreeFileInfo + SFileMpqBetTableOffset, // Offset of the BET table, relative to MPQ header (ULONGLONG) + SFileMpqBetTableSize, // Compressed size of the BET table (ULONGLONG) + SFileMpqBetHeader, // BET table header, followed by the flags (TMPQBetHeader + DWORD[]) + SFileMpqBetTable, // BET table as pointer. Must be freed using SFileFreeFileInfo + SFileMpqHashTableOffset, // Hash table offset, relative to MPQ header (ULONGLONG) + SFileMpqHashTableSize64, // Compressed size of the hash table (ULONGLONG) + SFileMpqHashTableSize, // Size of the hash table, in entries (DWORD) + SFileMpqHashTable, // Raw (unfixed) hash table (TMPQBlock []) + SFileMpqBlockTableOffset, // Block table offset, relative to MPQ header (ULONGLONG) + SFileMpqBlockTableSize64, // Compressed size of the block table (ULONGLONG) + SFileMpqBlockTableSize, // Size of the block table, in entries (DWORD) + SFileMpqBlockTable, // Raw (unfixed) block table (TMPQBlock []) + SFileMpqHiBlockTableOffset, // Hi-block table offset, relative to MPQ header (ULONGLONG) + SFileMpqHiBlockTableSize64, // Compressed size of the hi-block table (ULONGLONG) + SFileMpqHiBlockTable, // The hi-block table (USHORT []) + SFileMpqSignatures, // Signatures present in the MPQ (DWORD) + SFileMpqStrongSignatureOffset, // Byte offset of the strong signature, relative to begin of the file (ULONGLONG) + SFileMpqStrongSignatureSize, // Size of the strong signature (DWORD) + SFileMpqStrongSignature, // The strong signature (BYTE []) + SFileMpqBitmapOffset, // Byte offset of the MPQ bitmap, relative to begin of the file (ULONGLONG) + SFileMpqBitmapSize, // Size of the MPQ bitmap (DWORD) + SFileMpqBitmap, // The MPQ Bitmap (BYTE []) + SFileMpqArchiveSize64, // Archive size from the header (ULONGLONG) + SFileMpqArchiveSize, // Archive size from the header (DWORD) + SFileMpqMaxFileCount, // Max number of files in the archive (DWORD) + SFileMpqFileTableSize, // Number of entries in the file table (DWORD) + SFileMpqSectorSize, // Sector size (DWORD) + SFileMpqNumberOfFiles, // Number of files (DWORD) + SFileMpqRawChunkSize, // Size of the raw data chunk for MD5 + SFileMpqStreamFlags, // Stream flags (DWORD) + SFileMpqIsReadOnly, // Nonzero if the MPQ is read only (DWORD) + + // Info classes for files + SFileInfoPatchChain, // Chain of patches where the file is (TCHAR []) + SFileInfoFileEntry, // The file entry for the file (TFileEntry) + SFileInfoHashEntry, // Hash table entry for the file (TMPQHash) + SFileInfoHashIndex, // Index of the hash table entry (DWORD) + SFileInfoNameHash1, // The first name hash in the hash table (DWORD) + SFileInfoNameHash2, // The second name hash in the hash table (DWORD) + SFileInfoNameHash3, // 64-bit file name hash for the HET/BET tables (ULONGLONG) + SFileInfoLocale, // File locale (DWORD) + SFileInfoFileIndex, // Block index (DWORD) + SFileInfoByteOffset, // File position in the archive (ULONGLONG) + SFileInfoFileTime, // File time (ULONGLONG) + SFileInfoFileSize, // Size of the file (DWORD) + SFileInfoCompressedSize, // Compressed file size (DWORD) + SFileInfoFlags, // File flags from (DWORD) + SFileInfoEncryptionKey, // File encryption key + SFileInfoEncryptionKeyRaw, // Unfixed value of the file key +} SFileInfoClass; + +//----------------------------------------------------------------------------- +// Deprecated flags. These are going to be removed in next releases. + +// MPQ_FILE_COMPRESSED is deprecated. Do not use. +STORMLIB_DEPRECATED_FLAG(DWORD, MPQ_FILE_COMPRESSED, MPQ_FILE_COMPRESS_MASK); + +// Legacy values for file info classes. Included for backward compatibility, do not use. +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_ARCHIVE_NAME, SFileMpqFileName); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_ARCHIVE_SIZE, SFileMpqArchiveSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_MAX_FILE_COUNT, SFileMpqMaxFileCount); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_TABLE_SIZE, SFileMpqHashTableSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCK_TABLE_SIZE, SFileMpqBlockTableSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_SECTOR_SIZE, SFileMpqSectorSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_TABLE, SFileMpqHashTable); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCK_TABLE, SFileMpqBlockTable); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_NUM_FILES, SFileMpqNumberOfFiles); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_STREAM_FLAGS, SFileMpqStreamFlags); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_IS_READ_ONLY, SFileMpqIsReadOnly); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_HASH_INDEX, SFileInfoHashIndex); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_CODENAME1, SFileInfoNameHash1); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_CODENAME2, SFileInfoNameHash2); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_LOCALEID, SFileInfoLocale); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_BLOCKINDEX, SFileInfoFileIndex); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FILE_SIZE, SFileInfoFileSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_COMPRESSED_SIZE, SFileInfoCompressedSize); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FLAGS, SFileInfoFlags); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_POSITION, SFileInfoByteOffset); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_KEY, SFileInfoEncryptionKey); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_KEY_UNFIXED, SFileInfoEncryptionKeyRaw); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_FILETIME, SFileInfoFileTime); +STORMLIB_DEPRECATED_FLAG(SFileInfoClass, SFILE_INFO_PATCH_CHAIN, SFileInfoPatchChain); + +//----------------------------------------------------------------------------- // Callback functions // Values for compact callback -#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total) -#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total) -#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used -#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total) -#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used +#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total) +#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total) +#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used +#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total) +#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used typedef void (WINAPI * SFILE_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall); typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes); @@ -400,6 +475,7 @@ typedef struct TFileStream TFileStream; typedef struct _TBitArray { + DWORD NumberOfBytes; // Total number of bytes in "Elements" DWORD NumberOfBits; // Total number of bits that are available BYTE Elements[1]; // Array of elements (variable length) } TBitArray; @@ -602,10 +678,10 @@ typedef struct _TMPQBlock // Patch file information, preceding the sector offset table typedef struct _TPatchInfo { - DWORD dwLength; // Length of patch info header, in bytes - DWORD dwFlags; // Flags. 0x80000000 = MD5 (?) - DWORD dwDataSize; // Uncompressed size of the patch file - BYTE md5[0x10]; // MD5 of the entire patch file after decompression + DWORD dwLength; // Length of patch info header, in bytes + DWORD dwFlags; // Flags. 0x80000000 = MD5 (?) + DWORD dwDataSize; // Uncompressed size of the patch file + BYTE md5[0x10]; // MD5 of the entire patch file after decompression // Followed by the sector table (variable length) } TPatchInfo; @@ -614,21 +690,21 @@ typedef struct _TPatchInfo typedef struct _TPatchHeader { //-- PATCH header ----------------------------------- - DWORD dwSignature; // 'PTCH' - DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed) - DWORD dwSizeBeforePatch; // Size of the file before patch - DWORD dwSizeAfterPatch; // Size of file after patch + DWORD dwSignature; // 'PTCH' + DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed) + DWORD dwSizeBeforePatch; // Size of the file before patch + DWORD dwSizeAfterPatch; // Size of file after patch //-- MD5 block -------------------------------------- - DWORD dwMD5; // 'MD5_' - DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself - BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file - BYTE md5_after_patch[0x10]; // MD5 of the patched file + DWORD dwMD5; // 'MD5_' + DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself + BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file + BYTE md5_after_patch[0x10]; // MD5 of the patched file //-- XFRM block ------------------------------------- - DWORD dwXFRM; // 'XFRM' - DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data - DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY') + DWORD dwXFRM; // 'XFRM' + DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data + DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY') // Followed by the patch data } TPatchHeader; @@ -640,32 +716,74 @@ typedef struct _TPatchHeader // (attributes) file and from (listfile). typedef struct _TFileEntry { - ULONGLONG ByteOffset; // Position of the file content in the MPQ, relative to the MPQ header - ULONGLONG FileTime; // FileTime from the (attributes) file. 0 if not present. - ULONGLONG BetHash; // Lower part of the file name hash. Only used when the MPQ has BET table. - DWORD dwHashIndex; // Index to the hash table. Only used when the MPQ has classic hash table - DWORD dwHetIndex; // Index to the HET table. Only used when the MPQ has HET table - DWORD dwFileSize; // Decompressed size of the file - DWORD dwCmpSize; // Compressed size of the file (i.e., size of the file data in the MPQ) - DWORD dwFlags; // File flags (from block table) - USHORT lcLocale; // Locale ID for the file - USHORT wPlatform; // Platform ID for the file - DWORD dwCrc32; // CRC32 from (attributes) file. 0 if not present. - unsigned char md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present. - char * szFileName; // File name. NULL if not known. + ULONGLONG FileNameHash; // Jenkins hash of the file name. Only used when the MPQ has BET table. + ULONGLONG ByteOffset; // Position of the file content in the MPQ, relative to the MPQ header + ULONGLONG FileTime; // FileTime from the (attributes) file. 0 if not present. + DWORD dwHashIndex; // Index to the hash table. Only used when the MPQ has classic hash table + DWORD dwFileSize; // Decompressed size of the file + DWORD dwCmpSize; // Compressed size of the file (i.e., size of the file data in the MPQ) + DWORD dwFlags; // File flags (from block table) + USHORT lcLocale; // Locale ID for the file + USHORT wPlatform; // Platform ID for the file + DWORD dwCrc32; // CRC32 from (attributes) file. 0 if not present. + unsigned char md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present. + char * szFileName; // File name. NULL if not known. } TFileEntry; // Common header for HET and BET tables -typedef struct _TMPQExtTable +typedef struct _TMPQExtHeader { - DWORD dwSignature; // 'HET\x1A' or 'BET\x1A' - DWORD dwVersion; // Version. Seems to be always 1 - DWORD dwDataSize; // Size of the contained table + DWORD dwSignature; // 'HET\x1A' or 'BET\x1A' + DWORD dwVersion; // Version. Seems to be always 1 + DWORD dwDataSize; // Size of the contained table // Followed by the table header // Followed by the table data -} TMPQExtTable; +} TMPQExtHeader; + +// Structure for HET table header +typedef struct _TMPQHetHeader +{ + TMPQExtHeader ExtHdr; + + DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes) + DWORD dwEntryCount; // Number of occupied entries in the HET table + DWORD dwTotalCount; // Total number of entries in the HET table + DWORD dwNameHashBitSize; // Size of the name hash entry (in bits) + DWORD dwIndexSizeTotal; // Total size of file index (in bits) + DWORD dwIndexSizeExtra; // Extra bits in the file index + DWORD dwIndexSize; // Effective size of the file index (in bits) + DWORD dwIndexTableSize; // Size of the block index subtable (in bytes) + +} TMPQHetHeader; + +// Structure for BET table header +typedef struct _TMPQBetHeader +{ + TMPQExtHeader ExtHdr; + + DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes) + DWORD dwEntryCount; // Number of entries in the BET table. Must match HET_TABLE_HEADER::dwEntryCount + DWORD dwUnknown08; + DWORD dwTableEntrySize; // Size of one table entry (in bits) + DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record) + DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record) + DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record) + DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record) + DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record) + DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record) + DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record) + DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record) + DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record) + DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record) + DWORD dwBitTotal_NameHash2; // Total bit size of the NameHash2 + DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2 + DWORD dwBitCount_NameHash2; // Effective size of NameHash2 (in bits) + DWORD dwNameHashArraySize; // Size of NameHash2 table, in bytes + DWORD dwFlagCount; // Number of flags in the following array + +} TMPQBetHeader; // // MPQ data bitmap, can be found at (FileSize - sizeof(TMPQBlockMap)) @@ -675,175 +793,185 @@ typedef struct _TMPQExtTable // typedef struct _TMPQBitmap { - DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE) - DWORD dwAlways3; // Unknown, seems to always have value of 3 - DWORD dwBuildNumber; // Game build number for that MPQ - DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map - DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map - DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) + DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE) + DWORD dwAlways3; // Unknown, seems to always have value of 3 + DWORD dwBuildNumber; // Game build number for that MPQ + DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map + DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map + DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) } TMPQBitmap; // Structure for parsed HET table typedef struct _TMPQHetTable { - TBitArray * pBetIndexes; // Bit array of indexes to BET tables - LPBYTE pHetHashes; // Array of HET hashes. Each entry has size of 1 byte - ULONGLONG AndMask64; // AND mask used for calculating file name hash - ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash - - DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits) - DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes - DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits) - DWORD dwFileCount; // Number of occupied entries in the HET table - DWORD dwHashTableSize; // Number of entries in pBetHashes - DWORD dwHashBitSize; // Effective number of bits in the hash + TBitArray * pBetIndexes; // Bit array of FileIndex values + LPBYTE pNameHashes; // Array of NameHash1 values (NameHash1 = upper 8 bits of FileName hashe) + ULONGLONG AndMask64; // AND mask used for calculating file name hash + ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash + + DWORD dwEntryCount; // Number of occupied entries in the HET table + DWORD dwTotalCount; // Number of entries in both NameHash and FileIndex table + DWORD dwNameHashBitSize; // Size of the name hash entry (in bits) + DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits) + DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes + DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits) } TMPQHetTable; // Structure for parsed BET table typedef struct _TMPQBetTable { - TBitArray * pBetHashes; // Array of BET hashes - TBitArray * pFileTable; // Bit-based file table - LPDWORD pFileFlags; // Array of file flags - - DWORD dwTableEntrySize; // Size of one table entry, in bits - DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry - DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry - DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry - DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry - DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry - DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry - DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry - DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry - DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry - DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry - DWORD dwBetHashSizeTotal; // Total size of bet hash - DWORD dwBetHashSizeExtra; // Extra bits in the bet hash - DWORD dwBetHashSize; // Effective size of the bet hash - DWORD dwFileCount; // Number of files (usually equal to maximum number of files) - DWORD dwFlagCount; // Number of entries in pFileFlags + TBitArray * pNameHashes; // Array of NameHash2 entries (lower 24 bits of FileName hash) + TBitArray * pFileTable; // Bit-based file table + LPDWORD pFileFlags; // Array of file flags + + DWORD dwTableEntrySize; // Size of one table entry, in bits + DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry + DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry + DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry + DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry + DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry + DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry + DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry + DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry + DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry + DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry + DWORD dwBitTotal_NameHash2; // Total size of the NameHash2 + DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2 + DWORD dwBitCount_NameHash2; // Effective size of the NameHash2 + DWORD dwEntryCount; // Number of entries + DWORD dwFlagCount; // Number of fil flags in pFileFlags } TMPQBetTable; // Archive handle structure typedef struct _TMPQArchive { - TFileStream * pStream; // Open stream for the MPQ - - ULONGLONG UserDataPos; // Position of user data (relative to the begin of the file) - ULONGLONG MpqPos; // MPQ header offset (relative to the begin of the file) - - struct _TMPQArchive * haPatch; // Pointer to patch archive, if any - struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any - char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs - size_t cchPatchPrefix; // Length of the patch prefix, in characters - - TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file) - TMPQHeader * pHeader; // MPQ file header - TMPQBitmap * pBitmap; // MPQ bitmap - TMPQHash * pHashTable; // Hash table - TMPQHetTable * pHetTable; // Het table - TFileEntry * pFileTable; // File table - HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash + TFileStream * pStream; // Open stream for the MPQ + + ULONGLONG UserDataPos; // Position of user data (relative to the begin of the file) + ULONGLONG MpqPos; // MPQ header offset (relative to the begin of the file) + + struct _TMPQArchive * haPatch; // Pointer to patch archive, if any + struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any + char szPatchPrefix[MPQ_PATCH_PREFIX_LEN]; // Prefix for file names in patch MPQs + size_t cchPatchPrefix; // Length of the patch prefix, in characters + + TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file) + TMPQHeader * pHeader; // MPQ file header + TMPQBitmap * pBitmap; // MPQ bitmap + TMPQHash * pHashTable; // Hash table + TMPQHetTable * pHetTable; // HET table + TFileEntry * pFileTable; // File table + HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash - TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found + TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found BYTE HeaderData[MPQ_HEADER_SIZE_V4]; // Storage for MPQ header DWORD dwHETBlockSize; DWORD dwBETBlockSize; - DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one - DWORD dwMaxFileCount; // Maximum number of files in the MPQ - DWORD dwHashIndexMask; // Mask for converting MPQ_HASH_TABLE_INDEX into real index - DWORD dwSectorSize; // Default size of one file sector - DWORD dwFileFlags1; // Flags for (listfile) - DWORD dwFileFlags2; // Flags for (attributes) - DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX - DWORD dwFlags; // See MPQ_FLAG_XXXXX - DWORD dwSubType; // See MPQ_SUBTYPE_XXX - - SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files - void * pvAddFileUserData; // User data thats passed to the callback - - SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive - ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call - ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted - void * pvCompactUserData; // User data thats passed to the callback + DWORD dwBitmapSize; // sizeof(TMPQBitmap) + size of the bit array + DWORD dwMaxFileCount; // Maximum number of files in the MPQ. Also total size of the file table. + DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one + DWORD dwReservedFiles; // Number of entries reserved for internal MPQ files (listfile, attributes) + DWORD dwSectorSize; // Default size of one file sector + DWORD dwFileFlags1; // Flags for (listfile) + DWORD dwFileFlags2; // Flags for (attributes) + DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX + DWORD dwFlags; // See MPQ_FLAG_XXXXX + DWORD dwSubType; // See MPQ_SUBTYPE_XXX + + SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files + void * pvAddFileUserData; // User data thats passed to the callback + + SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive + ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call + ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted + void * pvCompactUserData; // User data thats passed to the callback } TMPQArchive; // File handle structure typedef struct _TMPQFile { - TFileStream * pStream; // File stream. Only used on local files - TMPQArchive * ha; // Archive handle - TFileEntry * pFileEntry; // File entry for the file - DWORD dwFileKey; // Decryption key - DWORD dwFilePos; // Current file position - ULONGLONG RawFilePos; // Offset in MPQ archive (relative to file begin) - ULONGLONG MpqFilePos; // Offset in MPQ archive (relative to MPQ header) - DWORD dwMagic; // 'FILE' - - struct _TMPQFile * hfPatchFile; // Pointer to opened patch file - TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file - LPBYTE pbFileData; // Loaded and patched file data. Only used if the file is a patch file - DWORD cbFileData; // Size of loaded patched data - - TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table - DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files. - DWORD * SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector - DWORD dwSectorCount; // Number of sectors in the file - DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ - DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry) - - LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content - DWORD dwSectorOffs; // File position of currently loaded file sector - DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size - - unsigned char hctx[HASH_STATE_SIZE];// Hash state for MD5. Used when saving file to MPQ - DWORD dwCrc32; // CRC32 value, used when saving file to MPQ - - bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs - bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file - bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile - bool bErrorOccured; // If true, then at least one error occured during saving the file to the archive + TFileStream * pStream; // File stream. Only used on local files + TMPQArchive * ha; // Archive handle + TFileEntry * pFileEntry; // File entry for the file + DWORD dwFileKey; // Decryption key + DWORD dwFilePos; // Current file position + ULONGLONG RawFilePos; // Offset in MPQ archive (relative to file begin) + ULONGLONG MpqFilePos; // Offset in MPQ archive (relative to MPQ header) + DWORD dwMagic; // 'FILE' + + struct _TMPQFile * hfPatch; // Pointer to opened patch file + TPatchHeader * pPatchHeader; // Patch header. Only used if the file is a patch file + LPBYTE pbFileData; // Loaded and patched file data. Only used if the file is a patch file + DWORD cbFileData; // Size of loaded patched data + + TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table + DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files. + DWORD * SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector + DWORD dwCompression0; // Compression that will be used on the first file sector + DWORD dwSectorCount; // Number of sectors in the file + DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ + DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry) + + LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content + DWORD dwSectorOffs; // File position of currently loaded file sector + DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size + + unsigned char hctx[HASH_STATE_SIZE]; // Hash state for MD5. Used when saving file to MPQ + DWORD dwCrc32; // CRC32 value, used when saving file to MPQ + + int nAddFileError; // Result of the "Add File" operations + + bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs + bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file + bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile } TMPQFile; // Structure for SFileFindFirstFile and SFileFindNextFile typedef struct _SFILE_FIND_DATA { - char cFileName[MAX_PATH]; // Full name of the found file - char * szPlainName; // Plain name of the found file - DWORD dwHashIndex; // Hash table index for the file - DWORD dwBlockIndex; // Block table index for the file - DWORD dwFileSize; // File size in bytes - DWORD dwFileFlags; // MPQ file flags - DWORD dwCompSize; // Compressed file size - DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present) - DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present) - LCID lcLocale; // Locale version + char cFileName[MAX_PATH]; // Full name of the found file + char * szPlainName; // Plain name of the found file + DWORD dwHashIndex; // Hash table index for the file + DWORD dwBlockIndex; // Block table index for the file + DWORD dwFileSize; // File size in bytes + DWORD dwFileFlags; // MPQ file flags + DWORD dwCompSize; // Compressed file size + DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present) + DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present) + LCID lcLocale; // Locale version } SFILE_FIND_DATA, *PSFILE_FIND_DATA; typedef struct _SFILE_CREATE_MPQ { - DWORD cbSize; // Size of this structure, in bytes - DWORD dwMpqVersion; // Version of the MPQ to be created - void *pvUserData; // Reserved, must be NULL - DWORD cbUserData; // Reserved, must be 0 - DWORD dwStreamFlags; // Stream flags for creating the MPQ - DWORD dwFileFlags1; // File flags for (listfile). 0 = default - DWORD dwFileFlags2; // File flags for (attributes). 0 = default - DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created - DWORD dwSectorSize; // Sector size for compressed files - DWORD dwRawChunkSize; // Size of raw data chunk - DWORD dwMaxFileCount; // File limit for the MPQ + DWORD cbSize; // Size of this structure, in bytes + DWORD dwMpqVersion; // Version of the MPQ to be created + void *pvUserData; // Reserved, must be NULL + DWORD cbUserData; // Reserved, must be 0 + DWORD dwStreamFlags; // Stream flags for creating the MPQ + DWORD dwFileFlags1; // File flags for (listfile). 0 = default + DWORD dwFileFlags2; // File flags for (attributes). 0 = default + DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created + DWORD dwSectorSize; // Sector size for compressed files + DWORD dwRawChunkSize; // Size of raw data chunk + DWORD dwMaxFileCount; // File limit for the MPQ } SFILE_CREATE_MPQ, *PSFILE_CREATE_MPQ; //----------------------------------------------------------------------------- // Stream support - functions +// UNICODE versions of the file access functions TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags); TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags); -TCHAR * FileStream_GetFileName(TFileStream * pStream); +const TCHAR * FileStream_GetFileName(TFileStream * pStream); + +//#ifdef _UNICODE +//TFileStream * FileStream_CreateFile(const char * szFileName, DWORD dwStreamFlags); +//TFileStream * FileStream_OpenFile(const char * szFileName, DWORD dwStreamFlags); +//#endif + bool FileStream_IsReadOnly(TFileStream * pStream); bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead); bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite); @@ -881,7 +1009,7 @@ LCID WINAPI SFileSetLocale(LCID lcNewLocale); // Functions for archive manipulation bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq); -bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq); +bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq); bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq); bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded); @@ -916,16 +1044,17 @@ bool WINAPI SFileIsPatchedArchive(HANDLE hMpq); // Functions for file manipulation // Reading from MPQ file +bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName); bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile); DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh); DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod); bool WINAPI SFileReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped); bool WINAPI SFileCloseFile(HANDLE hFile); -// Retrieving info about the file -bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName); +// Retrieving info about a file in the archive +bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, SFileInfoClass InfoClass, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded); bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName); -bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, DWORD dwInfoType, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded); +bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass); // High-level extract function bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR * szExtracted, DWORD dwSearchScope); diff --git a/src/StormPort.h b/src/StormPort.h index 4e34280..83d8624 100644 --- a/src/StormPort.h +++ b/src/StormPort.h @@ -169,15 +169,17 @@ #define _tcscpy strcpy #define _tcscat strcat #define _tcsrchr strrchr + #define _tcsstr strstr #define _tprintf printf #define _stprintf sprintf #define _tremove remove #define _stricmp strcasecmp #define _strnicmp strncasecmp + #define _tcsicmp strcasecmp #define _tcsnicmp strncasecmp -#endif // !WIN32 +#endif // !PLATFORM_WINDOWS // 64-bit calls are supplied by "normal" calls on Mac #if defined(PLATFORM_MAC) @@ -221,7 +223,6 @@ #define BSWAP_ARRAY32_UNSIGNED(a,b) {} #define BSWAP_ARRAY64_UNSIGNED(a,b) {} #define BSWAP_PART_HEADER(a) {} - #define BSWAP_TMPQUSERDATA(a) {} #define BSWAP_TMPQHEADER(a,b) {} #define BSWAP_TMPKHEADER(a) {} #else @@ -255,7 +256,6 @@ #define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b)) #define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b)) #define BSWAP_PART_HEADER(a) ConvertPartHeader(a) - #define BSWAP_TMPQUSERDATA(a) ConvertTMPQUserData((a)) #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b)) #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a)) #endif @@ -273,4 +273,12 @@ #define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated(_Text))) #endif +// When a flag is deprecated, use this macro +#ifndef _STORMLIB_NO_DEPRECATE + #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) \ + const STORMLIB_DEPRECATED(#oldflag " is deprecated. Use " #newflag ". To supress this warning, define _STORMLIB_NO_DEPRECATE") type oldflag = (type)newflag; +#else + #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) const type oldflag = (type)newflag; +#endif + #endif // __STORMPORT_H__ |