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 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 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; 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; // The new limit must not be lower than the index of the last file entry in the table if(nError == ERROR_SUCCESS && ha->dwFileTableSize > dwMaxFileCount) nError = ERROR_DISK_FULL; // ALL file names must be known in order to be able // to rebuild hash table size if(nError == ERROR_SUCCESS) { nError = CheckIfAllFilesKnown(ha, NULL, NULL); } // 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(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; // 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) { dwHashIndex = AllocateHashEntry(ha, pFileEntry); if(dwHashIndex == HASH_ENTRY_FREE) { 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++; } } } // Mark the archive as changed // Note: We always have to rebuild the (attributes) file due to file table change if(nError == ERROR_SUCCESS) { ha->dwMaxFileCount = dwMaxFileCount; 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); } // Return the result return (nError == ERROR_SUCCESS); }