static int WriteNakedMPQHeader(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; TMPQHeader Header; DWORD dwBytesToWrite = pHeader->dwHeaderSize; int nError = ERROR_SUCCESS; // Prepare the naked MPQ header memset(&Header, 0, sizeof(TMPQHeader)); Header.dwID = pHeader->dwID; Header.dwHeaderSize = pHeader->dwHeaderSize; Header.dwArchiveSize = pHeader->dwHeaderSize; Header.wFormatVersion = pHeader->wFormatVersion; Header.wSectorSize = pHeader->wSectorSize; // 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)) nError = GetLastError(); return nError; }
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 WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bReserved */) { TFileStream * pTempStream = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; ULONGLONG ByteOffset; ULONGLONG ByteCount; LPDWORD pFileKeys = NULL; char szTempFile[MAX_PATH] = ""; char * szTemp = NULL; int nError = ERROR_SUCCESS; // Test the valid parameters if (!IsValidMpqHandle(ha)) nError = ERROR_INVALID_HANDLE; if (ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; if (ha->pHetTable != NULL || ha->pBetTable != NULL) nError = ERROR_ACCESS_DENIED; // Create the table with file keys if (nError == ERROR_SUCCESS) { if ((pFileKeys = ALLOCMEM(DWORD, ha->dwFileTableSize)) != NULL) memset(pFileKeys, 0, sizeof(DWORD) * ha->dwFileTableSize); else nError = ERROR_NOT_ENOUGH_MEMORY; } // First of all, we have to check of we are able to decrypt all files. // If not, sorry, but the archive cannot be compacted. if (nError == ERROR_SUCCESS) { // Initialize the progress variables for compact callback FileStream_GetSize(ha->pStream, CompactTotalBytes); CompactBytesProcessed = 0; nError = CheckIfAllFilesKnown(ha, szListFile, pFileKeys); } // Get the temporary file name and create it if (nError == ERROR_SUCCESS) { strcpy(szTempFile, ha->pStream->szFileName); if ((szTemp = strrchr(szTempFile, '.')) != NULL) strcpy(szTemp + 1, "mp_"); else strcat(szTempFile, "_"); pTempStream = FileStream_CreateFile(szTempFile); if (pTempStream == NULL) nError = GetLastError(); } // Write the data before MPQ user data (if any) if (nError == ERROR_SUCCESS && ha->UserDataPos != 0) { // Inform the application about the progress if (CompactCB != NULL) CompactCB(pvUserData, CCB_COPYING_NON_MPQ_DATA, CompactBytesProcessed, CompactTotalBytes); ByteOffset = 0; ByteCount = ha->UserDataPos; nError = CopyNonMpqData(ha->pStream, pTempStream, ByteOffset, ByteCount); } // Write the MPQ user data (if any) if (nError == ERROR_SUCCESS && ha->MpqPos > ha->UserDataPos) { // At this point, we assume that the user data size is equal // to pUserData->dwHeaderOffs. // If this assumption doesn't work, then we have an unknown version of MPQ ByteOffset = ha->UserDataPos; ByteCount = ha->MpqPos - ha->UserDataPos; assert(ha->pUserData != NULL); assert(ha->pUserData->dwHeaderOffs == ByteCount); nError = CopyNonMpqData(ha->pStream, pTempStream, ByteOffset, ByteCount); } // Write the MPQ header if (nError == ERROR_SUCCESS) { // Remember the header size before swapping DWORD dwBytesToWrite = ha->pHeader->dwHeaderSize; BSWAP_TMPQHEADER(ha->pHeader); if (!FileStream_Write(pTempStream, NULL, ha->pHeader, dwBytesToWrite)) nError = GetLastError(); BSWAP_TMPQHEADER(ha->pHeader); // Update the progress CompactBytesProcessed += ha->pHeader->dwHeaderSize; ha->dwFlags &= ~MPQ_FLAG_NO_HEADER; } // Now copy all files if (nError == ERROR_SUCCESS) { nError = CopyMpqFiles(ha, pFileKeys, pTempStream); } // If succeeded, switch the streams if (nError == ERROR_SUCCESS) { if (FileStream_MoveFile(ha->pStream, pTempStream)) pTempStream = NULL; else nError = ERROR_CAN_NOT_COMPLETE; } // If all succeeded, save the MPQ tables if (nError == ERROR_SUCCESS) { // // Note: We don't recalculate position of the MPQ tables at this point. // SaveMPQTables does it automatically. // nError = SaveMPQTables(ha); if (nError == ERROR_SUCCESS && CompactCB != NULL) { CompactBytesProcessed += (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); CompactBytesProcessed += (ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock)); CompactCB(pvUserData, CCB_CLOSING_ARCHIVE, CompactBytesProcessed, CompactTotalBytes); } } // Invalidate the compact callback pvUserData = NULL; CompactCB = NULL; // Cleanup and return if (pTempStream != NULL) FileStream_Close(pTempStream); if (pFileKeys != NULL) FREEMEM(pFileKeys); if (nError != ERROR_SUCCESS) SetLastError(nError); 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 SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bReserved */) { TFileStream * pTempStream = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; ULONGLONG ByteOffset; ULONGLONG ByteCount; LPDWORD pFileKeys = NULL; TCHAR szTempFile[MAX_PATH] = _T(""); TCHAR * szTemp = NULL; int nError = ERROR_SUCCESS; // Test the valid parameters if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; // If the MPQ is changed at this moment, we have to flush the archive if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_CHANGED)) { SFileFlushArchive(hMpq); } // Create the table with file keys if(nError == ERROR_SUCCESS) { if((pFileKeys = STORM_ALLOC(DWORD, ha->dwFileTableSize)) != NULL) memset(pFileKeys, 0, sizeof(DWORD) * ha->dwFileTableSize); else nError = ERROR_NOT_ENOUGH_MEMORY; } // First of all, we have to check of we are able to decrypt all files. // If not, sorry, but the archive cannot be compacted. if(nError == ERROR_SUCCESS) { // Initialize the progress variables for compact callback FileStream_GetSize(ha->pStream, &(ha->CompactTotalBytes)); ha->CompactBytesProcessed = 0; nError = CheckIfAllFilesKnown(ha, szListFile, pFileKeys); } // Get the temporary file name and create it if(nError == ERROR_SUCCESS) { _tcscpy(szTempFile, FileStream_GetFileName(ha->pStream)); if((szTemp = _tcsrchr(szTempFile, '.')) != NULL) _tcscpy(szTemp + 1, _T("mp_")); else _tcscat(szTempFile, _T("_")); pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); if(pTempStream == NULL) nError = GetLastError(); } // Write the data before MPQ user data (if any) if(nError == ERROR_SUCCESS && ha->UserDataPos != 0) { // Inform the application about the progress if(ha->pfnCompactCB != NULL) ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes); ByteOffset = 0; ByteCount = ha->UserDataPos; nError = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount); } // Write the MPQ user data (if any) if(nError == ERROR_SUCCESS && ha->MpqPos > ha->UserDataPos) { // At this point, we assume that the user data size is equal // to pUserData->dwHeaderOffs. // If this assumption doesn't work, then we have an unknown version of MPQ ByteOffset = ha->UserDataPos; ByteCount = ha->MpqPos - ha->UserDataPos; assert(ha->pUserData != NULL); assert(ha->pUserData->dwHeaderOffs == ByteCount); nError = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount); } // Write the MPQ header if(nError == ERROR_SUCCESS) { TMPQHeader SaveMpqHeader; // 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)) nError = GetLastError(); // Update the progress ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize; } // Now copy all files if(nError == ERROR_SUCCESS) nError = CopyMpqFiles(ha, pFileKeys, pTempStream); // 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 if(nError == ERROR_SUCCESS) { if(FileStream_Replace(ha->pStream, pTempStream)) pTempStream = NULL; else nError = ERROR_CAN_NOT_COMPLETE; } // If all succeeded, save the MPQ tables if(nError == ERROR_SUCCESS) { // // Note: We don't recalculate position of the MPQ tables at this point. // SaveMPQTables does it automatically. // nError = SaveMPQTables(ha); if(nError == ERROR_SUCCESS && ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); ha->CompactBytesProcessed += (ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock)); ha->pfnCompactCB(ha->pvCompactUserData, CCB_CLOSING_ARCHIVE, ha->CompactBytesProcessed, ha->CompactTotalBytes); } } // Cleanup and return if(pTempStream != NULL) FileStream_Close(pTempStream); if(pFileKeys != NULL) STORM_FREE(pFileKeys); if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }
int SFileAddFile_Init( TMPQArchive * ha, const char * szArchivedName, TMPQFileTime * pFT, DWORD dwFileSize, LCID lcLocale, DWORD dwFlags, TMPQFile ** phf) { LARGE_INTEGER TempPos; // For various file offset calculations TMPQFile * hf = NULL; // File structure for newly added file int nError = ERROR_SUCCESS; // // Note: This is an internal function so no validity checks are done. // It is the caller's responsibility to make sure that no invalid // flags get to this point // // Adjust file flags for too-small files if(dwFileSize < 0x04) dwFlags &= ~(MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY); if(dwFileSize < 0x20) dwFlags &= ~(MPQ_FILE_COMPRESSED | MPQ_FILE_SECTOR_CRC); // Allocate the TMPQFile entry for newly added file hf = CreateMpqFile(ha, szArchivedName); if(hf == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; // If the MPQ header has not yet been written, do it now if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_NO_HEADER)) { // Remember the header size before swapping DWORD dwBytesToWrite = ha->pHeader->dwHeaderSize; BSWAP_TMPQHEADER(ha->pHeader); if(FileStream_Write(ha->pStream, &ha->MpqPos, ha->pHeader, dwBytesToWrite)) ha->dwFlags &= ~MPQ_FLAG_NO_HEADER; else nError = GetLastError(); BSWAP_TMPQHEADER(ha->pHeader); } if(nError == ERROR_SUCCESS) { // Check if the file already exists in the archive if((hf->pHash = GetHashEntryExact(ha, szArchivedName, lcLocale)) != NULL) { if(dwFlags & MPQ_FILE_REPLACEEXISTING) { hf->pBlockEx = ha->pExtBlockTable + hf->pHash->dwBlockIndex; hf->pBlock = ha->pBlockTable + hf->pHash->dwBlockIndex; } else { nError = ERROR_ALREADY_EXISTS; hf->pHash = NULL; } } if(nError == ERROR_SUCCESS && hf->pHash == NULL) { hf->pHash = FindFreeHashEntry(ha, szArchivedName); if(hf->pHash == NULL) { nError = ERROR_DISK_FULL; } } // Set the hash index hf->dwHashIndex = (DWORD)(hf->pHash - ha->pHashTable); hf->bIsWriteHandle = true; } // Find a free space in the MPQ, as well as free block table entry if(nError == ERROR_SUCCESS) { DWORD dwFreeBlock = FindFreeMpqSpace(ha, &hf->MpqFilePos); // Calculate the raw file offset hf->RawFilePos.QuadPart = ha->MpqPos.QuadPart + hf->MpqFilePos.QuadPart; // When format V1, the size of the archive cannot exceed 4 GB if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) { TempPos.QuadPart = hf->MpqFilePos.QuadPart + dwFileSize; TempPos.QuadPart += ha->pHeader->dwHashTableSize * sizeof(TMPQHash); TempPos.QuadPart += ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); TempPos.QuadPart += ha->pHeader->dwBlockTableSize * sizeof(TMPQBlockEx); if(TempPos.HighPart != 0) nError = ERROR_DISK_FULL; } // If we didn't get a block table entry assigned from hash table, assign it now if(hf->pBlock == NULL) { // Note: dwFreeBlock can be greater than dwHashTableSize, // in case that block table is bigger than hash table if(dwFreeBlock != 0xFFFFFFFF) { hf->pBlockEx = ha->pExtBlockTable + dwFreeBlock; hf->pBlock = ha->pBlockTable + dwFreeBlock; } else { nError = ERROR_DISK_FULL; } } // Calculate the index to the block table hf->dwBlockIndex = (DWORD)(hf->pBlock - ha->pBlockTable); } // Create key for file encryption if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED)) { szArchivedName = GetPlainMpqFileName(szArchivedName); hf->dwFileKey = DecryptFileKey(szArchivedName); if(dwFlags & MPQ_FILE_FIX_KEY) hf->dwFileKey = (hf->dwFileKey + hf->MpqFilePos.LowPart) ^ dwFileSize; } if(nError == ERROR_SUCCESS) { // Initialize the hash entry for the file hf->pHash->dwBlockIndex = hf->dwBlockIndex; hf->pHash->lcLocale = (USHORT)lcLocale; // Initialize the block table entry for the file hf->pBlockEx->wFilePosHigh = (USHORT)hf->MpqFilePos.HighPart; hf->pBlock->dwFilePos = hf->MpqFilePos.LowPart; hf->pBlock->dwFSize = dwFileSize; hf->pBlock->dwCSize = 0; hf->pBlock->dwFlags = dwFlags | MPQ_FILE_EXISTS; // Resolve CRC32 and MD5 entry for the file // Only do it when the MPQ archive has attributes if(ha->pAttributes != NULL) { hf->pFileTime = ha->pAttributes->pFileTime + hf->dwBlockIndex; hf->pCrc32 = ha->pAttributes->pCrc32 + hf->dwBlockIndex; hf->pMd5 = ha->pAttributes->pMd5 + hf->dwBlockIndex; // If the file has been overwritten, there still might be // stale entries in the attributes memset(hf->pFileTime, 0, sizeof(TMPQFileTime)); memset(hf->pMd5, 0, sizeof(TMPQMD5)); hf->pCrc32[0] = 0; // Initialize the file time, CRC32 and MD5 assert(sizeof(hf->hctx) >= sizeof(hash_state)); tommd5_init((hash_state *)hf->hctx); hf->dwCrc32 = crc32(0, Z_NULL, 0); // If the caller gave us a file time, use it. if(pFT != NULL) *hf->pFileTime = *pFT; } // Call the callback, if needed if(AddFileCB != NULL) AddFileCB(pvUserData, 0, hf->pBlock->dwFSize, false); } // If an error occured, remember it if(nError != ERROR_SUCCESS) hf->bErrorOccured = true; *phf = hf; return nError; }
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); }
BOOL WINAPI SFileCompactArchive(HANDLE hMPQ, const char * szListFile, BOOL /* bReserved */) { TMPQArchive * ha = (TMPQArchive *)hMPQ; HANDLE hFile = INVALID_HANDLE_VALUE; DWORD * pFileSeeds = NULL; char szTempFile[MAX_PATH] = ""; char * szTemp = NULL; DWORD dwTransferred; int nError = ERROR_SUCCESS; // Test the valid parameters if(!IsValidMpqHandle(ha)) nError = ERROR_INVALID_PARAMETER; // Create the table with file seeds if(nError == ERROR_SUCCESS) { if((pFileSeeds = ALLOCMEM(DWORD, ha->pHeader->dwBlockTableSize)) != NULL) memset(pFileSeeds, 0, sizeof(DWORD) * ha->pHeader->dwBlockTableSize); else nError = ERROR_NOT_ENOUGH_MEMORY; } // First of all, we have to check of we are able to decrypt all files. // If not, sorry, but the archive cannot be compacted. if(nError == ERROR_SUCCESS) nError = CheckIfAllFilesKnown(ha, szListFile, pFileSeeds); // Get the temporary file name and create it if(nError == ERROR_SUCCESS) { if(CompactCB != NULL) CompactCB(lpUserData, CCB_COPYING_NON_MPQ_DATA, 0, 0); strcpy(szTempFile, ha->szFileName); if((szTemp = strrchr(szTempFile, '.')) != NULL) strcpy(szTemp + 1, "mp_"); else strcat(szTempFile, "_"); hFile = CreateFile(szTempFile, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); if(hFile == INVALID_HANDLE_VALUE) nError = GetLastError(); } // Write the data before MPQ header (if any) if(nError == ERROR_SUCCESS && ha->MpqPos.QuadPart > 0) { SetFilePointer(ha->hFile, 0, NULL, FILE_BEGIN); if(ha->pShunt != NULL) nError = CopyNonMpqData(ha->hFile, hFile, ha->ShuntPos); else nError = CopyNonMpqData(ha->hFile, hFile, ha->MpqPos); } // Write the MPQ shunt (if any) if(nError == ERROR_SUCCESS && ha->pShunt != NULL) { BSWAP_TMPQSHUNT(ha->pShunt); WriteFile(hFile, ha->pShunt, sizeof(TMPQShunt), &dwTransferred, NULL); BSWAP_TMPQSHUNT(ha->pShunt); if(dwTransferred != sizeof(TMPQShunt)) nError = ERROR_DISK_FULL; } // Write the data between MPQ shunt and the MPQ header (if any) if(nError == ERROR_SUCCESS && ha->pShunt != NULL) { LARGE_INTEGER BytesToCopy; BytesToCopy.QuadPart = ha->MpqPos.QuadPart - (ha->ShuntPos.QuadPart + sizeof(TMPQShunt)); nError = CopyNonMpqData(ha->hFile, hFile, BytesToCopy); } // Write the MPQ header if(nError == ERROR_SUCCESS) { BSWAP_TMPQHEADER(ha->pHeader); WriteFile(hFile, ha->pHeader, ha->pHeader->dwHeaderSize, &dwTransferred, NULL); BSWAP_TMPQHEADER(ha->pHeader); if(dwTransferred != ha->pHeader->dwHeaderSize) nError = ERROR_DISK_FULL; } // Write the data between the header and between the first file // For this, we have to determine where the first file begins if(nError == ERROR_SUCCESS) { LARGE_INTEGER FirstFilePos; LARGE_INTEGER TempPos; TMPQBlockEx * pBlockEx = ha->pExtBlockTable; TMPQBlock * pBlockEnd = ha->pBlockTable + ha->pHeader->dwBlockTableSize; TMPQBlock * pBlock = ha->pBlockTable; // Maximum file position FirstFilePos.HighPart = 0x7FFFFFFF; FirstFilePos.LowPart = 0xFFFFFFFF; // Find the block with the least position in the MPQ while(pBlock < pBlockEnd) { TempPos.HighPart = pBlockEx->wFilePosHigh; TempPos.LowPart = pBlock->dwFilePos; if(TempPos.QuadPart < FirstFilePos.QuadPart) FirstFilePos = TempPos; pBlockEx++; pBlock++; } // Set the position in the source file right after the file header TempPos.QuadPart = ha->MpqPos.QuadPart + ha->pHeader->dwHeaderSize; SetFilePointer(ha->hFile, TempPos.LowPart, &TempPos.HighPart, FILE_BEGIN); // Get the number of bytes to copy FirstFilePos.QuadPart -= ha->pHeader->dwHeaderSize; nError = CopyNonMpqData(ha->hFile, hFile, FirstFilePos); } // Now write all file blocks. if(nError == ERROR_SUCCESS) nError = CopyMpqFiles(hFile, ha, pFileSeeds); // Now we need to update the tables positions // (but only if the tables are at the end of the file) if(nError == ERROR_SUCCESS) { LARGE_INTEGER RelativePos; LARGE_INTEGER FilePos = {0}; // Set the hash table position FilePos.LowPart = SetFilePointer(hFile, 0, &FilePos.HighPart, FILE_CURRENT); RelativePos.QuadPart = FilePos.QuadPart - ha->MpqPos.QuadPart; ha->pHeader->wHashTablePosHigh = (USHORT)RelativePos.HighPart; ha->pHeader->dwHashTablePos = RelativePos.LowPart; ha->HashTablePos = FilePos; // Set the block table position RelativePos.QuadPart += ha->pHeader->dwHashTableSize * sizeof(TMPQHash); FilePos.QuadPart += ha->pHeader->dwHashTableSize * sizeof(TMPQHash); ha->pHeader->wBlockTablePosHigh = (USHORT)RelativePos.HighPart; ha->pHeader->dwBlockTablePos = RelativePos.LowPart; ha->BlockTablePos = FilePos; // Set the extended block table position RelativePos.QuadPart += ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); FilePos.QuadPart += ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); if(ha->ExtBlockTablePos.QuadPart != 0) { ha->pHeader->ExtBlockTablePos = RelativePos; ha->ExtBlockTablePos = FilePos; RelativePos.QuadPart += ha->pHeader->dwBlockTableSize * sizeof(TMPQBlockEx); FilePos.QuadPart += ha->pHeader->dwBlockTableSize * sizeof(TMPQBlockEx); } // Set the archive size ha->pHeader->dwArchiveSize = RelativePos.LowPart; ha->MpqSize = RelativePos; } // If succeeded, update the tables in the file if(nError == ERROR_SUCCESS) { CloseHandle(ha->hFile); ha->hFile = hFile; hFile = INVALID_HANDLE_VALUE; nError = SaveMPQTables(ha); } // If all succeeded, switch the archives if(nError == ERROR_SUCCESS) { if(CompactCB != NULL) CompactCB(lpUserData, CCB_CLOSING_ARCHIVE, 0, 0); if(!DeleteFile(ha->szFileName) || // Delete the old archive !CloseHandle(ha->hFile) || // Close the new archive !MoveFile(szTempFile, ha->szFileName)) // Rename the temporary archive nError = GetLastError(); } // Now open the freshly renamed archive file if(nError == ERROR_SUCCESS) { ha->hFile = CreateFile(ha->szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if(ha->hFile == INVALID_HANDLE_VALUE) nError = GetLastError(); } // Invalidate the positions of the archive if(nError == ERROR_SUCCESS) { ha->pLastFile = NULL; ha->dwBlockPos = 0; ha->dwBuffPos = 0; } // Cleanup and return if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); if(pFileSeeds != NULL) FREEMEM(pFileSeeds); if(nError != ERROR_SUCCESS) SetLastError(nError); DeleteFile(szTempFile); CompactCB = NULL; return (nError == ERROR_SUCCESS); }
int EXPORT_SYMBOL SFileCompactArchive(void * hMpq, const char * szListFile, int bReserved) { TFileStream * pTempStream = NULL; TMPQArchive * ha = (TMPQArchive *)hMpq; uint64_t ByteOffset; uint64_t ByteCount; uint32_t * pFileKeys = NULL; char szTempFile[1024] = ""; char * szTemp = NULL; int nError = ERROR_SUCCESS; /* Test the valid parameters */ if(!IsValidMpqHandle(hMpq)) nError = ERROR_INVALID_HANDLE; if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; /* If the MPQ is changed at this moment, we have to flush the archive */ if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_CHANGED)) { SFileFlushArchive(hMpq); } /* Create the table with file keys */ if(nError == ERROR_SUCCESS) { if((pFileKeys = STORM_ALLOC(uint32_t, ha->dwFileTableSize)) != NULL) memset(pFileKeys, 0, sizeof(uint32_t) * ha->dwFileTableSize); else nError = ERROR_NOT_ENOUGH_MEMORY; } /* First of all, we have to check of we are able to decrypt all files. */ /* If not, sorry, but the archive cannot be compacted. */ if(nError == ERROR_SUCCESS) { /* Initialize the progress variables for compact callback */ FileStream_GetSize(ha->pStream, &(ha->CompactTotalBytes)); ha->CompactBytesProcessed = 0; nError = CheckIfAllKeysKnown(ha, szListFile, pFileKeys); } /* Get the temporary file name and create it */ if(nError == ERROR_SUCCESS) { strcpy(szTempFile, FileStream_GetFileName(ha->pStream)); if((szTemp = strrchr(szTempFile, '.')) != NULL) strcpy(szTemp + 1, "mp_"); else strcat(szTempFile, "_"); pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); if(pTempStream == NULL) nError = GetLastError(); } /* Write the data before MPQ user data (if any) */ if(nError == ERROR_SUCCESS && ha->UserDataPos != 0) { /* Inform the application about the progress */ if(ha->pfnCompactCB != NULL) ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes); ByteOffset = 0; ByteCount = ha->UserDataPos; nError = CopyNonMpqData(ha, ha->pStream, pTempStream, &ByteOffset, ByteCount); } /* Write the MPQ user data (if any) */ if(nError == ERROR_SUCCESS && ha->MpqPos > ha->UserDataPos) { /* At this point, we assume that the user data size is equal */ /* to pUserData->dwHeaderOffs. */ /* If this assumption doesn't work, then we have an unknown version of MPQ */ ByteOffset = ha->UserDataPos; ByteCount = ha->MpqPos - ha->UserDataPos; assert(ha->pUserData != NULL); assert(ha->pUserData->dwHeaderOffs == ByteCount); nError = CopyNonMpqData(ha, ha->pStream, pTempStream, &ByteOffset, ByteCount); } /* Write the MPQ header */ if(nError == ERROR_SUCCESS) { TMPQHeader SaveMpqHeader; /* 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)) nError = GetLastError(); /* Update the progress */ ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize; } /* Now copy all files */ if(nError == ERROR_SUCCESS) nError = CopyMpqFiles(ha, pFileKeys, pTempStream); /* If succeeded, switch the streams */ if(nError == ERROR_SUCCESS) { ha->dwFlags |= MPQ_FLAG_CHANGED; if(FileStream_Replace(ha->pStream, pTempStream)) pTempStream = NULL; else nError = ERROR_CAN_NOT_COMPLETE; } /* Final user notification */ if(nError == ERROR_SUCCESS && ha->pfnCompactCB != NULL) { ha->CompactBytesProcessed += (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); ha->CompactBytesProcessed += (ha->dwFileTableSize * sizeof(TMPQBlock)); ha->pfnCompactCB(ha->pvCompactUserData, CCB_CLOSING_ARCHIVE, ha->CompactBytesProcessed, ha->CompactTotalBytes); } /* Cleanup and return */ if(pTempStream != NULL) FileStream_Close(pTempStream); if(pFileKeys != NULL) STORM_FREE(pFileKeys); if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }