// Copies all file blocks into another archive.
static int CopyMpqFileBlocks(
    HANDLE hFile,
    TMPQArchive * ha,
    TMPQBlockEx * pBlockEx,
    TMPQBlock * pBlock,
    DWORD dwSeed)
{
    LARGE_INTEGER FilePos = {0};
    DWORD * pdwBlockPos2 = NULL;        // File block positions to be written to target file
    DWORD * pdwBlockPos = NULL;         // File block positions (unencrypted)
    BYTE  * pbBlock = NULL;             // Buffer for the file block
    DWORD dwTransferred;                // Number of bytes transferred
    DWORD dwCSize = 0;                  // Compressed file size
    DWORD dwBytes = 0;                  // Number of bytes
    DWORD dwSeed1 = 0;                  // File seed used for decryption
    DWORD dwSeed2 = 0;                  // File seed used for encryption
    DWORD nBlocks = 0;                  // Number of file blocks
    DWORD nBlock = 0;                   // Currently processed file block
    int nError = ERROR_SUCCESS;

    // When file length is zero, do nothing
    if(pBlock->dwFSize == 0)
        return ERROR_SUCCESS;

    // Calculate number of blocks in the file
    if(nError == ERROR_SUCCESS)
    {
        nBlocks = pBlock->dwFSize / ha->dwBlockSize;
        if(pBlock->dwFSize % ha->dwBlockSize)
            nBlocks++;
        pbBlock = ALLOCMEM(BYTE, ha->dwBlockSize);
        if(pbBlock == NULL)
            nError = ERROR_NOT_ENOUGH_MEMORY;
    }

    // Set the position to the begin of the file within archive
    if(nError == ERROR_SUCCESS)
    {
        FilePos.HighPart = pBlockEx->wFilePosHigh;
        FilePos.LowPart = pBlock->dwFilePos;
        FilePos.QuadPart += ha->MpqPos.QuadPart;
        if(SetFilePointer(ha->hFile, FilePos.LowPart, &FilePos.HighPart, FILE_BEGIN) != FilePos.LowPart)
            nError = GetLastError();
    }

    // Remember the position in the destination file
    if(nError == ERROR_SUCCESS)
    {
        FilePos.HighPart = 0;
        FilePos.LowPart = SetFilePointer(hFile, 0, &FilePos.HighPart, FILE_CURRENT);
    }

    // Resolve decryption seeds. The 'dwSeed' parameter is the decryption
    // seed for the file.
    if(nError == ERROR_SUCCESS && (pBlock->dwFlags & MPQ_FILE_ENCRYPTED))
    {
        dwSeed1 = dwSeed;
        if(pBlock->dwFlags & MPQ_FILE_FIXSEED)
            dwSeed = (dwSeed1 ^ pBlock->dwFSize) - pBlock->dwFilePos;

        dwSeed2 = dwSeed;
        if(pBlock->dwFlags & MPQ_FILE_FIXSEED)
            dwSeed2 = (dwSeed + (DWORD)(FilePos.QuadPart - ha->MpqPos.QuadPart)) ^ pBlock->dwFSize;
    }

    // Load the file positions from the archive and save it to the target file
    // (only if the file is compressed)
    if(pBlock->dwFlags & MPQ_FILE_COMPRESSED)
    {
        // Allocate buffers
        if(nError == ERROR_SUCCESS)
        {
            pdwBlockPos = ALLOCMEM(DWORD, nBlocks + 2);
            pdwBlockPos2 = ALLOCMEM(DWORD, nBlocks + 2);

            if(pdwBlockPos == NULL || pdwBlockPos2 == NULL)
                nError = ERROR_NOT_ENOUGH_MEMORY;
        }

        // Load the block positions
        if(nError == ERROR_SUCCESS)
        {
            dwBytes = (nBlocks + 1) * sizeof(DWORD);
            if(pBlock->dwFlags & MPQ_FILE_HAS_EXTRA)
                dwBytes += sizeof(DWORD);

            ReadFile(ha->hFile, pdwBlockPos, dwBytes, &dwTransferred, NULL);
            if(dwTransferred != dwBytes)
                nError = ERROR_FILE_CORRUPT;
        }

        // Re-encrypt the block table positions
        if(nError == ERROR_SUCCESS)
        {
            BSWAP_ARRAY32_UNSIGNED(pdwBlockPos, dwBytes / sizeof(DWORD));
            if(pBlock->dwFlags & MPQ_FILE_ENCRYPTED)
            {
                DecryptMPQBlock(pdwBlockPos, dwBytes, dwSeed1 - 1);
                if(pdwBlockPos[0] != dwBytes)
                    nError = ERROR_FILE_CORRUPT;
            
                memcpy(pdwBlockPos2, pdwBlockPos, dwBytes);
                EncryptMPQBlock(pdwBlockPos2, dwBytes, dwSeed2 - 1);
            }
            else
            {
                memcpy(pdwBlockPos2, pdwBlockPos, dwBytes);
            }
            BSWAP_ARRAY32_UNSIGNED(pdwBlockPos2, dwBytes / sizeof(DWORD));
        }

        // Write to the target file
        if(nError == ERROR_SUCCESS)
        {
            WriteFile(hFile, pdwBlockPos2, dwBytes, &dwTransferred, NULL);
            dwCSize += dwTransferred;
            if(dwTransferred != dwBytes)
                nError = ERROR_DISK_FULL;
        }
    }

    // Now we have to copy all file blocks. We will do it without
    // recompression, because re-compression is not necessary in this case
    if(nError == ERROR_SUCCESS)
    {
        for(nBlock = 0; nBlock < nBlocks; nBlock++)
        {
            // Fix: The last block must not be exactly the size of one block.
            dwBytes = ha->dwBlockSize;
            if(nBlock == nBlocks - 1)
            {
                dwBytes = pBlock->dwFSize - (ha->dwBlockSize * (nBlocks - 1));
            }

            if(pBlock->dwFlags & MPQ_FILE_COMPRESSED)
                dwBytes = pdwBlockPos[nBlock+1] - pdwBlockPos[nBlock];

            // Read the file block
            ReadFile(ha->hFile, pbBlock, dwBytes, &dwTransferred, NULL);
            if(dwTransferred != dwBytes)
            {
                nError = ERROR_FILE_CORRUPT;
                break;
            }

            // If necessary, re-encrypt the block
            // Note: Recompression is not necessary here. Unlike encryption, 
            // the compression does not depend on the position of the file in MPQ.
            if((pBlock->dwFlags & MPQ_FILE_ENCRYPTED) && dwSeed1 != dwSeed2)
            {
                BSWAP_ARRAY32_UNSIGNED((DWORD *)pbBlock, dwBytes/sizeof(DWORD));
                DecryptMPQBlock((DWORD *)pbBlock, dwBytes, dwSeed1 + nBlock);
                EncryptMPQBlock((DWORD *)pbBlock, dwBytes, dwSeed2 + nBlock);
                BSWAP_ARRAY32_UNSIGNED((DWORD *)pbBlock, dwBytes/sizeof(DWORD));
            }

            // Now write the block back to the file
            WriteFile(hFile, pbBlock, dwBytes, &dwTransferred, NULL);
            dwCSize += dwTransferred;
            if(dwTransferred != dwBytes)
            {
                nError = ERROR_DISK_FULL;
                break;
            }
        }
    }

    // Copy the file extras, if any
    // These extras does not seem to be encrypted, and their purpose is unknown
    if(nError == ERROR_SUCCESS && (pBlock->dwFlags & MPQ_FILE_HAS_EXTRA))
    {
        dwBytes = pdwBlockPos[nBlocks + 1] - pdwBlockPos[nBlocks];
        if(dwBytes != 0)
        {
            ReadFile(ha->hFile, pbBlock, dwBytes, &dwTransferred, NULL);
            if(dwTransferred == dwBytes)
            {
                WriteFile(hFile, pbBlock, dwBytes, &dwTransferred, NULL);
                dwCSize += dwTransferred;
                if(dwTransferred != dwBytes)
                    nError = ERROR_DISK_FULL;
            }
            else
            {
                nError = ERROR_FILE_CORRUPT;
            }
        }
    }

    // Update file position in the block table
    if(nError == 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
        // (maybe new archive version ?)
        assert(dwCSize == pBlock->dwCSize);

        // Update file pos in the block table
        FilePos.QuadPart -= ha->MpqPos.QuadPart;
        pBlockEx->wFilePosHigh = (USHORT)FilePos.HighPart;
        pBlock->dwFilePos = FilePos.LowPart;
    }

    // Cleanup and return
    if(pdwBlockPos2 != NULL)
        FREEMEM(pdwBlockPos2);
    if(pdwBlockPos != NULL)
        FREEMEM(pdwBlockPos);
    if(pbBlock != NULL)
        FREEMEM(pbBlock);
    return nError;
}
Example #2
0
static int RecryptFileData(
    TMPQArchive * ha,
    DWORD dwSaveBlockIndex,
    const char * szFileName,
    const char * szNewFileName)
{
    LARGE_INTEGER BlockFilePos;
    LARGE_INTEGER MpqFilePos;
    TMPQBlockEx * pBlockEx = ha->pExtBlockTable + dwSaveBlockIndex;
    TMPQBlock * pBlock = ha->pBlockTable + dwSaveBlockIndex;
    const char * szPlainName;
    LPDWORD pdwBlockPos1 = NULL;
    LPDWORD pdwBlockPos2 = NULL;
    LPBYTE pbFileBlock = NULL;
    DWORD dwTransferred;
    DWORD dwOldSeed;
    DWORD dwNewSeed;
    DWORD dwToRead;
    int nBlocks;
    int nError = ERROR_SUCCESS;

    // The file must be encrypted
    assert(pBlock->dwFlags & MPQ_FILE_ENCRYPTED);

    // File decryption seed is calculated from the plain name
    szPlainName = strrchr(szFileName, '\\');
    if(szPlainName != NULL)
        szFileName = szPlainName + 1;
    szPlainName = strrchr(szNewFileName, '\\');
    if(szPlainName != NULL)
        szNewFileName = szPlainName + 1;

    // Calculate both file seeds
    dwOldSeed = DecryptFileSeed(szFileName);
    dwNewSeed = DecryptFileSeed(szNewFileName);
    if(pBlock->dwFlags & MPQ_FILE_FIXSEED)
    {
        dwOldSeed = (dwOldSeed + pBlock->dwFilePos) ^ pBlock->dwFSize;
        dwNewSeed = (dwNewSeed + pBlock->dwFilePos) ^ pBlock->dwFSize;
    }

    // Incase the seeds are equal, don't recrypt the file
    if(dwNewSeed == dwOldSeed)
        return ERROR_SUCCESS;

    // Calculate the file position of the archived file
    MpqFilePos.LowPart = pBlock->dwFilePos;
    MpqFilePos.HighPart = pBlockEx->wFilePosHigh;
    MpqFilePos.QuadPart += ha->MpqPos.QuadPart;

    // Calculate the number of file blocks
    nBlocks = pBlock->dwFSize / ha->dwBlockSize;
    if(pBlock->dwFSize % ha->dwBlockSize)
        nBlocks++;

    // If the file is stored as single unit, we recrypt one block only
    if(pBlock->dwFlags & MPQ_FILE_SINGLE_UNIT)
    {
        // Allocate the block
        pbFileBlock = ALLOCMEM(BYTE, pBlock->dwCSize);
        if(pbFileBlock == NULL)
            return ERROR_NOT_ENOUGH_MEMORY;

        SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN);
        ReadFile(ha->hFile, pbFileBlock, pBlock->dwCSize, &dwTransferred, NULL);
        if(dwTransferred == pBlock->dwCSize)
            nError = ERROR_FILE_CORRUPT;

        if(nError == ERROR_SUCCESS)
        {
            // Recrypt the block
            DecryptMPQBlock((DWORD *)pbFileBlock, pBlock->dwCSize, dwOldSeed);
            EncryptMPQBlock((DWORD *)pbFileBlock, pBlock->dwCSize, dwNewSeed);

            // Write it back
            SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN);
            WriteFile(ha->hFile, pbFileBlock, pBlock->dwCSize, &dwTransferred, NULL);
            if(dwTransferred != pBlock->dwCSize)
                nError = ERROR_WRITE_FAULT;
        }
        FREEMEM(pbFileBlock);
        return nError;
    }

    // If the file is compressed, we have to re-crypt block table first,
    // then all file blocks
    if(pBlock->dwFlags & MPQ_FILE_COMPRESSED)
    {
        // Allocate buffer for both blocks
        pdwBlockPos1 = ALLOCMEM(DWORD, nBlocks + 2);
        pdwBlockPos2 = ALLOCMEM(DWORD, nBlocks + 2);
        if(pdwBlockPos1 == NULL || pdwBlockPos2 == NULL)
            return ERROR_NOT_ENOUGH_MEMORY;

        // Calculate number of bytes to be read
        dwToRead = (nBlocks + 1) * sizeof(DWORD);
        if(pBlock->dwFlags & MPQ_FILE_HAS_EXTRA)
            dwToRead += sizeof(DWORD);

        // Read the block positions
        SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN);
        ReadFile(ha->hFile, pdwBlockPos1, dwToRead, &dwTransferred, NULL);
        if(dwTransferred != dwToRead)
            nError = ERROR_FILE_CORRUPT;

        // Recrypt the block table
        if(nError == ERROR_SUCCESS)
        {
            BSWAP_ARRAY32_UNSIGNED(pdwBlockPos1, dwToRead / sizeof(DWORD));
            DecryptMPQBlock(pdwBlockPos1, dwToRead, dwOldSeed - 1);
            if(pdwBlockPos1[0] != dwToRead)
                nError = ERROR_FILE_CORRUPT;

            memcpy(pdwBlockPos2, pdwBlockPos1, dwToRead);
            EncryptMPQBlock(pdwBlockPos2, dwToRead, dwNewSeed - 1);
            BSWAP_ARRAY32_UNSIGNED(pdwBlockPos2, dwToRead / sizeof(DWORD));
        }

        // Write the recrypted block table back
        if(nError == ERROR_SUCCESS)
        {
            SetFilePointer(ha->hFile, MpqFilePos.LowPart, &MpqFilePos.HighPart, FILE_BEGIN);
            WriteFile(ha->hFile, pdwBlockPos2, dwToRead, &dwTransferred, NULL);
            if(dwTransferred != dwToRead)
                nError = ERROR_WRITE_FAULT;
        }
    }

    // Allocate the transfer buffer
    if(nError == ERROR_SUCCESS)
    {
        pbFileBlock = ALLOCMEM(BYTE, ha->dwBlockSize);
        if(pbFileBlock == NULL)
            nError = ERROR_NOT_ENOUGH_MEMORY;
    }

    // Now we have to recrypt all file blocks
    if(nError == ERROR_SUCCESS)
    {
        for(int nBlock = 0; nBlock < nBlocks; nBlock++)
        {
            // Calc position and length for uncompressed file
            BlockFilePos.QuadPart = MpqFilePos.QuadPart + (ha->dwBlockSize * nBlock);
            dwToRead = ha->dwBlockSize;
            if(nBlock == nBlocks - 1)
                dwToRead = pBlock->dwFSize - (ha->dwBlockSize * (nBlocks - 1));

            // Fix position and length for compressed file
            if(pBlock->dwFlags & MPQ_FILE_COMPRESS)
            {
                BlockFilePos.QuadPart = MpqFilePos.QuadPart + pdwBlockPos1[nBlock];
                dwToRead = pdwBlockPos1[nBlock+1] - pdwBlockPos1[nBlock];
            }

            // Read the file block
            SetFilePointer(ha->hFile, BlockFilePos.LowPart, &BlockFilePos.HighPart, FILE_BEGIN);
            ReadFile(ha->hFile, pbFileBlock, dwToRead, &dwTransferred, NULL);
            if(dwTransferred != dwToRead)
                nError = ERROR_FILE_CORRUPT;

            // Recrypt the file block
            BSWAP_ARRAY32_UNSIGNED((DWORD *)pbFileBlock, dwToRead/sizeof(DWORD));
            DecryptMPQBlock((DWORD *)pbFileBlock, dwToRead, dwOldSeed + nBlock);
            EncryptMPQBlock((DWORD *)pbFileBlock, dwToRead, dwNewSeed + nBlock);
            BSWAP_ARRAY32_UNSIGNED((DWORD *)pbFileBlock, dwToRead/sizeof(DWORD));

            // Write the block back
            SetFilePointer(ha->hFile, BlockFilePos.LowPart, &BlockFilePos.HighPart, FILE_BEGIN);
            WriteFile(ha->hFile, pbFileBlock, dwToRead, &dwTransferred, NULL);
            if(dwTransferred != dwToRead)
                nError = ERROR_WRITE_FAULT;
        }
    }

    // Free buffers and exit
    if(pbFileBlock != NULL)
        FREEMEM(pbFileBlock);
    if(pdwBlockPos2 != NULL)
        FREEMEM(pdwBlockPos2);
    if(pdwBlockPos1 != NULL)
        FREEMEM(pdwBlockPos1);
    return nError;
}
Example #3
0
// TODO: Test for archives > 4GB
static DWORD WINAPI ReadMPQBlocks(TMPQFile * hf, DWORD dwBlockPos, BYTE * buffer, DWORD blockBytes)
{
    LARGE_INTEGER FilePos;
    TMPQArchive * ha = hf->ha;          // Archive handle
    BYTE  * tempBuffer = NULL;          // Buffer for reading compressed data from the file
    DWORD   dwFilePos = dwBlockPos;     // Reading position from the file
    DWORD   dwToRead;                   // Number of bytes to read
    DWORD   blockNum;                   // Block number (needed for decrypt)
    DWORD   dwBytesRead = 0;            // Total number of bytes read
    DWORD   bytesRemain = 0;            // Number of data bytes remaining up to the end of the file
    DWORD   nBlocks;                    // Number of blocks to load
    DWORD   i;

    // Test parameters. Block position and block size must be block-aligned, block size nonzero
    if((dwBlockPos & (ha->dwBlockSize - 1)) || blockBytes == 0)
        return 0;

    // Check the end of file
    if((dwBlockPos + blockBytes) > hf->pBlock->dwFSize)
        blockBytes = hf->pBlock->dwFSize - dwBlockPos;

    bytesRemain = hf->pBlock->dwFSize - dwBlockPos;
    blockNum    = dwBlockPos / ha->dwBlockSize;
    nBlocks     = blockBytes / ha->dwBlockSize;
    if(blockBytes % ha->dwBlockSize)
        nBlocks++;

    // If file has variable block positions, we have to load them
    if((hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) && hf->bBlockPosLoaded == FALSE)
    {
        // Move file pointer to the begin of the file in the MPQ
        if(hf->MpqFilePos.QuadPart != ha->FilePointer.QuadPart)
        {
            SetFilePointer(ha->hFile, hf->MpqFilePos.LowPart, &hf->MpqFilePos.HighPart, FILE_BEGIN);
        }

        // Read block positions from begin of file.
        dwToRead = (hf->nBlocks+1) * sizeof(DWORD);
        if(hf->pBlock->dwFlags & MPQ_FILE_HAS_EXTRA)
            dwToRead += sizeof(DWORD);

        // Read the block pos table and convert the buffer to little endian
        ReadFile(ha->hFile, hf->pdwBlockPos, dwToRead, &dwBytesRead, NULL);
        BSWAP_ARRAY32_UNSIGNED(hf->pdwBlockPos, (hf->nBlocks+1));

        //
        // If the archive if protected some way, perform additional check
        // Sometimes, the file appears not to be encrypted, but it is.
        //
        // Note: In WoW 1.10+, there's a new flag. With this flag present,
        // there's one additional entry in the block table.
        //

        if(hf->pdwBlockPos[0] != dwBytesRead)
            hf->pBlock->dwFlags |= MPQ_FILE_ENCRYPTED;

        // Decrypt loaded block positions if necessary
        if(hf->pBlock->dwFlags & MPQ_FILE_ENCRYPTED)
        {
            // If we don't know the file seed, try to find it.
            if(hf->dwSeed1 == 0)
                hf->dwSeed1 = DetectFileSeed(hf->pdwBlockPos, dwBytesRead);

            // If we don't know the file seed, sorry but we cannot extract the file.
            if(hf->dwSeed1 == 0)
                return 0;

            // Decrypt block positions
            DecryptMPQBlock(hf->pdwBlockPos, dwBytesRead, hf->dwSeed1 - 1);

            // Check if the block positions are correctly decrypted
            // I don't know why, but sometimes it will result invalid block positions on some files
            if(hf->pdwBlockPos[0] != dwBytesRead)
            {
                // Try once again to detect file seed and decrypt the blocks
                // TODO: Test with >4GB
                SetFilePointer(ha->hFile, hf->MpqFilePos.LowPart, &hf->MpqFilePos.HighPart, FILE_BEGIN);
                ReadFile(ha->hFile, hf->pdwBlockPos, dwToRead, &dwBytesRead, NULL);

                BSWAP_ARRAY32_UNSIGNED(hf->pdwBlockPos, (hf->nBlocks+1));
                hf->dwSeed1 = DetectFileSeed(hf->pdwBlockPos, dwBytesRead);
                DecryptMPQBlock(hf->pdwBlockPos, dwBytesRead, hf->dwSeed1 - 1);

                // Check if the block positions are correctly decrypted
                if(hf->pdwBlockPos[0] != dwBytesRead)
                    return 0;
            }
        }

        // Update hf's variables
        ha->FilePointer.QuadPart = hf->MpqFilePos.QuadPart + dwBytesRead;
        hf->bBlockPosLoaded = TRUE;
    }

    // Get file position and number of bytes to read
    dwFilePos = dwBlockPos;
    dwToRead  = blockBytes;
    if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED)
    {
        dwFilePos = hf->pdwBlockPos[blockNum];
        dwToRead  = hf->pdwBlockPos[blockNum + nBlocks] - dwFilePos;
    }
    FilePos.QuadPart = hf->MpqFilePos.QuadPart + dwFilePos;

    // Get work buffer for store read data
    tempBuffer = buffer;
    if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED)
    {
        if((tempBuffer = ALLOCMEM(BYTE, dwToRead)) == NULL)
        {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return 0;
        }
    }

    // Set file pointer, if necessary
    if(ha->FilePointer.QuadPart != FilePos.QuadPart)
    {
        SetFilePointer(ha->hFile, FilePos.LowPart, &FilePos.HighPart, FILE_BEGIN);
    }

    // 15018F87 : Read all requested blocks
    ReadFile(ha->hFile, tempBuffer, dwToRead, &dwBytesRead, NULL);
    ha->FilePointer.QuadPart = FilePos.QuadPart + dwBytesRead;

    // Block processing part.
    DWORD blockStart = 0;               // Index of block start in work buffer
    DWORD blockSize  = min(blockBytes, ha->dwBlockSize);
    DWORD index      = blockNum;        // Current block index

    dwBytesRead = 0;                      // Clear read byte counter

    // Walk through all blocks
    for(i = 0; i < nBlocks; i++, index++)
    {
        BYTE * inputBuffer = tempBuffer + blockStart;
        int    outLength = ha->dwBlockSize;

        if(bytesRemain < (DWORD)outLength)
            outLength = bytesRemain;

        // Get current block length
        if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED)
            blockSize = hf->pdwBlockPos[index+1] - hf->pdwBlockPos[index];

        // If block is encrypted, we have to decrypt it.
        if(hf->pBlock->dwFlags & MPQ_FILE_ENCRYPTED)
        {
            BSWAP_ARRAY32_UNSIGNED((DWORD *)inputBuffer, blockSize / sizeof(DWORD));

            // If we don't know the seed, try to decode it as WAVE file
            if(hf->dwSeed1 == 0)
                hf->dwSeed1 = DetectFileSeed2((DWORD *)inputBuffer, 3, ID_WAVE, hf->pBlock->dwFSize - 8, 0x45564157);

            // Let's try MSVC's standard EXE or header
            if(hf->dwSeed1 == 0)
                hf->dwSeed1 = DetectFileSeed2((DWORD *)inputBuffer, 2, 0x00905A4D, 0x00000003);

            if(hf->dwSeed1 == 0)
                return 0;

            DecryptMPQBlock((DWORD *)inputBuffer, blockSize, hf->dwSeed1 + index);
            BSWAP_ARRAY32_UNSIGNED((DWORD *)inputBuffer, blockSize / sizeof(DWORD));
        }

        // If the block is really compressed, decompress it.
        // WARNING : Some block may not be compressed, it can be determined only
        // by comparing uncompressed and compressed size !!!
        if(blockSize < (DWORD)outLength)
        {
            // Is the file compressed with PKWARE Data Compression Library ?
            if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESS_PKWARE)
                Decompress_pklib((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize);

            // Is it a file compressed by Blizzard's multiple compression ?
            // Note that Storm.dll v 1.0.9 distributed with Warcraft III
            // passes the full path name of the opened archive as the new last parameter
            if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESS_MULTI)
                SCompDecompress((char *)buffer, &outLength, (char *)inputBuffer, (int)blockSize);
            dwBytesRead += outLength;
            buffer    += outLength;
        }
        else
        {
            if(buffer != inputBuffer)
                memcpy(buffer, inputBuffer, blockSize);

            dwBytesRead += blockSize;
            buffer    += blockSize;
        }
        blockStart  += blockSize;
        bytesRemain -= outLength;
    }

    // Delete input buffer, if necessary
    if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED)
        FREEMEM(tempBuffer);

    return dwBytesRead;
}