int EXPORT_SYMBOL SFileSetMaxFileCount(void * hMpq, uint32_t dwMaxFileCount)
{
    TMPQArchive * ha = (TMPQArchive *)hMpq;
    uint32_t dwNewHashTableSize = 0;
    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(dwMaxFileCount < ha->dwFileTableSize)
        nError = ERROR_DISK_FULL;

    /* ALL file names must be known in order to be able to rebuild hash table */
    if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
    {
        nError = CheckIfAllFilesKnown(ha);
        if(nError == ERROR_SUCCESS)
        {
            /* Calculate the hash table size for the new file limit */
            dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);

            /* Rebuild both file tables */
            nError = RebuildFileTable(ha, dwNewHashTableSize);
        }
    }

    /* We always have to rebuild the (attributes) file due to file table change */
    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);
    }

    /* Return the error */
    if(nError != ERROR_SUCCESS)
        SetLastError(nError);
    return (nError == ERROR_SUCCESS);
}
Ejemplo n.º 2
0
bool WINAPI SFileSetHashTableSize(HANDLE hMpq, DWORD dwNewTableSize)
{
    TMPQArchive * ha = (TMPQArchive *)hMpq;
    TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
    TFileEntry * pFileEntry;
    TMPQHash * pOldHashTable = NULL;
    TMPQHash * pNewHashTable = NULL;
    TMPQHash * pTableToFree = NULL;
    TMPQHash * pHash = NULL;
    DWORD dwOldHashTableSize = 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;
    if (ha->pHetTable != NULL || ha->pHetTable != NULL)
        nError = ERROR_ACCESS_DENIED;

    // New hash table size must be a power of two
    if (dwNewTableSize & (dwNewTableSize - 1))
        nError = ERROR_INVALID_PARAMETER;

    // 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);

    // Allocate buffer fo new hash table
    if (nError == ERROR_SUCCESS)
    {
        dwOldHashTableSize = ha->pHeader->dwHashTableSize;
        pOldHashTable = ha->pHashTable;

        pNewHashTable = ALLOCMEM(TMPQHash, dwNewTableSize);
        if (pOldHashTable == NULL || pNewHashTable == NULL)
            nError = ERROR_NOT_ENOUGH_MEMORY;
    }

    // Now build the new hash table
    // we have to reallocate block table, and also all tables in the (attributes)
    if (nError == ERROR_SUCCESS)
    {
        // Set new hash table
        memset(pNewHashTable, 0xFF, dwNewTableSize * sizeof(TMPQHash));
        ha->pHashTable = pNewHashTable;
        ha->pHeader->dwHashTableSize = dwNewTableSize;

        // Make new hash table entry for each file
        for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
        {
            if (pFileEntry->dwFlags & MPQ_FILE_EXISTS)
            {
                // The file name must be known
                assert(pFileEntry->szFileName != NULL);

                // Create new hashtable entry
                pHash = AllocateHashEntry(ha, pFileEntry->szFileName, pFileEntry->lcLocale);
                if (pHash == NULL)
                {
                    nError = ERROR_CAN_NOT_COMPLETE;
                    break;
                }

                // Fill the hash table entry
                pHash->wPlatform = pFileEntry->wPlatform;
                pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);

                // Fix the hash table index
                pFileEntry->dwHashIndex = (DWORD)(pHash - pNewHashTable);
            }
        }

        // If something failed, we have to revert hash table
        if (nError == ERROR_SUCCESS)
        {
            pTableToFree = pOldHashTable;
            ha->dwFlags |= MPQ_FLAG_CHANGED;
        }
        else
        {
            ha->pHeader->dwHashTableSize = dwOldHashTableSize;
            ha->pHashTable = pOldHashTable;
            pTableToFree = pNewHashTable;
        }
    }

    // Free buffers
    if (pTableToFree != NULL)
        FREEMEM(pTableToFree);

    // Return the result
    if (nError != ERROR_SUCCESS)
        SetLastError(nError);
    return (nError == ERROR_SUCCESS);
}
Ejemplo n.º 3
0
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);
}
Ejemplo n.º 4
0
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);
}
Ejemplo n.º 5
0
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);
}
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);
}