mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-12-02 22:32:19 -05:00
b3e299dbde
Some modifications to handle backslashes and forward slashes, along with some optimizations to speed up OTR generation.
655 lines
24 KiB
C++
655 lines
24 KiB
C++
/*****************************************************************************/
|
|
/* SFileCompactArchive.cpp Copyright (c) Ladislav Zezula 2003 */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Archive compacting function */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Date Ver Who Comment */
|
|
/* -------- ---- --- ------- */
|
|
/* 14.04.03 1.00 Lad Splitted from SFileCreateArchiveEx.cpp */
|
|
/* 19.11.03 1.01 Dan Big endian handling */
|
|
/* 21.04.13 1.02 Dea Compact callback now part of TMPQArchive */
|
|
/*****************************************************************************/
|
|
|
|
#define __STORMLIB_SELF__
|
|
#include "StormLib.h"
|
|
#include "StormCommon.h"
|
|
|
|
/*****************************************************************************/
|
|
/* Local functions */
|
|
/*****************************************************************************/
|
|
|
|
static DWORD CheckIfAllFilesKnown(TMPQArchive * ha)
|
|
{
|
|
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
|
|
TFileEntry * pFileEntry;
|
|
DWORD dwBlockIndex = 0;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Verify the file table
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++, dwBlockIndex++)
|
|
{
|
|
// If there is an existing entry in the file table, check its name
|
|
if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
|
|
{
|
|
// The name must be valid and must not be a pseudo-name
|
|
if(pFileEntry->szFileName == NULL || IsPseudoFileName(pFileEntry->szFileName, NULL))
|
|
{
|
|
dwErrCode = ERROR_UNKNOWN_FILE_NAMES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
static DWORD CheckIfAllKeysKnown(TMPQArchive * ha, const TCHAR * szListFile, LPDWORD pFileKeys)
|
|
{
|
|
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
|
|
TFileEntry * pFileEntry;
|
|
DWORD dwBlockIndex = 0;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Add the listfile to the MPQ
|
|
if(szListFile != NULL)
|
|
{
|
|
// Notify the user
|
|
if(ha->pfnCompactCB != NULL)
|
|
ha->pfnCompactCB(ha->pvCompactUserData, CCB_CHECKING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
|
|
|
|
dwErrCode = SFileAddListFile((HANDLE)ha, szListFile);
|
|
}
|
|
|
|
// Verify the file table
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++, dwBlockIndex++)
|
|
{
|
|
// If the file exists and it's encrypted
|
|
if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
|
|
{
|
|
// If we know the name, we decrypt the file key from the file name
|
|
if(pFileEntry->szFileName != NULL && !IsPseudoFileName(pFileEntry->szFileName, NULL))
|
|
{
|
|
// Give the key to the caller
|
|
pFileKeys[dwBlockIndex] = DecryptFileKey(pFileEntry->szFileName,
|
|
pFileEntry->ByteOffset,
|
|
pFileEntry->dwFileSize,
|
|
pFileEntry->dwFlags);
|
|
continue;
|
|
}
|
|
|
|
// We don't know the encryption key of this file,
|
|
// thus we cannot compact the file
|
|
dwErrCode = ERROR_UNKNOWN_FILE_NAMES;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
static DWORD CopyNonMpqData(
|
|
TMPQArchive * ha,
|
|
TFileStream * pSrcStream,
|
|
TFileStream * pTrgStream,
|
|
ULONGLONG & ByteOffset,
|
|
ULONGLONG & ByteCount)
|
|
{
|
|
ULONGLONG DataSize = ByteCount;
|
|
DWORD dwToRead;
|
|
char DataBuffer[0x1000];
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Copy the data
|
|
while(DataSize > 0)
|
|
{
|
|
// Get the proper size of data
|
|
dwToRead = sizeof(DataBuffer);
|
|
if(DataSize < dwToRead)
|
|
dwToRead = (DWORD)DataSize;
|
|
|
|
// Read from the source stream
|
|
if(!FileStream_Read(pSrcStream, &ByteOffset, DataBuffer, dwToRead))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Write to the target stream
|
|
if(!FileStream_Write(pTrgStream, NULL, DataBuffer, dwToRead))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Update the progress
|
|
if(ha->pfnCompactCB != NULL)
|
|
{
|
|
ha->CompactBytesProcessed += dwToRead;
|
|
ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes);
|
|
}
|
|
|
|
// Decrement the number of data to be copied
|
|
ByteOffset += dwToRead;
|
|
DataSize -= dwToRead;
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
// Copies all file sectors into another archive.
|
|
static DWORD CopyMpqFileSectors(
|
|
TMPQArchive * ha,
|
|
TMPQFile * hf,
|
|
TFileStream * pNewStream,
|
|
ULONGLONG MpqFilePos) // MPQ file position in the new archive
|
|
{
|
|
TFileEntry * pFileEntry = hf->pFileEntry;
|
|
ULONGLONG RawFilePos; // Used for calculating sector offset in the old MPQ archive
|
|
DWORD dwBytesToCopy = pFileEntry->dwCmpSize;
|
|
DWORD dwPatchSize = 0; // Size of patch header
|
|
DWORD dwFileKey1 = 0; // File key used for decryption
|
|
DWORD dwFileKey2 = 0; // File key used for encryption
|
|
DWORD dwCmpSize = 0; // Compressed file size, including patch header
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Resolve decryption keys. Note that the file key given
|
|
// in the TMPQFile structure also includes the key adjustment
|
|
if(dwErrCode == ERROR_SUCCESS && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED))
|
|
{
|
|
dwFileKey2 = dwFileKey1 = hf->dwFileKey;
|
|
if(pFileEntry->dwFlags & MPQ_FILE_FIX_KEY)
|
|
{
|
|
dwFileKey2 = (dwFileKey1 ^ pFileEntry->dwFileSize) - (DWORD)pFileEntry->ByteOffset;
|
|
dwFileKey2 = (dwFileKey2 + (DWORD)MpqFilePos) ^ pFileEntry->dwFileSize;
|
|
}
|
|
}
|
|
|
|
// If we have to save patch header, do it
|
|
if(dwErrCode == ERROR_SUCCESS && hf->pPatchInfo != NULL)
|
|
{
|
|
BSWAP_ARRAY32_UNSIGNED(hf->pPatchInfo, sizeof(DWORD) * 3);
|
|
if(!FileStream_Write(pNewStream, NULL, hf->pPatchInfo, hf->pPatchInfo->dwLength))
|
|
dwErrCode = GetLastError();
|
|
|
|
// Save the size of the patch info
|
|
dwPatchSize = hf->pPatchInfo->dwLength;
|
|
}
|
|
|
|
// If we have to save sector offset table, do it.
|
|
if(dwErrCode == ERROR_SUCCESS && hf->SectorOffsets != NULL)
|
|
{
|
|
DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD));
|
|
DWORD dwSectorOffsLen = hf->SectorOffsets[0];
|
|
|
|
assert((pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0);
|
|
assert(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
|
|
|
|
if(SectorOffsetsCopy == NULL)
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// Encrypt the secondary sector offset table and write it to the target file
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen);
|
|
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
|
|
EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwFileKey2 - 1);
|
|
|
|
BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen);
|
|
if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorOffsLen))
|
|
dwErrCode = GetLastError();
|
|
|
|
dwBytesToCopy -= dwSectorOffsLen;
|
|
dwCmpSize += dwSectorOffsLen;
|
|
}
|
|
|
|
// Update compact progress
|
|
if(ha->pfnCompactCB != NULL)
|
|
{
|
|
ha->CompactBytesProcessed += dwSectorOffsLen;
|
|
ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
|
|
}
|
|
|
|
STORM_FREE(SectorOffsetsCopy);
|
|
}
|
|
|
|
// Now we have to copy all file sectors. We do it without
|
|
// recompression, because recompression is not necessary in this case
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++)
|
|
{
|
|
DWORD dwRawDataInSector = hf->dwSectorSize;
|
|
DWORD dwRawByteOffset = dwSector * hf->dwSectorSize;
|
|
|
|
// Fix the raw data length if the file is compressed
|
|
if(hf->SectorOffsets != NULL)
|
|
{
|
|
dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector];
|
|
dwRawByteOffset = hf->SectorOffsets[dwSector];
|
|
}
|
|
|
|
// Last sector: If there is not enough bytes remaining in the file, cut the raw size
|
|
if(dwRawDataInSector > dwBytesToCopy)
|
|
dwRawDataInSector = dwBytesToCopy;
|
|
|
|
// Calculate the raw file offset of the file sector
|
|
RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset);
|
|
|
|
// Read the file sector
|
|
if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// If necessary, re-encrypt the sector
|
|
// Note: Recompression is not necessary here. Unlike encryption,
|
|
// the compression does not depend on the position of the file in MPQ.
|
|
if((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) && dwFileKey1 != dwFileKey2)
|
|
{
|
|
BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
|
|
DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey1 + dwSector);
|
|
EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey2 + dwSector);
|
|
BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
|
|
}
|
|
|
|
// Now write the sector back to the file
|
|
if(!FileStream_Write(pNewStream, NULL, hf->pbFileSector, dwRawDataInSector))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Update compact progress
|
|
if(ha->pfnCompactCB != NULL)
|
|
{
|
|
ha->CompactBytesProcessed += dwRawDataInSector;
|
|
ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
|
|
}
|
|
|
|
// Adjust byte counts
|
|
dwBytesToCopy -= dwRawDataInSector;
|
|
dwCmpSize += dwRawDataInSector;
|
|
}
|
|
}
|
|
|
|
// Copy the sector CRCs, if any
|
|
// Sector CRCs are always compressed (not imploded) and unencrypted
|
|
if(dwErrCode == ERROR_SUCCESS && hf->SectorOffsets != NULL && hf->SectorChksums != NULL)
|
|
{
|
|
DWORD dwCrcLength;
|
|
|
|
dwCrcLength = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
|
|
if(dwCrcLength != 0)
|
|
{
|
|
if(!FileStream_Read(ha->pStream, NULL, hf->SectorChksums, dwCrcLength))
|
|
dwErrCode = GetLastError();
|
|
|
|
if(!FileStream_Write(pNewStream, NULL, hf->SectorChksums, dwCrcLength))
|
|
dwErrCode = GetLastError();
|
|
|
|
// Update compact progress
|
|
if(ha->pfnCompactCB != NULL)
|
|
{
|
|
ha->CompactBytesProcessed += dwCrcLength;
|
|
ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
|
|
}
|
|
|
|
// Size of the CRC block is also included in the compressed file size
|
|
dwBytesToCopy -= dwCrcLength;
|
|
dwCmpSize += dwCrcLength;
|
|
}
|
|
}
|
|
|
|
// There might be extra data beyond sector checksum table
|
|
// Sometimes, these data are even part of sector offset table
|
|
// Examples:
|
|
// 2012 - WoW\15354\locale-enGB.MPQ:DBFilesClient\SpellLevels.dbc
|
|
// 2012 - WoW\15354\locale-enGB.MPQ:Interface\AddOns\Blizzard_AuctionUI\Blizzard_AuctionUI.xml
|
|
if(dwErrCode == ERROR_SUCCESS && dwBytesToCopy != 0)
|
|
{
|
|
LPBYTE pbExtraData;
|
|
|
|
// Allocate space for the extra data
|
|
pbExtraData = STORM_ALLOC(BYTE, dwBytesToCopy);
|
|
if(pbExtraData != NULL)
|
|
{
|
|
if(!FileStream_Read(ha->pStream, NULL, pbExtraData, dwBytesToCopy))
|
|
dwErrCode = GetLastError();
|
|
|
|
if(!FileStream_Write(pNewStream, NULL, pbExtraData, dwBytesToCopy))
|
|
dwErrCode = GetLastError();
|
|
|
|
// Include these extra data in the compressed size
|
|
dwCmpSize += dwBytesToCopy;
|
|
STORM_FREE(pbExtraData);
|
|
}
|
|
else
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
// Write the MD5's of the raw file data, if needed
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0)
|
|
{
|
|
dwErrCode = WriteMpqDataMD5(pNewStream,
|
|
ha->MpqPos + MpqFilePos,
|
|
pFileEntry->dwCmpSize,
|
|
ha->pHeader->dwRawChunkSize);
|
|
}
|
|
|
|
// Verify the number of bytes written
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// At this point, number of bytes written should be exactly
|
|
// the same like the compressed file size. If it isn't,
|
|
// there's something wrong (an unknown archive version, MPQ malformation, ...)
|
|
//
|
|
// Note: Diablo savegames have very weird layout, and the file "hero"
|
|
// seems to have improper compressed size. Instead of real compressed size,
|
|
// the "dwCmpSize" member of the block table entry contains
|
|
// uncompressed size of file data + size of the sector table.
|
|
// If we compact the archive, Diablo will refuse to load the game
|
|
//
|
|
// Note: Some patch files in WOW patches don't count the patch header
|
|
// into compressed size
|
|
//
|
|
|
|
if(!(dwCmpSize <= pFileEntry->dwCmpSize && pFileEntry->dwCmpSize <= dwCmpSize + dwPatchSize))
|
|
{
|
|
dwErrCode = ERROR_FILE_CORRUPT;
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
static DWORD CopyMpqFiles(TMPQArchive * ha, LPDWORD pFileKeys, TFileStream * pNewStream)
|
|
{
|
|
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
|
|
TFileEntry * pFileEntry;
|
|
TMPQFile * hf = NULL;
|
|
ULONGLONG MpqFilePos;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Walk through all files and write them to the destination MPQ archive
|
|
for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
|
|
{
|
|
// Copy all the file sectors
|
|
// Only do that when the file has nonzero size
|
|
if((pFileEntry->dwFlags & MPQ_FILE_EXISTS))
|
|
{
|
|
// Query the position where the destination file will be
|
|
FileStream_GetPos(pNewStream, &MpqFilePos);
|
|
MpqFilePos = MpqFilePos - ha->MpqPos;
|
|
|
|
// Perform file copy ONLY if the file has nonzero size
|
|
if(pFileEntry->dwFileSize != 0)
|
|
{
|
|
// Allocate structure for the MPQ file
|
|
hf = CreateFileHandle(ha, pFileEntry);
|
|
if(hf == NULL)
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// Set the file decryption key
|
|
hf->dwFileKey = pFileKeys[pFileEntry - ha->pFileTable];
|
|
|
|
// If the file is a patch file, load the patch header
|
|
if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
|
|
{
|
|
dwErrCode = AllocatePatchInfo(hf, true);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
// Allocate buffers for file sector and sector offset table
|
|
dwErrCode = AllocateSectorBuffer(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
|
|
// Also allocate sector offset table and sector checksum table
|
|
dwErrCode = AllocateSectorOffsets(hf, true);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
|
|
// Also load sector checksums, if any
|
|
if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
|
|
{
|
|
dwErrCode = AllocateSectorChecksums(hf, false);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
// Copy all file sectors
|
|
dwErrCode = CopyMpqFileSectors(ha, hf, pNewStream, MpqFilePos);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
break;
|
|
|
|
// Free buffers. This also sets "hf" to NULL.
|
|
FreeFileHandle(hf);
|
|
}
|
|
|
|
// Note: DO NOT update the compressed size in the file entry, no matter how bad it is.
|
|
pFileEntry->ByteOffset = MpqFilePos;
|
|
}
|
|
}
|
|
|
|
// Cleanup and exit
|
|
if(hf != NULL)
|
|
FreeFileHandle(hf);
|
|
return dwErrCode;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Public functions */
|
|
/*****************************************************************************/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Changing hash table size
|
|
|
|
DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq)
|
|
{
|
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
|
|
|
return ha->dwMaxFileCount;
|
|
}
|
|
|
|
bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
|
|
{
|
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
|
DWORD dwNewHashTableSize = 0;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Test the valid parameters
|
|
if(!IsValidMpqHandle(hMpq))
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
|
|
dwErrCode = ERROR_ACCESS_DENIED;
|
|
if(dwMaxFileCount < ha->dwFileTableSize)
|
|
dwErrCode = ERROR_DISK_FULL;
|
|
|
|
// ALL file names must be known in order to be able to rebuild hash table
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHashTable != NULL)
|
|
{
|
|
dwErrCode = CheckIfAllFilesKnown(ha);
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Calculate the hash table size for the new file limit
|
|
dwNewHashTableSize = GetNearestPowerOfTwo(dwMaxFileCount);
|
|
|
|
// Rebuild both file tables
|
|
dwErrCode = RebuildFileTable(ha, dwNewHashTableSize);
|
|
}
|
|
}
|
|
|
|
// We always have to rebuild the (attributes) file due to file table change
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Invalidate (listfile) and (attributes)
|
|
InvalidateInternalFiles(ha);
|
|
|
|
// Rebuild the HET table, if we have any
|
|
if(ha->pHetTable != NULL)
|
|
dwErrCode = RebuildHetTable(ha);
|
|
}
|
|
|
|
// Return the error
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Archive compacting
|
|
|
|
bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK pfnCompactCB, void * pvUserData)
|
|
{
|
|
TMPQArchive * ha = (TMPQArchive *) hMpq;
|
|
|
|
if (!IsValidMpqHandle(hMpq))
|
|
{
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
ha->pfnCompactCB = pfnCompactCB;
|
|
ha->pvCompactUserData = pvUserData;
|
|
return true;
|
|
}
|
|
|
|
bool WINAPI SFileCompactArchive(HANDLE hMpq, const TCHAR * szListFile, bool /* bReserved */)
|
|
{
|
|
TFileStream * pTempStream = NULL;
|
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
|
ULONGLONG ByteOffset;
|
|
ULONGLONG ByteCount;
|
|
LPDWORD pFileKeys = NULL;
|
|
TCHAR szTempFile[MAX_PATH+1] = _T("");
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Test the valid parameters
|
|
if(!IsValidMpqHandle(hMpq))
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
|
|
dwErrCode = ERROR_ACCESS_DENIED;
|
|
|
|
// If the MPQ is changed at this moment, we have to flush the archive
|
|
if(dwErrCode == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_CHANGED))
|
|
{
|
|
SFileFlushArchive(hMpq);
|
|
}
|
|
|
|
// Create the table with file keys
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
if((pFileKeys = STORM_ALLOC(DWORD, ha->dwFileTableSize)) != NULL)
|
|
memset(pFileKeys, 0, sizeof(DWORD) * ha->dwFileTableSize);
|
|
else
|
|
dwErrCode = 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(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Initialize the progress variables for compact callback
|
|
FileStream_GetSize(ha->pStream, &(ha->CompactTotalBytes));
|
|
ha->CompactBytesProcessed = 0;
|
|
dwErrCode = CheckIfAllKeysKnown(ha, szListFile, pFileKeys);
|
|
}
|
|
|
|
// Get the temporary file name and create it
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Create temporary file name. Prevent buffer overflow
|
|
StringCopy(szTempFile, _countof(szTempFile), FileStream_GetFileName(ha->pStream));
|
|
StringCat(szTempFile, _countof(szTempFile), _T(".tmp"));
|
|
|
|
// Create temporary file
|
|
pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
|
|
if(pTempStream == NULL)
|
|
dwErrCode = GetLastError();
|
|
}
|
|
|
|
// Write the data before MPQ user data (if any)
|
|
if(dwErrCode == 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;
|
|
dwErrCode = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount);
|
|
}
|
|
|
|
// Write the MPQ user data (if any)
|
|
if(dwErrCode == 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);
|
|
dwErrCode = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount);
|
|
}
|
|
|
|
// Write the MPQ header
|
|
if(dwErrCode == 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))
|
|
dwErrCode = GetLastError();
|
|
|
|
// Update the progress
|
|
ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize;
|
|
}
|
|
|
|
// Now copy all files
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
dwErrCode = CopyMpqFiles(ha, pFileKeys, pTempStream);
|
|
|
|
// If succeeded, switch the streams
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
ha->dwFlags |= MPQ_FLAG_CHANGED;
|
|
if(FileStream_Replace(ha->pStream, pTempStream))
|
|
pTempStream = NULL;
|
|
else
|
|
dwErrCode = ERROR_CAN_NOT_COMPLETE;
|
|
}
|
|
|
|
// Final user notification
|
|
if(dwErrCode == 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(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|