// When this function is called, it is already ensured that the parameters are valid // (e.g. the "dwToRead + dwFilePos" is not greater than the file size) // TODO: Test for archives > 4GB static DWORD WINAPI ReadMPQFileSingleUnit(TMPQFile * hf, DWORD dwFilePos, BYTE * pbBuffer, DWORD dwToRead) { TMPQArchive * ha = hf->ha; DWORD dwBytesRead = 0; if(ha->FilePointer.QuadPart != hf->MpqFilePos.QuadPart) { SetFilePointer(ha->hFile, hf->MpqFilePos.LowPart, &hf->MpqFilePos.HighPart, FILE_BEGIN); ha->FilePointer = hf->MpqFilePos; } // If the file is really compressed, decompress it. // Otherwise, read the data as-is to the caller. if(hf->pBlock->dwCSize < hf->pBlock->dwFSize) { if(hf->pbFileBuffer == NULL) { BYTE * inputBuffer = NULL; int outputBufferSize = (int)hf->pBlock->dwFSize; int inputBufferSize = (int)hf->pBlock->dwCSize; hf->pbFileBuffer = ALLOCMEM(BYTE, outputBufferSize); inputBuffer = ALLOCMEM(BYTE, inputBufferSize); if(inputBuffer != NULL && hf->pbFileBuffer != NULL) { // Read the compressed file data ReadFile(ha->hFile, inputBuffer, inputBufferSize, &dwBytesRead, NULL); // Is the file compressed with PKWARE Data Compression Library ? if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESS_PKWARE) Decompress_pklib((char *)hf->pbFileBuffer, &outputBufferSize, (char *)inputBuffer, (int)inputBufferSize); // 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 *)hf->pbFileBuffer, &outputBufferSize, (char *)inputBuffer, (int)inputBufferSize); } // Free the temporary buffer if(inputBuffer != NULL) FREEMEM(inputBuffer); } // Copy the file data, if any there if(hf->pbFileBuffer != NULL) { memcpy(pbBuffer, hf->pbFileBuffer + dwFilePos, dwToRead); dwBytesRead += dwToRead; } } else { // Read the uncompressed file data ReadFile(ha->hFile, pbBuffer, dwToRead, &dwBytesRead, NULL); dwBytesRead = (int)dwBytesRead; } return (DWORD)dwBytesRead; }
JNIH_EXCEPTION_TRAP_BEGIN() { JByteArray jInBuffer(env, inBuffer); JByteArray tempBuffer(env, outBufferLength); jint decompressedSize = outBufferLength; jboolean result = SCompDecompress( tempBuffer.getData(), reinterpret_cast<int*>(&decompressedSize), jInBuffer.getData(), inBufferLength ); if (!result) { ErrorCodeToException(env, GetLastError()); } else { JByteArray outBuffer(env, decompressedSize); memcpy(outBuffer.getData(), tempBuffer.getData(), decompressedSize); return outBuffer.getArray(); } } JNIH_EXCEPTION_TRAP_END
// hf - MPQ File handle. // pbBuffer - Pointer to target buffer to store sectors. // dwByteOffset - Position of sector in the file (relative to file begin) // dwBytesToRead - Number of bytes to read. Must be multiplier of sector size. // pdwBytesRead - Stored number of bytes loaded static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbRawSector = NULL; LPBYTE pbOutSector = pbBuffer; LPBYTE pbInSector = pbBuffer; DWORD dwRawBytesToRead; DWORD dwRawSectorOffset = dwByteOffset; DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize; DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize; DWORD dwSectorsDone = 0; DWORD dwBytesRead = 0; int nError = ERROR_SUCCESS; // Note that dwByteOffset must be aligned to size of one sector // Note that dwBytesToRead must be a multiplier of one sector size // This is local function, so we won't check if that's true. // Note that files stored in single units are processed by a separate function // If there is not enough bytes remaining, cut dwBytesToRead if((dwByteOffset + dwBytesToRead) > hf->dwDataSize) dwBytesToRead = hf->dwDataSize - dwByteOffset; dwRawBytesToRead = dwBytesToRead; // Perform all necessary work to do with compressed files if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) { // If the sector positions are not loaded yet, do it if(hf->SectorOffsets == NULL) { nError = AllocateSectorOffsets(hf, true); if(nError != ERROR_SUCCESS) return nError; } // If the sector checksums are not loaded yet, load them now. if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)) { nError = AllocateSectorChecksums(hf, true); if(nError != ERROR_SUCCESS) return nError; } // If the file is compressed, also allocate secondary buffer pbInSector = pbRawSector = ALLOCMEM(BYTE, dwBytesToRead); if(pbRawSector == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Assign the temporary buffer as target for read operation dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex]; dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset; } // Calculate raw file offset where the sector(s) are stored. CalculateRawSectorOffset(RawFilePos, hf, dwRawSectorOffset); // Set file pointer and read all required sectors if(!FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead)) return GetLastError(); dwBytesRead = 0; // Now we have to decrypt and decompress all file sectors that have been loaded for(DWORD i = 0; i < dwSectorsToRead; i++) { DWORD dwRawBytesInThisSector = ha->dwSectorSize; DWORD dwBytesInThisSector = ha->dwSectorSize; DWORD dwIndex = dwSectorIndex + i; // If there is not enough bytes in the last sector, // cut the number of bytes in this sector if(dwRawBytesInThisSector > dwBytesToRead) dwRawBytesInThisSector = dwBytesToRead; if(dwBytesInThisSector > dwBytesToRead) dwBytesInThisSector = dwBytesToRead; // If the file is compressed, we have to adjust the raw sector size if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex]; // If the file is encrypted, we have to decrypt the sector if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); // If we don't know the key, try to detect it by file content if(hf->dwFileKey == 0) { hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector); if(hf->dwFileKey == 0) { nError = ERROR_UNKNOWN_FILE_KEY; break; } } DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex); BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector); } // If the file has sector CRC check turned on, perform it if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL) { DWORD dwAdlerExpected = hf->SectorChksums[dwIndex]; DWORD dwAdlerValue = 0; // We can only check sector CRC when it's not zero // Neither can we check it if it's 0xFFFFFFFF. if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF) { dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector); if(dwAdlerValue != dwAdlerExpected) { nError = ERROR_CHECKSUM_ERROR; break; } } } // If the sector is really compressed, decompress it. // WARNING : Some sectors may not be compressed, it can be determined only // by comparing uncompressed and compressed size !!! if(dwRawBytesInThisSector < dwBytesInThisSector) { int cbOutSector = dwBytesInThisSector; int cbInSector = dwRawBytesInThisSector; int nResult = 0; // Is the file compressed by PKWARE Data Compression Library ? if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); // Is the file compressed by Blizzard's multiple compression ? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) nResult = SCompDecompress((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); // Did the decompression fail ? if(nResult == 0) { nError = ERROR_FILE_CORRUPT; break; } } else { if(pbOutSector != pbInSector) memcpy(pbOutSector, pbInSector, dwBytesInThisSector); } // Move pointers dwBytesToRead -= dwBytesInThisSector; dwByteOffset += dwBytesInThisSector; dwBytesRead += dwBytesInThisSector; pbOutSector += dwBytesInThisSector; pbInSector += dwRawBytesInThisSector; dwSectorsDone++; } // Free all used buffers if(pbRawSector != NULL) FREEMEM(pbRawSector); // Give the caller thenumber of bytes read *pdwBytesRead = dwBytesRead; return nError; }
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData = NULL; int nError; // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; pbRawData = hf->pbFileSector; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; // If the file buffer is not loaded yet, do it if(hf->dwSectorOffs != 0) { // // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: // // File CmpSize FileSize Data // -------------------------------------- ------- -------- --------------- // esES\DBFilesClient\LightSkyBox.dbc 0xBE 0xBC Is compressed // deDE\DBFilesClient\MountCapability.dbc 0x93 0x77 Is uncompressed // // Now tell me how to deal with this mess. Apparently // someone made a mistake at Blizzard ... // if(hf->pPatchInfo != NULL) { // Allocate space for pbCompressed = ALLOCMEM(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Read the entire file if(!FileStream_Read(ha->pStream, &RawFilePos, pbCompressed, pFileEntry->dwCmpSize)) { FREEMEM(pbCompressed); return GetLastError(); } // We assume that patch files are not encrypted assert((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) == 0); assert((pFileEntry->dwFlags & MPQ_FILE_IMPLODE) == 0); // Check the 'PTCH' signature to find out if it's compressed or not if(pbCompressed[0] != 'P' || pbCompressed[1] != 'T' || pbCompressed[2] != 'C' || pbCompressed[3] != 'H') { int cbOutBuffer = (int)hf->dwDataSize; int nResult = SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbCompressed, (int)pFileEntry->dwCmpSize); if(nResult == 0) { FREEMEM(pbCompressed); return ERROR_FILE_CORRUPT; } } else { memcpy(hf->pbFileSector, pbCompressed, hf->dwDataSize); } // Free the decompression buffer. FREEMEM(pbCompressed); } else { // If the file is compressed, we have to allocate buffer for compressed data if(pFileEntry->dwCmpSize < hf->dwDataSize) { pbCompressed = ALLOCMEM(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; pbRawData = pbCompressed; } // Read the entire file if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) { FREEMEM(pbCompressed); return GetLastError(); } // If the file is encrypted, we have to decrypt the data first if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); } // If the file is compressed, we have to decompress it now if(pFileEntry->dwCmpSize < hf->dwDataSize) { int cbOutBuffer = (int)hf->dwDataSize; int nResult = 0; // Note: Single unit files compressed with IMPLODE are not supported by Blizzard if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize); if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) nResult = SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize); // Free the decompression buffer. FREEMEM(pbCompressed); if(nResult == 0) return ERROR_FILE_CORRUPT; } } // The file sector is now properly loaded hf->dwSectorOffs = 0; } // At this moment, we have the file loaded into the file buffer. // Copy as much as the caller wants if(hf->dwSectorOffs == 0) { // File position is greater or equal to file size ? if(hf->dwFilePos >= hf->dwDataSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // If not enough bytes remaining in the file, cut them if((hf->dwDataSize - hf->dwFilePos) < dwToRead) dwToRead = (hf->dwDataSize - hf->dwFilePos); // Copy the bytes memcpy(pvBuffer, hf->pbFileSector + hf->dwFilePos, dwToRead); hf->dwFilePos += dwToRead; // Give the number of bytes read *pdwBytesRead = dwToRead; return ERROR_SUCCESS; } // An error, sorry return ERROR_CAN_NOT_COMPLETE; }
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData = NULL; bool bIsReallyCompressed = false; int nError = ERROR_SUCCESS; // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) return nError; pbRawData = hf->pbFileSector; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; // If the file sector is not loaded yet, do it if(hf->dwSectorOffs != 0) { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { // Allocate space for compressed data pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; bIsReallyCompressed = true; pbRawData = pbCompressed; } // Load the raw (compressed, encrypted) data if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) { STORM_FREE(pbCompressed); return GetLastError(); } // If the file is encrypted, we have to decrypt the data first if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); } // // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: // // File CmpSize FileSize Data // -------------------------------------- ------- -------- --------------- // esES\DBFilesClient\LightSkyBox.dbc 0xBE 0xBC Is compressed // deDE\DBFilesClient\MountCapability.dbc 0x93 0x77 Is uncompressed // // Now tell me how to deal with this mess. // if(hf->pPatchInfo != NULL) { if(pbRawData[0] == 'P' && pbRawData[1] == 'T' && pbRawData[2] == 'C' && pbRawData[3] == 'H') { assert(pFileEntry->dwCmpSize >= hf->dwDataSize); bIsReallyCompressed = false; } } else { if(pFileEntry->dwCmpSize >= hf->dwDataSize) bIsReallyCompressed = false; } // If the file is compressed, we have to decompress it now if(bIsReallyCompressed) { int cbOutBuffer = (int)hf->dwDataSize; // Note: Single unit files compressed with IMPLODE are not supported by Blizzard if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) { if(!SCompExplode((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) nError = ERROR_FILE_CORRUPT; } if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { if(!SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) nError = ERROR_FILE_CORRUPT; } } else { if(pbRawData != hf->pbFileSector) memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); } // Free the decompression buffer. if(pbCompressed != NULL) STORM_FREE(pbCompressed); // The file sector is now properly loaded hf->dwSectorOffs = 0; } // At this moment, we have the file loaded into the file buffer. // Copy as much as the caller wants if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) { // File position is greater or equal to file size ? if(dwFilePos >= hf->dwDataSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // If not enough bytes remaining in the file, cut them if((hf->dwDataSize - dwFilePos) < dwToRead) dwToRead = (hf->dwDataSize - dwFilePos); // Copy the bytes memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); // Give the number of bytes read *pdwBytesRead = dwToRead; return ERROR_SUCCESS; } // An error, sorry return ERROR_CAN_NOT_COMPLETE; }
static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) { ULONGLONG RawFilePos = hf->RawFilePos; TMPQArchive * ha = hf->ha; TFileEntry * pFileEntry = hf->pFileEntry; LPBYTE pbCompressed = NULL; LPBYTE pbRawData; int nError = ERROR_SUCCESS; // If the file buffer is not allocated yet, do it. if(hf->pbFileSector == NULL) { nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL) return nError; } // If the file is a patch file, adjust raw data offset if(hf->pPatchInfo != NULL) RawFilePos += hf->pPatchInfo->dwLength; pbRawData = hf->pbFileSector; // If the file sector is not loaded yet, do it if(hf->dwSectorOffs != 0) { // Is the file compressed? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { // Allocate space for compressed data pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize); if(pbCompressed == NULL) return ERROR_NOT_ENOUGH_MEMORY; pbRawData = pbCompressed; } // Load the raw (compressed, encrypted) data if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize)) { STORM_FREE(pbCompressed); return GetLastError(); } // If the file is encrypted, we have to decrypt the data first if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey); BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize); } // If the file is compressed, we have to decompress it now if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) { int cbOutBuffer = (int)hf->dwDataSize; int cbInBuffer = (int)pFileEntry->dwCmpSize; int nResult = 0; // // If the file is an incremental patch, the size of compressed data // is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo) // // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA: // // File CmprSize DcmpSize DataSize Compressed? // -------------------------------------- ---------- -------- -------- --------------- // esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes // deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No // if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) cbInBuffer = cbInBuffer - sizeof(TPatchInfo); // Is the file compressed by Blizzard's multiple compression ? if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) { // Remember the last used compression hf->dwCompression0 = pbRawData[0]; // Decompress the file if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); else nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); } // Is the file compressed by PKWARE Data Compression Library ? // Note: Single unit files compressed with IMPLODE are not supported by Blizzard else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer); nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; } else { if(hf->pbFileSector != NULL && pbRawData != hf->pbFileSector) memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize); } // Free the decompression buffer. if(pbCompressed != NULL) STORM_FREE(pbCompressed); // The file sector is now properly loaded hf->dwSectorOffs = 0; } // At this moment, we have the file loaded into the file buffer. // Copy as much as the caller wants if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0) { // File position is greater or equal to file size ? if(dwFilePos >= hf->dwDataSize) { *pdwBytesRead = 0; return ERROR_SUCCESS; } // If not enough bytes remaining in the file, cut them if((hf->dwDataSize - dwFilePos) < dwToRead) dwToRead = (hf->dwDataSize - dwFilePos); // Copy the bytes memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead); // Give the number of bytes read *pdwBytesRead = dwToRead; return ERROR_SUCCESS; } // An error, sorry return ERROR_CAN_NOT_COMPLETE; }
// 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; }