Esempio n. 1
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;
}
// 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;
}