bool WINAPI SFileCloseArchive(HANDLE hMpq) { TMPQArchive * ha = (TMPQArchive *)hMpq; bool bResult; // Flush all unsaved data to the storage bResult = SFileFlushArchive(hMpq); // Free all memory used by MPQ archive FreeMPQArchive(ha); return bResult; }
// TODO: Test for archives > 4GB BOOL WINAPI SFileCloseArchive(HANDLE hMPQ) { TMPQArchive * ha = (TMPQArchive *)hMPQ; if(!IsValidMpqHandle(ha)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } if(ha->dwFlags & MPQ_FLAG_CHANGED) { SListFileSaveToMpq(ha); SaveMPQTables(ha); } FreeMPQArchive(ha); return TRUE; }
bool WINAPI SFileCreateArchive(const char * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq) { TFileStream * pStream = NULL; // File stream TMPQArchive * ha = NULL; // MPQ archive handle ULONGLONG MpqPos = 0; // Position of MPQ header in the file HANDLE hMpq = NULL; USHORT wFormatVersion = MPQ_FORMAT_VERSION_1; DWORD dwBlockTableSize = 0; // Initial block table size DWORD dwHashTableSize = 0; int nError = ERROR_SUCCESS; // Check the parameters, if they are valid if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // One time initialization of MPQ cryptography InitializeMpqCryptography(); // We verify if the file already exists and if it's a MPQ archive. // If yes, we won't allow to overwrite it. if(SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) { SFileCloseArchive(hMpq); SetLastError(ERROR_ALREADY_EXISTS); return false; } // // At this point, we have to create the archive. // - If the file exists, convert it to MPQ archive. // - If the file doesn't exist, create new empty file // pStream = FileStream_OpenFile(szMpqName, true); if(pStream == NULL) { pStream = FileStream_CreateFile(szMpqName); if(pStream == NULL) return false; } // Decide what format to use wFormatVersion = (USHORT)((dwFlags & MPQ_CREATE_ARCHIVE_VMASK) >> 16); if(wFormatVersion > MPQ_FORMAT_VERSION_4) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // Increment the maximum amount of files to have space // for listfile and attributes file if(dwFlags & MPQ_CREATE_ATTRIBUTES) dwMaxFileCount++; dwMaxFileCount++; // If file count is not zero, initialize the hash table size dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); // Retrieve the file size and round it up to 0x200 bytes FileStream_GetSize(pStream, MpqPos); MpqPos = (MpqPos + 0x1FF) & (ULONGLONG)0xFFFFFFFFFFFFFE00ULL; if(!FileStream_SetSize(pStream, MpqPos)) nError = GetLastError(); #ifdef _DEBUG // Debug code, used for testing StormLib // dwBlockTableSize = dwHashTableSize * 2; #endif // Create the archive handle if(nError == ERROR_SUCCESS) { if((ha = ALLOCMEM(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Fill the MPQ archive handle structure if(nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); ha->pStream = pStream; ha->dwSectorSize = (wFormatVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000; ha->UserDataPos = MpqPos; ha->MpqPos = MpqPos; ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->dwMaxFileCount = dwMaxFileCount; ha->dwFileTableSize = 0; ha->dwFileFlags1 = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING; ha->dwFileFlags2 = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING; ha->dwFlags = 0; // Setup the attributes if(dwFlags & MPQ_CREATE_ATTRIBUTES) ha->dwAttrFlags = MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5; pStream = NULL; } // Fill the MPQ header if(nError == ERROR_SUCCESS) { TMPQHeader * pHeader = ha->pHeader; // Fill the MPQ header memset(pHeader, 0, sizeof(ha->HeaderData)); pHeader->dwID = ID_MPQ; pHeader->dwHeaderSize = MpqHeaderSizes[wFormatVersion]; pHeader->dwArchiveSize = pHeader->dwHeaderSize + dwHashTableSize * sizeof(TMPQHash); pHeader->wFormatVersion = wFormatVersion; pHeader->wSectorSize = GetSectorSizeShift(ha->dwSectorSize); pHeader->dwHashTablePos = pHeader->dwHeaderSize; pHeader->dwHashTableSize = dwHashTableSize; pHeader->dwBlockTablePos = pHeader->dwHashTablePos + dwHashTableSize * sizeof(TMPQHash); pHeader->dwBlockTableSize = dwBlockTableSize; // For MPQs version 4 and higher, we set the size of raw data block // for calculating MD5 if(wFormatVersion >= MPQ_FORMAT_VERSION_4) pHeader->dwRawChunkSize = 0x4000; // Write the naked MPQ header nError = WriteNakedMPQHeader(ha); // // Note: Don't recalculate position of MPQ tables at this point. // We merely set a flag that indicates that the MPQ tables // have been changed, and SaveMpqTables will do the work when closing the archive. // ha->dwFlags |= MPQ_FLAG_CHANGED; } // Create initial hash table if(nError == ERROR_SUCCESS) { nError = CreateHashTable(ha, dwHashTableSize); } // Create initial HET table, if the caller required an MPQ format 3.0 or newer if(nError == ERROR_SUCCESS && wFormatVersion >= MPQ_FORMAT_VERSION_3) { ha->pHetTable = CreateHetTable(ha->dwMaxFileCount, 0x40, true); if(ha->pHetTable == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Create initial file table if(nError == ERROR_SUCCESS) { ha->pFileTable = ALLOCMEM(TFileEntry, dwMaxFileCount); if(ha->pFileTable != NULL) memset(ha->pFileTable, 0x00, sizeof(TFileEntry) * dwMaxFileCount); else nError = ERROR_NOT_ENOUGH_MEMORY; } // Cleanup : If an error, delete all buffers and return if(nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeMPQArchive(ha); SetLastError(nError); ha = NULL; } // Return the values *phMpq = (HANDLE)ha; return (nError == ERROR_SUCCESS); }
bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq) { TFileStream * pStream = NULL; // File stream TMPQArchive * ha = NULL; // MPQ archive handle ULONGLONG MpqPos = 0; // Position of MPQ header in the file HANDLE hMpq = NULL; DWORD dwBlockTableSize = 0; // Initial block table size DWORD dwHashTableSize = 0; DWORD dwMaxFileCount; int nError = ERROR_SUCCESS; // Check the parameters, if they are valid if(szMpqName == NULL || *szMpqName == 0 || pCreateInfo == NULL || phMpq == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // Verify if all variables in SFILE_CREATE_MPQ are correct if((pCreateInfo->cbSize == 0 || pCreateInfo->cbSize > sizeof(SFILE_CREATE_MPQ)) || (pCreateInfo->dwMpqVersion > MPQ_FORMAT_VERSION_4) || (pCreateInfo->pvUserData != NULL || pCreateInfo->cbUserData != 0) || (pCreateInfo->dwAttrFlags & ~MPQ_ATTRIBUTE_ALL) || (pCreateInfo->dwSectorSize & (pCreateInfo->dwSectorSize - 1)) || (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)) || (pCreateInfo->dwMaxFileCount < 4)) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // One time initialization of MPQ cryptography InitializeMpqCryptography(); // We verify if the file already exists and if it's a MPQ archive. // If yes, we won't allow to overwrite it. if(SFileOpenArchive(szMpqName, 0, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE | MPQ_OPEN_NO_ATTRIBUTES | MPQ_OPEN_NO_LISTFILE, &hMpq)) { SFileCloseArchive(hMpq); SetLastError(ERROR_ALREADY_EXISTS); return false; } // // At this point, we have to create the archive. // - If the file exists, convert it to MPQ archive. // - If the file doesn't exist, create new empty file // pStream = FileStream_OpenFile(szMpqName, pCreateInfo->dwStreamFlags); if(pStream == NULL) { pStream = FileStream_CreateFile(szMpqName, pCreateInfo->dwStreamFlags); if(pStream == NULL) 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++; // If file count is not zero, initialize the hash table size dwHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); // Retrieve the file size and round it up to 0x200 bytes FileStream_GetSize(pStream, MpqPos); MpqPos = (MpqPos + 0x1FF) & (ULONGLONG)0xFFFFFFFFFFFFFE00ULL; if(!FileStream_SetSize(pStream, MpqPos)) nError = GetLastError(); #ifdef _DEBUG // Debug code, used for testing StormLib // dwBlockTableSize = dwHashTableSize * 2; #endif // Create the archive handle if(nError == ERROR_SUCCESS) { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Fill the MPQ archive handle structure if(nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); ha->pStream = pStream; ha->dwSectorSize = pCreateInfo->dwSectorSize; ha->UserDataPos = MpqPos; ha->MpqPos = MpqPos; ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->dwMaxFileCount = dwMaxFileCount; ha->dwFileTableSize = 0; ha->dwFileFlags1 = pCreateInfo->dwFileFlags1; ha->dwFileFlags2 = pCreateInfo->dwFileFlags2; ha->dwFlags = 0; // Setup the attributes ha->dwAttrFlags = pCreateInfo->dwAttrFlags; pStream = NULL; } // Fill the MPQ header if(nError == ERROR_SUCCESS) { TMPQHeader * pHeader = ha->pHeader; // Fill the MPQ header memset(pHeader, 0, sizeof(ha->HeaderData)); pHeader->dwID = ID_MPQ; pHeader->dwHeaderSize = MpqHeaderSizes[pCreateInfo->dwMpqVersion]; pHeader->dwArchiveSize = pHeader->dwHeaderSize + dwHashTableSize * sizeof(TMPQHash); pHeader->wFormatVersion = (USHORT)pCreateInfo->dwMpqVersion; pHeader->wSectorSize = GetSectorSizeShift(ha->dwSectorSize); pHeader->dwHashTablePos = pHeader->dwHeaderSize; pHeader->dwHashTableSize = dwHashTableSize; pHeader->dwBlockTablePos = pHeader->dwHashTablePos + dwHashTableSize * sizeof(TMPQHash); pHeader->dwBlockTableSize = dwBlockTableSize; // For MPQs version 4 and higher, we set the size of raw data block // for calculating MD5 if(pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_4) pHeader->dwRawChunkSize = pCreateInfo->dwRawChunkSize; // Write the naked MPQ header nError = WriteNakedMPQHeader(ha); // Remember that the (listfile) and (attributes) need to be saved ha->dwFlags |= MPQ_FLAG_CHANGED | MPQ_FLAG_INV_LISTFILE | MPQ_FLAG_INV_ATTRIBUTES; } // 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) { ha->pHetTable = CreateHetTable(ha->dwMaxFileCount, 0x40, true); if(ha->pHetTable == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Create initial hash table if(nError == ERROR_SUCCESS) { nError = CreateHashTable(ha, dwHashTableSize); } // Create initial file table if(nError == ERROR_SUCCESS) { ha->pFileTable = STORM_ALLOC(TFileEntry, ha->dwMaxFileCount); if(ha->pFileTable != NULL) memset(ha->pFileTable, 0x00, sizeof(TFileEntry) * ha->dwMaxFileCount); else nError = ERROR_NOT_ENOUGH_MEMORY; } // Cleanup : If an error, delete all buffers and return if(nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeMPQArchive(ha); SetLastError(nError); ha = NULL; } // Return the values *phMpq = (HANDLE)ha; return (nError == ERROR_SUCCESS); }
bool WINAPI SFileOpenArchive( const char * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq) { TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle ULONGLONG FileSize = 0; // Size of the file int nError = ERROR_SUCCESS; // Verify the parameters if (szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) nError = ERROR_INVALID_PARAMETER; // One time initialization of MPQ cryptography InitializeMpqCryptography(); dwPriority = dwPriority; // Open the MPQ archive file if (nError == ERROR_SUCCESS) { if (!(dwFlags & MPQ_OPEN_ENCRYPTED)) { pStream = FileStream_OpenFile(szMpqName, (dwFlags & MPQ_OPEN_READ_ONLY) ? false : true); if (pStream == NULL) nError = GetLastError(); } else { pStream = FileStream_OpenEncrypted(szMpqName); if (pStream == NULL) nError = GetLastError(); } } // Allocate the MPQhandle if (nError == ERROR_SUCCESS) { FileStream_GetSize(pStream, FileSize); if ((ha = ALLOCMEM(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize handle structure and allocate structure for MPQ header if (nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); ha->pStream = pStream; pStream = NULL; // Remember if the archive is open for write if (ha->pStream->StreamFlags & (STREAM_FLAG_READ_ONLY | STREAM_FLAG_ENCRYPTED_FILE)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Also remember if we shall check sector CRCs when reading file if (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ha->dwFlags |= MPQ_FLAG_CHECK_SECTOR_CRC; } // Find the offset of MPQ header within the file if (nError == ERROR_SUCCESS) { ULONGLONG SearchPos = 0; DWORD dwHeaderID; while (SearchPos < FileSize) { DWORD dwBytesAvailable = MPQ_HEADER_SIZE_V4; // Cut the bytes available, if needed if ((FileSize - SearchPos) < MPQ_HEADER_SIZE_V4) dwBytesAvailable = (DWORD)(FileSize - SearchPos); // Read the eventual MPQ header if (!FileStream_Read(ha->pStream, &SearchPos, ha->HeaderData, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. if (SearchPos == 0 && IsAviFile(ha->HeaderData)) { nError = ERROR_AVI_FILE; break; } // 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) { // 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) { // Fill the user data header ha->pUserData = &ha->UserData; memcpy(ha->pUserData, ha->HeaderData, sizeof(TMPQUserData)); BSWAP_TMPQUSERDATA(ha->pUserData); // Remember the position of the user data and continue search ha->UserDataPos = SearchPos; SearchPos += ha->pUserData->dwHeaderOffs; continue; } } // There must be MPQ header signature if (dwHeaderID == ID_MPQ) { // Save the position where the MPQ header has been found if (ha->pUserData == NULL) ha->UserDataPos = SearchPos; ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchPos; // Now convert the header to version 4 BSWAP_TMPQHEADER(ha->pHeader); ConvertMpqHeaderToFormat4(ha, FileSize, dwFlags); break; } // Move to the next possible offset SearchPos += 0x200; } // If we haven't found MPQ header in the file, it's an error if (ha->pHeader == NULL) nError = ERROR_BAD_FORMAT; } // Fix table positions according to format if (nError == ERROR_SUCCESS) { // 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 // 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) { ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; ha->dwFlags |= MPQ_FLAG_READ_ONLY; ha->pUserData = NULL; } // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if (dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); // Verify if any of the tables doesn't start beyond the end of the file nError = VerifyMpqTablePositions(ha, FileSize); } // Read the hash table. // "interface.MPQ.part" in trial version of World of Warcraft // has compressed block table and hash table. if (nError == ERROR_SUCCESS) { // // Note: We will not check if the hash table is properly decrypted. // Some MPQ protectors corrupt the hash table by rewriting part of it. // Hash table, the way how it works, allows arbitrary values for unused entries. // nError = LoadHashTable(ha); } // Read Het and Bet tables, if they are present if (nError == ERROR_SUCCESS) { nError = LoadHetAndBetTable(ha); if (ha->pHetTable || ha->pBetTable) ha->dwFlags |= MPQ_FLAG_READ_ONLY; } // Now, build the file table. It will be built by combining // the block table, hi-block table, (attributes) and (listfile). if (nError == ERROR_SUCCESS) { nError = BuildFileTable(ha, FileSize); } // Verify the block 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 * pFileEntry = ha->pFileTable; // ULONGLONG ArchiveSize = 0; ULONGLONG RawFilePos; // Parse all file entries for (pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // If that file entry is valid, check the file position if (pFileEntry->dwFlags & MPQ_FILE_EXISTS) { // Get the 64-bit file position, // relative to the begin of the file RawFilePos = ha->MpqPos + pFileEntry->ByteOffset; // Begin of the file must be within range if (RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } // End of the file must be within range RawFilePos += pFileEntry->dwCmpSize; if (RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } // Also, we remember end of the file // if (RawFilePos > ArchiveSize) // ArchiveSize = RawFilePos; } } } // Load the "(attributes)" file and merge it to the file table if (nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0) { // Ignore result of the operation. (attributes) is optional. SAttrLoadAttributes(ha); } // Load the internal listfile and include it to the file table if (nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { // Ignore result of the operation. (listfile) is optional. SFileAddListFile((HANDLE)ha, NULL); } // Test the indexes from BET and BET table #ifdef __STORMLIB_TEST__ if (nError == ERROR_SUCCESS) { TestNewHashBlockTables(ha); } #endif // Cleanup and exit if (nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeMPQArchive(ha); SetLastError(nError); ha = NULL; } *phMpq = ha; return (nError == ERROR_SUCCESS); }
BOOL SFileOpenArchiveEx( const char * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMPQ, DWORD dwAccessMode) { LARGE_INTEGER TempPos; TMPQArchive * ha = NULL; // Archive handle HANDLE hFile = INVALID_HANDLE_VALUE;// Opened archive file handle DWORD dwMaxBlockIndex = 0; // Maximum value of block entry DWORD dwBlockTableSize = 0; // Block table size. DWORD dwTransferred; // Number of bytes read DWORD dwBytes = 0; // Number of bytes to read int nError = ERROR_SUCCESS; // Check the right parameters if(nError == ERROR_SUCCESS) { if(szMpqName == NULL || *szMpqName == 0 || phMPQ == NULL) nError = ERROR_INVALID_PARAMETER; } // Ensure that StormBuffer is allocated if(nError == ERROR_SUCCESS) nError = PrepareStormBuffer(); // Open the MPQ archive file if(nError == ERROR_SUCCESS) { hFile = CreateFile(szMpqName, dwAccessMode, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) nError = GetLastError(); } // Allocate the MPQhandle if(nError == ERROR_SUCCESS) { if((ha = ALLOCMEM(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize handle structure and allocate structure for MPQ header if(nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); strncpy(ha->szFileName, szMpqName, strlen(szMpqName)); ha->hFile = hFile; ha->dwPriority = dwPriority; ha->pHeader = &ha->Header; ha->pListFile = NULL; hFile = INVALID_HANDLE_VALUE; } // Find the offset of MPQ header within the file if(nError == ERROR_SUCCESS) { LARGE_INTEGER SearchPos = {0}; LARGE_INTEGER MpqPos = {0}; DWORD dwHeaderID; for(;;) { // Invalidate the MPQ ID and read the eventual header SetFilePointer(ha->hFile, MpqPos.LowPart, &MpqPos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pHeader, sizeof(TMPQHeader2), &dwTransferred, NULL); dwHeaderID = BSWAP_INT32_UNSIGNED(ha->pHeader->dwID); // Special check : Some MPQs are actually AVI files, only with // changed extension. if(MpqPos.QuadPart == 0 && IsAviFile(ha->pHeader)) { nError = ERROR_AVI_FILE; break; } // If different number of bytes read, break the loop if(dwTransferred != sizeof(TMPQHeader2)) { nError = ERROR_BAD_FORMAT; break; } // If there is the MPQ shunt signature, process it if(dwHeaderID == ID_MPQ_SHUNT && ha->pShunt == NULL) { // Fill the shunt header ha->ShuntPos = MpqPos; ha->pShunt = &ha->Shunt; memcpy(ha->pShunt, ha->pHeader, sizeof(TMPQShunt)); BSWAP_TMPQSHUNT(ha->pShunt); // Set the MPQ pos and repeat the search MpqPos.QuadPart = SearchPos.QuadPart + ha->pShunt->dwHeaderPos; continue; } // There must be MPQ header signature if(dwHeaderID == ID_MPQ) { BSWAP_TMPQHEADER(ha->pHeader); // Save the position where the MPQ header has been found ha->MpqPos = MpqPos; // If valid signature has been found, break the loop if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) { // W3M Map Protectors set some garbage value into the "dwHeaderSize" // field of MPQ header. This value is apparently ignored by Storm.dll if(ha->pHeader->dwHeaderSize != sizeof(TMPQHeader) && ha->pHeader->dwHeaderSize != sizeof(TMPQHeader2)) { ha->dwFlags |= MPQ_FLAG_PROTECTED; ha->pHeader->dwHeaderSize = sizeof(TMPQHeader); } if(ha->pHeader->dwHashTablePos < ha->pHeader->dwArchiveSize && ha->pHeader->dwBlockTablePos < ha->pHeader->dwArchiveSize) { break; } } if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_2) { break; } nError = ERROR_NOT_SUPPORTED; break; } // If a MPQ shunt already has been found, // and no MPQ header was at potision pointed by the shunt, // then the archive is corrupt if(ha->pShunt != NULL) { nError = ERROR_BAD_FORMAT; break; } // Move to the next possible offset SearchPos.QuadPart += 0x200; MpqPos = SearchPos; } } // Relocate tables position if(nError == ERROR_SUCCESS) { // Clear the fields not supported in older formats if(ha->pHeader->wFormatVersion < MPQ_FORMAT_VERSION_2) { ha->pHeader->ExtBlockTablePos.QuadPart = 0; ha->pHeader->wBlockTablePosHigh = 0; ha->pHeader->wHashTablePosHigh = 0; } ha->dwBlockSize = (0x200 << ha->pHeader->wBlockSize); nError = RelocateMpqTablePositions(ha); } // Allocate buffers if(nError == ERROR_SUCCESS) { // // Note that the block table should be as large as the hash table // (For later file additions). // // I have found a MPQ which has the block table larger than // the hash table. We should avoid buffer overruns caused by that. // dwBlockTableSize = max(ha->pHeader->dwHashTableSize, ha->pHeader->dwBlockTableSize); ha->pHashTable = ALLOCMEM(TMPQHash, ha->pHeader->dwHashTableSize); ha->pBlockTable = ALLOCMEM(TMPQBlock, dwBlockTableSize); ha->pExtBlockTable = ALLOCMEM(TMPQBlockEx, dwBlockTableSize); ha->pbBlockBuffer = ALLOCMEM(BYTE, ha->dwBlockSize); if(!ha->pHashTable || !ha->pBlockTable || !ha->pExtBlockTable || !ha->pbBlockBuffer) nError = ERROR_NOT_ENOUGH_MEMORY; } // Read the hash table into memory if(nError == ERROR_SUCCESS) { dwBytes = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); SetFilePointer(ha->hFile, ha->HashTablePos.LowPart, &ha->HashTablePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pHashTable, dwBytes, &dwTransferred, NULL); if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } // Decrypt hash table and check if it is correctly decrypted if(nError == ERROR_SUCCESS) { TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; TMPQHash * pHash; // We have to convert the hash table from LittleEndian BSWAP_ARRAY32_UNSIGNED((DWORD *)ha->pHashTable, (dwBytes / sizeof(DWORD))); DecryptHashTable((DWORD *)ha->pHashTable, (BYTE *)"(hash table)", (ha->pHeader->dwHashTableSize * 4)); // Check hash table if is correctly decrypted for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) { // Note: Some MPQs from World of Warcraft have wPlatform set to 0x0100. // If not free or deleted hash entry, check for valid values if(pHash->dwBlockIndex < HASH_ENTRY_DELETED) { // The block index should not be larger than size of the block table if(pHash->dwBlockIndex > ha->pHeader->dwBlockTableSize) { nError = ERROR_BAD_FORMAT; break; } // Remember the highest block table entry if(pHash->dwBlockIndex > dwMaxBlockIndex) dwMaxBlockIndex = pHash->dwBlockIndex; } } } // Now, read the block table if(nError == ERROR_SUCCESS) { memset(ha->pBlockTable, 0, dwBlockTableSize * sizeof(TMPQBlock)); dwBytes = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); SetFilePointer(ha->hFile, ha->BlockTablePos.LowPart, &ha->BlockTablePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pBlockTable, dwBytes, &dwTransferred, NULL); // We have to convert every DWORD in ha->block from LittleEndian BSWAP_ARRAY32_UNSIGNED((DWORD *)ha->pBlockTable, dwBytes / sizeof(DWORD)); if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } // Decrypt block table. // Some MPQs don't have Decrypted block table, e.g. cracked Diablo version // We have to check if block table is really encrypted if(nError == ERROR_SUCCESS) { TMPQBlock * pBlockEnd = ha->pBlockTable + ha->pHeader->dwBlockTableSize; TMPQBlock * pBlock = ha->pBlockTable; BOOL bBlockTableEncrypted = FALSE; // Verify all blocks entries in the table // The loop usually stops at the first entry while(pBlock < pBlockEnd) { // The lower 8 bits of the MPQ flags are always zero. // Note that this may change in next MPQ versions if(pBlock->dwFlags & 0x000000FF) { bBlockTableEncrypted = TRUE; break; } // Move to the next block table entry pBlock++; } if(bBlockTableEncrypted) { DecryptBlockTable((DWORD *)ha->pBlockTable, (BYTE *)"(block table)", (ha->pHeader->dwBlockTableSize * 4)); } } // Now, read the extended block table. // For V1 archives, we still will maintain the extended block table // (it will be filled with zeros) // TODO: Test with >4GB if(nError == ERROR_SUCCESS) { memset(ha->pExtBlockTable, 0, dwBlockTableSize * sizeof(TMPQBlockEx)); if(ha->pHeader->ExtBlockTablePos.QuadPart != 0) { dwBytes = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlockEx); SetFilePointer(ha->hFile, ha->ExtBlockTablePos.LowPart, &ha->ExtBlockTablePos.HighPart, FILE_BEGIN); ReadFile(ha->hFile, ha->pExtBlockTable, dwBytes, &dwTransferred, NULL); // We have to convert every DWORD in ha->block from LittleEndian BSWAP_ARRAY16_UNSIGNED((USHORT *)ha->pExtBlockTable, dwBytes / sizeof(USHORT)); // The extended block table is not encrypted (so far) if(dwTransferred != dwBytes) nError = ERROR_FILE_CORRUPT; } } // Verify the both block tables (If the MPQ file is not protected) if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0) { TMPQBlockEx * pBlockEx = ha->pExtBlockTable; TMPQBlock * pBlockEnd = ha->pBlockTable + dwMaxBlockIndex + 1; TMPQBlock * pBlock = ha->pBlockTable; // If the MPQ file is not protected, // we will check if all sizes in the block table is correct. // Note that we will not relocate the block table (change from previous versions) for(; pBlock < pBlockEnd; pBlock++, pBlockEx++) { if(pBlock->dwFlags & MPQ_FILE_EXISTS) { // Get the 64-bit file position TempPos.HighPart = pBlockEx->wFilePosHigh; TempPos.LowPart = pBlock->dwFilePos; if(TempPos.QuadPart > ha->MpqSize.QuadPart || pBlock->dwCSize > ha->MpqSize.QuadPart) { nError = ERROR_BAD_FORMAT; break; } } } } // If the user didn't specified otherwise, // include the internal listfile to the TMPQArchive structure if((dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { if(nError == ERROR_SUCCESS) SListFileCreateListFile(ha); // Add the internal listfile if(nError == ERROR_SUCCESS) SFileAddListFile((HANDLE)ha, NULL); } // Cleanup and exit if(nError != ERROR_SUCCESS) { FreeMPQArchive(ha); if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); SetLastError(nError); } else { if(pFirstOpen == NULL) pFirstOpen = ha; } *phMPQ = ha; return (nError == ERROR_SUCCESS); }
bool WINAPI SFileOpenArchive( const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq) { TMPQUserData * pUserData; TFileStream * pStream = NULL; // Open file stream TMPQArchive * ha = NULL; // Archive handle TFileEntry * pFileEntry; ULONGLONG FileSize = 0; // Size of the file int nError = ERROR_SUCCESS; // Verify the parameters if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) nError = ERROR_INVALID_PARAMETER; // One time initialization of MPQ cryptography InitializeMpqCryptography(); dwPriority = dwPriority; // Open the MPQ archive file if(nError == ERROR_SUCCESS) { DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK); // If not forcing MPQ v 1.0, also use file bitmap dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP; // Initialize the stream pStream = FileStream_OpenFile(szMpqName, dwStreamFlags); if(pStream == NULL) nError = GetLastError(); } // 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_BAD_FORMAT; } // Allocate the MPQhandle if(nError == ERROR_SUCCESS) { if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize handle structure and allocate structure for MPQ header if(nError == ERROR_SUCCESS) { ULONGLONG SearchOffset = 0; DWORD dwStreamFlags = 0; DWORD dwHeaderSize; DWORD dwHeaderID; memset(ha, 0, sizeof(TMPQArchive)); ha->pfnHashString = HashString; ha->pStream = pStream; pStream = NULL; // Set the archive read only if the stream is read-only FileStream_GetFlags(ha->pStream, &dwStreamFlags); ha->dwFlags |= (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0; // Also remember if we shall check sector CRCs when reading file if(dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ha->dwFlags |= MPQ_FLAG_CHECK_SECTOR_CRC; // Find the offset of MPQ header within the file while(SearchOffset < FileSize) { DWORD dwBytesAvailable = MPQ_HEADER_SIZE_V4; // Cut the bytes available, if needed if((FileSize - SearchOffset) < MPQ_HEADER_SIZE_V4) dwBytesAvailable = (DWORD)(FileSize - SearchOffset); // Read the eventual MPQ header if(!FileStream_Read(ha->pStream, &SearchOffset, ha->HeaderData, dwBytesAvailable)) { nError = GetLastError(); break; } // There are AVI files from Warcraft III with 'MPQ' extension. if(SearchOffset == 0 && IsAviFile(ha->HeaderData)) { nError = ERROR_AVI_FILE; break; } // If there is the MPQ user data signature, process it dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]); if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0) { // Verify if this looks like a valid user data pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData); if(pUserData != NULL) { // Fill the user data header ha->UserDataPos = SearchOffset; ha->pUserData = &ha->UserData; memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData)); // Continue searching from that position SearchOffset += ha->pUserData->dwHeaderOffs; continue; } } // There must be MPQ header signature. Note that STORM.dll from Warcraft III actually // tests the MPQ header size. It must be at least 0x20 bytes in order to load it // Abused by Spazzler Map protector. Note that the size check is not present // in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway. dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]); if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1) { // Now convert the header to version 4 nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags); break; } // Check for MPK archives (Longwu Online - MPQ fork) if(dwHeaderID == ID_MPK) { // Now convert the MPK header to MPQ Header version 4 nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags); break; } // If searching for the MPQ header is disabled, return an error if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH) { nError = ERROR_NOT_SUPPORTED; break; } // Move to the next possible offset SearchOffset += 0x200; } // Did we identify one of the supported headers? if(nError == ERROR_SUCCESS) { // Set the user data position to the MPQ header, if none if(ha->pUserData == NULL) ha->UserDataPos = SearchOffset; // Set the position of the MPQ header ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->MpqPos = SearchOffset; // Sector size must be nonzero. if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0) nError = ERROR_BAD_FORMAT; } } // Fix table positions according to format if(nError == ERROR_SUCCESS) { // Dump the header // DumpMpqHeader(ha->pHeader); // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data, // 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) { ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1; ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1; ha->dwFlags |= MPQ_FLAG_READ_ONLY; ha->pUserData = NULL; } // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); // Verify if any of the tables doesn't start beyond the end of the file nError = VerifyMpqTablePositions(ha, FileSize); } // Read the hash table. Ignore the result, as hash table is no longer required // Read HET table. Ignore the result, as HET table is no longer required if(nError == ERROR_SUCCESS) { nError = LoadAnyHashTable(ha); } // Now, build the file table. It will be built by combining // the block table, BET table, hi-block table, (attributes) and (listfile). if(nError == ERROR_SUCCESS) { nError = BuildFileTable(ha); } // Verify the file table, if no kind of malformation was detected if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_MALFORMED) == 0) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; ULONGLONG RawFilePos; // Parse all file entries for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // If that file entry is valid, check the file position if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) { // Get the 64-bit file position, // relative to the begin of the file RawFilePos = ha->MpqPos + pFileEntry->ByteOffset; // Begin of the file must be within range if(RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } // End of the file must be within range RawFilePos += pFileEntry->dwCmpSize; if(RawFilePos > FileSize) { nError = ERROR_FILE_CORRUPT; break; } } } } // Load the internal listfile and include it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0) { // 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; } } // Load the "(attributes)" file and merge it to the file table if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0) { // 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; } } // Cleanup and exit if(nError != ERROR_SUCCESS) { FileStream_Close(pStream); FreeMPQArchive(ha); SetLastError(nError); ha = NULL; } *phMpq = ha; return (nError == ERROR_SUCCESS); }
BOOL WINAPI SFileCreateArchiveEx(const char * szMpqName, DWORD dwCreationDisposition, DWORD dwHashTableSize, HANDLE * phMPQ) { LARGE_INTEGER MpqPos = {0}; // Position of MPQ header in the file TMPQArchive * ha = NULL; // MPQ archive handle HANDLE hFile = INVALID_HANDLE_VALUE; // File handle DWORD dwTransferred = 0; // Number of bytes written into the archive USHORT wFormatVersion; BOOL bCreateAttributes = FALSE; BOOL bFileExists = FALSE; int nIndex = 0; int nError = ERROR_SUCCESS; // Pre-initialize the result value if(phMPQ != NULL) *phMPQ = NULL; // Check the parameters, if they are valid if(szMpqName == NULL || *szMpqName == 0 || phMPQ == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // Check the value of dwCreationDisposition against file existence bFileExists = (GetFileAttributes(szMpqName) != 0xFFFFFFFF); // Extract format version from the "dwCreationDisposition" bCreateAttributes = (dwCreationDisposition & MPQ_CREATE_ATTRIBUTES); wFormatVersion = (USHORT)((dwCreationDisposition >> 0x10) & 0x0000000F); dwCreationDisposition &= 0x0000FFFF; // If the file exists and open required, do it. if(bFileExists && (dwCreationDisposition == OPEN_EXISTING || dwCreationDisposition == OPEN_ALWAYS)) { // Try to open the archive normal way. If it fails, it means that // the file exist, but it is not a MPQ archive. if(SFileOpenArchiveEx(szMpqName, 0, 0, phMPQ, GENERIC_READ | GENERIC_WRITE)) return TRUE; // If the caller required to open the existing archive, // and the file is not MPQ archive, return error if(dwCreationDisposition == OPEN_EXISTING) return FALSE; } // Two error cases if(dwCreationDisposition == CREATE_NEW && bFileExists) { SetLastError(ERROR_ALREADY_EXISTS); return FALSE; } if(dwCreationDisposition == OPEN_EXISTING && bFileExists == FALSE) { SetLastError(ERROR_FILE_NOT_FOUND); return FALSE; } // At this point, we have to create the archive. If the file exists, // we will convert it to MPQ archive. // Check the value of hash table size. It has to be a power of two // and must be between HASH_TABLE_SIZE_MIN and HASH_TABLE_SIZE_MAX if(dwHashTableSize < HASH_TABLE_SIZE_MIN) dwHashTableSize = HASH_TABLE_SIZE_MIN; if(dwHashTableSize > HASH_TABLE_SIZE_MAX) dwHashTableSize = HASH_TABLE_SIZE_MAX; // Round the hash table size up to the nearest power of two for(nIndex = 0; PowersOfTwo[nIndex] != 0; nIndex++) { if(dwHashTableSize <= PowersOfTwo[nIndex]) { dwHashTableSize = PowersOfTwo[nIndex]; break; } } // Prepare the buffer for decryption engine if(nError == ERROR_SUCCESS) nError = PrepareStormBuffer(); // Get the position where the MPQ header will begin. if(nError == ERROR_SUCCESS) { hFile = CreateFile(szMpqName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, dwCreationDisposition, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) nError = GetLastError(); } // Retrieve the file size and round it up to 0x200 bytes if(nError == ERROR_SUCCESS) { MpqPos.LowPart = GetFileSize(hFile, (LPDWORD)&MpqPos.HighPart); MpqPos.QuadPart += 0x1FF; MpqPos.LowPart &= 0xFFFFFE00; if(wFormatVersion == MPQ_FORMAT_VERSION_1 && MpqPos.HighPart != 0) nError = ERROR_DISK_FULL; if(wFormatVersion == MPQ_FORMAT_VERSION_2 && MpqPos.HighPart > 0x0000FFFF) nError = ERROR_DISK_FULL; } // Move to the end of the file (i.e. begin of the MPQ) if(nError == ERROR_SUCCESS) { if(SetFilePointer(hFile, MpqPos.LowPart, &MpqPos.HighPart, FILE_BEGIN) == 0xFFFFFFFF) nError = GetLastError(); } // Set the new end of the file to the MPQ header position if(nError == ERROR_SUCCESS) { if(!SetEndOfFile(hFile)) nError = GetLastError(); } // Create the archive handle if(nError == ERROR_SUCCESS) { if((ha = ALLOCMEM(TMPQArchive, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Fill the MPQ archive handle structure and create the header, // block buffer, hash table and block table if(nError == ERROR_SUCCESS) { memset(ha, 0, sizeof(TMPQArchive)); strcpy(ha->szFileName, szMpqName); ha->hFile = hFile; ha->dwBlockSize = 0x200 << DEFAULT_BLOCK_SIZE; ha->MpqPos = MpqPos; ha->pHeader = &ha->Header; ha->pHashTable = ALLOCMEM(TMPQHash, dwHashTableSize); ha->pBlockTable = ALLOCMEM(TMPQBlock, dwHashTableSize); ha->pExtBlockTable = ALLOCMEM(TMPQBlockEx, dwHashTableSize); ha->pbBlockBuffer = ALLOCMEM(BYTE, ha->dwBlockSize); ha->pListFile = NULL; ha->dwFlags |= MPQ_FLAG_CHANGED; if(!ha->pHashTable || !ha->pBlockTable || !ha->pExtBlockTable || !ha->pbBlockBuffer) nError = GetLastError(); hFile = INVALID_HANDLE_VALUE; } // Fill the MPQ header and all buffers if(nError == ERROR_SUCCESS) { LARGE_INTEGER TempPos; TMPQHeader2 * pHeader = ha->pHeader; DWORD dwHeaderSize = (wFormatVersion == MPQ_FORMAT_VERSION_2) ? sizeof(TMPQHeader2) : sizeof(TMPQHeader); memset(pHeader, 0, sizeof(TMPQHeader2)); pHeader->dwID = ID_MPQ; pHeader->dwHeaderSize = dwHeaderSize; pHeader->dwArchiveSize = pHeader->dwHeaderSize + dwHashTableSize * sizeof(TMPQHash); pHeader->wFormatVersion = wFormatVersion; pHeader->wBlockSize = 3; // 0x1000 bytes per block pHeader->dwHashTableSize = dwHashTableSize; // Set proper hash table positions ha->HashTablePos.QuadPart = ha->MpqPos.QuadPart + pHeader->dwHeaderSize; ha->pHeader->dwHashTablePos = pHeader->dwHeaderSize; ha->pHeader->wHashTablePosHigh = 0; // Set proper block table positions ha->BlockTablePos.QuadPart = ha->HashTablePos.QuadPart + (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); TempPos.QuadPart = ha->BlockTablePos.QuadPart - ha->MpqPos.QuadPart; ha->pHeader->dwBlockTablePos = TempPos.LowPart; ha->pHeader->wBlockTablePosHigh = (USHORT)TempPos.HighPart; // For now, we set extended block table positioon top zero unless we add enough // files to cause the archive size exceed 4 GB ha->ExtBlockTablePos.QuadPart = 0; // Clear all tables memset(ha->pBlockTable, 0, sizeof(TMPQBlock) * dwHashTableSize); memset(ha->pExtBlockTable, 0, sizeof(TMPQBlockEx) * dwHashTableSize); memset(ha->pHashTable, 0xFF, sizeof(TMPQHash) * dwHashTableSize); } // Write the MPQ header to the file if(nError == ERROR_SUCCESS) { DWORD dwHeaderSize = ha->pHeader->dwHeaderSize; BSWAP_TMPQHEADER(ha->pHeader); WriteFile(ha->hFile, ha->pHeader, dwHeaderSize, &dwTransferred, NULL); BSWAP_TMPQHEADER(ha->pHeader); if(dwTransferred != ha->pHeader->dwHeaderSize) nError = ERROR_DISK_FULL; ha->MpqSize.QuadPart += dwTransferred; } // Create the internal listfile if(nError == ERROR_SUCCESS) nError = SListFileCreateListFile(ha); // Try to add the internal listfile, attributes. // Also add internal listfile to the search lists if(nError == ERROR_SUCCESS) { if(SFileAddListFile((HANDLE)ha, NULL) != ERROR_SUCCESS) AddInternalFile(ha, LISTFILE_NAME); } // Create the file attributes if(nError == ERROR_SUCCESS && bCreateAttributes) { if(SAttrFileCreate(ha) == ERROR_SUCCESS) AddInternalFile(ha, ATTRIBUTES_NAME); } // Cleanup : If an error, delete all buffers and return if(nError != ERROR_SUCCESS) { FreeMPQArchive(ha); if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); SetLastError(nError); ha = NULL; } // Return the values *phMPQ = (HANDLE)ha; return (nError == ERROR_SUCCESS); }