diff options
author | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:56:53 +0100 |
---|---|---|
committer | Ladislav Zezula <ladislav.zezula@avg.com> | 2013-12-05 15:56:53 +0100 |
commit | ff0c25952a28a927c48738ab5207b9bda69e588a (patch) | |
tree | 6140d1edfa4adad30b9d29605ca0711e58162028 | |
parent | 870acd582701bc67c29b48319972303e5ceb706d (diff) |
+ StormLib 9.0 BETA
-rw-r--r-- | src/SFileGetFileInfo.cpp | 1341 | ||||
-rw-r--r-- | test/Readme.txt | 88 | ||||
-rw-r--r-- | test/TLogHelper.cpp | 278 |
3 files changed, 1707 insertions, 0 deletions
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp new file mode 100644 index 0000000..6a85bd9 --- /dev/null +++ b/src/SFileGetFileInfo.cpp @@ -0,0 +1,1341 @@ +/*****************************************************************************/ +/* SFileReadFile.cpp Copyright (c) Ladislav Zezula 2003 */ +/*---------------------------------------------------------------------------*/ +/* Description : */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* xx.xx.99 1.00 Lad The first version of SFileReadFile.cpp */ +/* 24.03.99 1.00 Lad Added the SFileGetFileInfo function */ +/*****************************************************************************/ + +#define __STORMLIB_SELF__ +#include "StormLib.h" +#include "StormCommon.h" + +//----------------------------------------------------------------------------- +// Local functions + +static DWORD GetMpqFileCount(TMPQArchive * ha) +{ + TFileEntry * pFileTableEnd; + TFileEntry * pFileEntry; + DWORD dwFileCount = 0; + + // Go through all open MPQs, including patches + while(ha != NULL) + { + // Only count files that are not patch files + pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; + for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) + { + // If the file is patch file and this is not primary archive, skip it + // BUGBUG: This errorneously counts non-patch files that are in both + // base MPQ and in patches, and increases the number of files by cca 50% + if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS) + dwFileCount++; + } + + // Move to the next patch archive + ha = ha->haPatch; + } + + return dwFileCount; +} + +static TCHAR * GetFilePatchChain(TMPQFile * hf, DWORD * pcbChainLength) +{ + TMPQFile * hfTemp; + TCHAR * szPatchChain = NULL; + TCHAR * szPatchItem = NULL; + TCHAR * szFileName; + size_t cchCharsNeeded = 1; + size_t nLength; + + // Patch chain is only supported on MPQ files. + if(hf->pStream == NULL) + { + // Calculate the necessary length of the multi-string + for(hfTemp = hf; hfTemp != NULL; hfTemp->hfPatchFile) + cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; + + // Allocate space for the multi-string + szPatchChain = szPatchItem = STORM_ALLOC(TCHAR, cchCharsNeeded); + if(szPatchChain != NULL) + { + // Fill-in all the names + for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) + { + szFileName = FileStream_GetFileName(hfTemp->ha->pStream); + nLength = _tcslen(szFileName) + 1; + + memcpy(szPatchItem, szFileName, nLength * sizeof(TCHAR)); + szPatchItem += nLength; + } + + // Terminate the multi-string + *szPatchItem++ = 0; + } + + // The length must match + assert((size_t)(szPatchItem - szPatchChain) == cchCharsNeeded); + } + + // Give the length of the patch chain, in bytes + if(pcbChainLength != NULL) + pcbChainLength[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); + return szPatchChain; +} + +// 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_COMPRESS_MASK) + { + // 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) && hf->bLoadedSectorCRCs == false) + { + // + // Sector CRCs is plain crap feature. It is almost never present, + // often it's empty, or the end offset of sector CRCs is zero. + // We only try to load sector CRCs once, and regardless if it fails + // or not, we won't try that again for the given file. + // + + AllocateSectorChecksums(hf, true); + hf->bLoadedSectorCRCs = true; + } + + // TODO: If the raw data MD5s are not loaded yet, load them now + // Only do it if the MPQ is of format 4.0 +// if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0) +// { +// nError = AllocateRawMD5s(hf, true); +// if(nError != ERROR_SUCCESS) +// return nError; +// } + + // If the file is compressed, also allocate secondary buffer + pbInSector = pbRawSector = STORM_ALLOC(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_COMPRESS_MASK) + 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 Blizzard's multiple compression ? + if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) + { + if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2) + nResult = SCompDecompress2(pbOutSector, &cbOutSector, pbInSector, cbInSector); + else + nResult = SCompDecompress(pbOutSector, &cbOutSector, pbInSector, cbInSector); + } + + // Is the file compressed by PKWARE Data Compression Library ? + else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) + { + nResult = SCompExplode(pbOutSector, &cbOutSector, 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) + STORM_FREE(pbRawSector); + + // Give the caller thenumber of bytes read + *pdwBytesRead = dwBytesRead; + return nError; +} + +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; + 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_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) + { + 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(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 ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) +{ + ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C) + TMPQArchive * ha = hf->ha; + TFileEntry * pFileEntry = hf->pFileEntry; + LPBYTE pbCompressed = NULL; + LPBYTE pbRawData = hf->pbFileSector; + int nError = ERROR_SUCCESS; + + // We do not support patch files in MPK archives + assert(hf->pPatchInfo == NULL); + + // If the file buffer is not allocated yet, do it. + if(hf->pbFileSector == NULL) + { + nError = AllocateSectorBuffer(hf); + if(nError != ERROR_SUCCESS) + return nError; + + // 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) + { + DecryptMpkTable(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; + + if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, 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 ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead) +{ + TMPQArchive * ha = hf->ha; + LPBYTE pbBuffer = (BYTE *)pvBuffer; + DWORD dwTotalBytesRead = 0; // Total bytes read in all three parts + DWORD dwSectorSizeMask = ha->dwSectorSize - 1; // Mask for block size, usually 0x0FFF + DWORD dwFileSectorPos; // File offset of the loaded sector + DWORD dwBytesRead; // Number of bytes read (temporary variable) + int nError; + + // If the file position is at or beyond end of file, do nothing + if(dwFilePos >= hf->dwDataSize) + { + *pdwBytesRead = 0; + return ERROR_SUCCESS; + } + + // If not enough bytes in the file remaining, cut them + if(dwBytesToRead > (hf->dwDataSize - dwFilePos)) + dwBytesToRead = (hf->dwDataSize - dwFilePos); + + // Compute sector position in the file + dwFileSectorPos = dwFilePos & ~dwSectorSizeMask; // Position in the block + + // If the file sector buffer is not allocated yet, do it now + if(hf->pbFileSector == NULL) + { + nError = AllocateSectorBuffer(hf); + if(nError != ERROR_SUCCESS) + return nError; + } + + // Load the first (incomplete) file sector + if(dwFilePos & dwSectorSizeMask) + { + DWORD dwBytesInSector = ha->dwSectorSize; + DWORD dwBufferOffs = dwFilePos & dwSectorSizeMask; + DWORD dwToCopy; + + // Is the file sector already loaded ? + if(hf->dwSectorOffs != dwFileSectorPos) + { + // Load one MPQ sector into archive buffer + nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesInSector); + if(nError != ERROR_SUCCESS) + return nError; + + // Remember that the data loaded to the sector have new file offset + hf->dwSectorOffs = dwFileSectorPos; + } + else + { + if((dwFileSectorPos + dwBytesInSector) > hf->dwDataSize) + dwBytesInSector = hf->dwDataSize - dwFileSectorPos; + } + + // Copy the data from the offset in the loaded sector to the end of the sector + dwToCopy = dwBytesInSector - dwBufferOffs; + if(dwToCopy > dwBytesToRead) + dwToCopy = dwBytesToRead; + + // Copy data from sector buffer into target buffer + memcpy(pbBuffer, hf->pbFileSector + dwBufferOffs, dwToCopy); + + // Update pointers and byte counts + dwTotalBytesRead += dwToCopy; + dwFileSectorPos += dwBytesInSector; + pbBuffer += dwToCopy; + dwBytesToRead -= dwToCopy; + } + + // Load the whole ("middle") sectors only if there is at least one full sector to be read + if(dwBytesToRead >= ha->dwSectorSize) + { + DWORD dwBlockBytes = dwBytesToRead & ~dwSectorSizeMask; + + // Load all sectors to the output buffer + nError = ReadMpqSectors(hf, pbBuffer, dwFileSectorPos, dwBlockBytes, &dwBytesRead); + if(nError != ERROR_SUCCESS) + return nError; + + // Update pointers + dwTotalBytesRead += dwBytesRead; + dwFileSectorPos += dwBytesRead; + pbBuffer += dwBytesRead; + dwBytesToRead -= dwBytesRead; + } + + // Read the terminating sector + if(dwBytesToRead > 0) + { + DWORD dwToCopy = ha->dwSectorSize; + + // Is the file sector already loaded ? + if(hf->dwSectorOffs != dwFileSectorPos) + { + // Load one MPQ sector into archive buffer + nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesRead); + if(nError != ERROR_SUCCESS) + return nError; + + // Remember that the data loaded to the sector have new file offset + hf->dwSectorOffs = dwFileSectorPos; + } + + // Check number of bytes read + if(dwToCopy > dwBytesToRead) + dwToCopy = dwBytesToRead; + + // Copy the data from the cached last sector to the caller's buffer + memcpy(pbBuffer, hf->pbFileSector, dwToCopy); + + // Update pointers + dwTotalBytesRead += dwToCopy; + } + + // Store total number of bytes read to the caller + *pdwBytesRead = dwTotalBytesRead; + return ERROR_SUCCESS; +} + +static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) +{ + DWORD dwBytesToRead = dwToRead; + DWORD dwBytesRead = 0; + int nError = ERROR_SUCCESS; + + // Make sure that the patch file is loaded completely + if(hf->pbFileData == NULL) + { + // Load the original file and store its content to "pbOldData" + hf->pbFileData = STORM_ALLOC(BYTE, hf->pFileEntry->dwFileSize); + hf->cbFileData = hf->pFileEntry->dwFileSize; + if(hf->pbFileData == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Read the file data + if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) + nError = ReadMpqFileSingleUnit(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); + else + nError = ReadMpqFileSectorFile(hf, hf->pbFileData, 0, hf->cbFileData, &dwBytesRead); + + // Fix error code + if(nError == ERROR_SUCCESS && dwBytesRead != hf->cbFileData) + nError = ERROR_FILE_CORRUPT; + + // Patch the file data + if(nError == ERROR_SUCCESS) + nError = PatchFileData(hf); + + // Reset number of bytes read to zero + dwBytesRead = 0; + } + + // If there is something to read, do it + if(nError == ERROR_SUCCESS) + { + if(dwFilePos < hf->cbFileData) + { + // Make sure we don't copy more than file size + if((dwFilePos + dwToRead) > hf->cbFileData) + dwToRead = hf->cbFileData - dwFilePos; + + // Copy the appropriate amount of the file data to the caller's buffer + memcpy(pvBuffer, hf->pbFileData + dwFilePos, dwToRead); + dwBytesRead = dwToRead; + } + + // Set the proper error code + nError = (dwBytesRead == dwBytesToRead) ? ERROR_SUCCESS : ERROR_HANDLE_EOF; + } + + // Give the result to the caller + if(pdwBytesRead != NULL) + *pdwBytesRead = dwBytesRead; + return nError; +} + +static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead) +{ + ULONGLONG FilePosition1 = dwFilePos; + ULONGLONG FilePosition2; + DWORD dwBytesRead = 0; + int nError = ERROR_SUCCESS; + + assert(hf->pStream != NULL); + + // Because stream I/O functions are designed to read + // "all or nothing", we compare file position before and after, + // and if they differ, we assume that number of bytes read + // is the difference between them + + if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead)) + { + // If not all bytes have been read, then return the number of bytes read + if((nError = GetLastError()) == ERROR_HANDLE_EOF) + { + FileStream_GetPos(hf->pStream, &FilePosition2); + dwBytesRead = (DWORD)(FilePosition2 - FilePosition1); + } + } + else + { + dwBytesRead = dwToRead; + } + + *pdwBytesRead = dwBytesRead; + return nError; +} + +//----------------------------------------------------------------------------- +// SFileReadFile + +bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped) +{ + TMPQFile * hf = (TMPQFile *)hFile; + DWORD dwBytesRead = 0; // Number of bytes read + int nError = ERROR_SUCCESS; + + // Keep compilers happy + lpOverlapped = lpOverlapped; + + // Check valid parameters + if(!IsValidFileHandle(hFile)) + { + SetLastError(ERROR_INVALID_HANDLE); + return false; + } + + if(pvBuffer == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + // If the file is local file, read the data directly from the stream + if(hf->pStream != NULL) + { + nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); + } + + // If the file is a patch file, we have to read it special way + else if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + { + nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); + } + + // If the archive is a MPK archive, we need special way to read the file + else if(hf->ha->dwSubType == MPQ_SUBTYPE_MPK) + { + nError = ReadMpkFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); + } + + // If the file is single unit file, redirect it to read file + else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) + { + nError = ReadMpqFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); + } + + // Otherwise read it as sector based MPQ file + else + { + nError = ReadMpqFileSectorFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead); + } + + // Increment the file position + hf->dwFilePos += dwBytesRead; + + // Give the caller the number of bytes read + if(pdwRead != NULL) + *pdwRead = dwBytesRead; + + // If the read operation succeeded, but not full number of bytes was read, + // set the last error to ERROR_HANDLE_EOF + if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead)) + nError = ERROR_HANDLE_EOF; + + // If something failed, set the last error value + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return (nError == ERROR_SUCCESS); +} + +//----------------------------------------------------------------------------- +// SFileGetFileSize + +DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh) +{ + ULONGLONG FileSize; + TMPQFile * hf = (TMPQFile *)hFile; + + // Validate the file handle before we go on + if(IsValidFileHandle(hFile)) + { + // Make sure that the variable is initialized + FileSize = 0; + + // If the file is patched file, we have to get the size of the last version + if(hf->hfPatchFile != NULL) + { + // Walk through the entire patch chain, take the last version + while(hf != NULL) + { + // Get the size of the currently pointed version + FileSize = hf->pFileEntry->dwFileSize; + + // Move to the next patch file in the hierarchy + hf = hf->hfPatchFile; + } + } + else + { + // Is it a local file ? + if(hf->pStream != NULL) + { + FileStream_GetSize(hf->pStream, &FileSize); + } + else + { + FileSize = hf->dwDataSize; + } + } + + // If opened from archive, return file size + if(pdwFileSizeHigh != NULL) + *pdwFileSizeHigh = (DWORD)(FileSize >> 32); + return (DWORD)FileSize; + } + + SetLastError(ERROR_INVALID_HANDLE); + return SFILE_INVALID_SIZE; +} + +DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) +{ + TMPQFile * hf = (TMPQFile *)hFile; + ULONGLONG FilePosition; + ULONGLONG MoveOffset; + DWORD dwFilePosHi; + + // If the hFile is not a valid file handle, return an error. + if(!IsValidFileHandle(hFile)) + { + SetLastError(ERROR_INVALID_HANDLE); + return SFILE_INVALID_POS; + } + + // Get the relative point where to move from + switch(dwMoveMethod) + { + case FILE_BEGIN: + FilePosition = 0; + break; + + case FILE_CURRENT: + if(hf->pStream != NULL) + { + FileStream_GetPos(hf->pStream, &FilePosition); + } + else + { + FilePosition = hf->dwFilePos; + } + break; + + case FILE_END: + if(hf->pStream != NULL) + { + FileStream_GetSize(hf->pStream, &FilePosition); + } + else + { + FilePosition = SFileGetFileSize(hFile, NULL); + } + break; + + default: + SetLastError(ERROR_INVALID_PARAMETER); + return SFILE_INVALID_POS; + } + + // Now get the move offset. Note that both values form + // a signed 64-bit value (a file pointer can be moved backwards) + if(plFilePosHigh != NULL) + dwFilePosHi = *plFilePosHigh; + else + dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0; + MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos); + + // Now calculate the new file pointer + // Do not allow the file pointer to go before the begin of the file + FilePosition += MoveOffset; + if(FilePosition < 0) + FilePosition = 0; + + // Now apply the file pointer to the file + if(hf->pStream != NULL) + { + // Apply the new file position + if(!FileStream_Read(hf->pStream, &FilePosition, NULL, 0)) + return SFILE_INVALID_POS; + + // Return the new file position + if(plFilePosHigh != NULL) + *plFilePosHigh = (LONG)(FilePosition >> 32); + return (DWORD)FilePosition; + } + else + { + // Files in MPQ can't be bigger than 4 GB. + // We don't allow to go past 4 GB + if(FilePosition >> 32) + { + SetLastError(ERROR_INVALID_PARAMETER); + return SFILE_INVALID_POS; + } + + // Change the file position + hf->dwFilePos = (DWORD)FilePosition; + + // Return the new file position + if(plFilePosHigh != NULL) + *plFilePosHigh = 0; + return (DWORD)FilePosition; + } +} + +//----------------------------------------------------------------------------- +// Tries to retrieve the file name + +struct TFileHeader2Ext +{ + DWORD dwOffset00Data; // Required data at offset 00 (32-bits) + DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored + DWORD dwOffset04Data; // Required data at offset 04 (32-bits) + DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored + const char * szExt; // Supplied extension, if the condition is true +}; + +static TFileHeader2Ext data2ext[] = +{ + {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files + {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files + {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') + {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' + {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' + {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) + {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I + {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II + {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' + {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM + {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files + {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures + {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) + {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files + {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps + {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' + {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 + {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc + {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders + {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image + {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension + {0, 0, 0, 0, NULL} // Terminator +}; + +static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) +{ + TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle + DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file + DWORD dwBytesRead = 0; + DWORD dwFilePos; // Saved file position + + // Read the first 2 DWORDs bytes from the file + dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); + SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); + SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); + + // If we read at least 8 bytes + if(dwBytesRead == sizeof(FirstBytes)) + { + // Make sure that the array is properly BSWAP-ed + BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); + + // Try to guess file extension from those 2 DWORDs + for(size_t i = 0; data2ext[i].szExt != NULL; i++) + { + if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && + (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) + { + char szPseudoName[20] = ""; + + // Format the pseudo-name + sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); + + // Save the pseudo-name in the file entry as well + AllocateFileName(hf->ha, pFileEntry, szPseudoName); + + // If the caller wants to copy the file name, do it + if(szFileName != NULL) + strcpy(szFileName, szPseudoName); + return ERROR_SUCCESS; + } + } + } + + return ERROR_NOT_SUPPORTED; +} + +bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) +{ + TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle + TCHAR * szFileNameT; + int nError = ERROR_INVALID_HANDLE; + + // Pre-zero the output buffer + if(szFileName != NULL) + *szFileName = 0; + + // Check valid parameters + if(IsValidFileHandle(hFile)) + { + TFileEntry * pFileEntry = hf->pFileEntry; + + // For MPQ files, retrieve the file name from the file entry + if(hf->pStream == NULL) + { + if(pFileEntry != NULL) + { + // If the file name is not there yet, create a pseudo name + if(pFileEntry->szFileName == NULL) + { + nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); + } + else + { + if(szFileName != NULL) + strcpy(szFileName, pFileEntry->szFileName); + nError = ERROR_SUCCESS; + } + } + } + + // For local files, copy the file name from the stream + else + { + if(szFileName != NULL) + { + szFileNameT = FileStream_GetFileName(hf->pStream); + CopyFileName(szFileName, szFileNameT, _tcslen(szFileNameT)); + } + nError = ERROR_SUCCESS; + } + } + + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return (nError == ERROR_SUCCESS); +} + +//----------------------------------------------------------------------------- +// Retrieves an information about an archive or about a file within the archive +// +// hMpqOrFile - Handle to an MPQ archive or to a file +// dwInfoType - Information to obtain + +bool WINAPI SFileGetFileInfo( + HANDLE hMpqOrFile, + DWORD dwInfoType, + void * pvFileInfo, + DWORD cbFileInfo, + LPDWORD pcbLengthNeeded) +{ + TMPQArchive * ha = NULL; + TMPQBlock * pBlockTable = NULL; + ULONGLONG Int64Value = 0; + TMPQFile * hf = NULL; + TCHAR * szPatchChain = NULL; + void * pvSrcFileInfo = NULL; + DWORD cbSrcFileInfo = 0; + DWORD dwInt32Value = 0; + int nError = ERROR_INVALID_PARAMETER; + + switch(dwInfoType) + { + case SFILE_INFO_ARCHIVE_NAME: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = FileStream_GetFileName(ha->pStream); + cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); + } + break; + + case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwArchiveSize; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->dwMaxFileCount; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwHashTableSize; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_SECTOR_SIZE: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &ha->dwSectorSize; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_HASH_TABLE: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = ha->pHashTable; + cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); + } + break; + + case SFILE_INFO_BLOCK_TABLE: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = pBlockTable = TranslateBlockTable(ha, &Int64Value, NULL); + cbSrcFileInfo = (DWORD)(Int64Value / sizeof(TMPQBlock)); + } + break; + + case SFILE_INFO_NUM_FILES: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + dwInt32Value = GetMpqFileCount(ha); + } + break; + + case SFILE_INFO_STREAM_FLAGS: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + FileStream_GetFlags(ha->pStream, &dwInt32Value); + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_IS_READ_ONLY: + ha = IsValidMpqHandle(hMpqOrFile); + if(ha != NULL) + { + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + dwInt32Value = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); + } + break; + + case SFILE_INFO_HASH_INDEX: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->dwHashIndex; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_CODENAME1: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) + { + pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_CODENAME2: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->ha != NULL && hf->ha->pHashTable != NULL) + { + pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_LOCALEID: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + dwInt32Value = hf->pFileEntry->lcLocale; + } + break; + + case SFILE_INFO_BLOCKINDEX: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL && hf->ha != NULL) + { + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); + } + break; + + case SFILE_INFO_FILE_SIZE: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->dwFileSize; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_COMPRESSED_SIZE: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_FLAGS: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->dwFlags; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_POSITION: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->ByteOffset; + cbSrcFileInfo = sizeof(ULONGLONG); + } + break; + + case SFILE_INFO_KEY: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->dwFileKey; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_KEY_UNFIXED: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + dwInt32Value = hf->dwFileKey; + if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) + dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos; + pvSrcFileInfo = &dwInt32Value; + cbSrcFileInfo = sizeof(DWORD); + } + break; + + case SFILE_INFO_FILETIME: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + { + pvSrcFileInfo = &hf->pFileEntry->FileTime; + cbSrcFileInfo = sizeof(ULONGLONG); + } + break; + + case SFILE_INFO_PATCH_CHAIN: + hf = IsValidFileHandle(hMpqOrFile); + if(hf != NULL) + pvSrcFileInfo = szPatchChain = GetFilePatchChain(hf, &cbSrcFileInfo); + break; + } + + // Check if one of the cases case yielded a result + if(pvSrcFileInfo != NULL && pvFileInfo != NULL) + { + // Give the length needed + if(pcbLengthNeeded != NULL) + pcbLengthNeeded[0] = cbSrcFileInfo; + + // Verify if we have enough space in the output buffer + if(cbSrcFileInfo <= cbFileInfo) + { + memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo); + nError = ERROR_SUCCESS; + } + else + { + nError = ERROR_INSUFFICIENT_BUFFER; + } + } + + // Free the allocated buffers, if any + if(szPatchChain != NULL) + STORM_FREE(szPatchChain); + if(pBlockTable != NULL) + STORM_FREE(pBlockTable); + + // Set the last error value, if needed + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return (nError == ERROR_SUCCESS); +} diff --git a/test/Readme.txt b/test/Readme.txt new file mode 100644 index 0000000..8ef0c67 --- /dev/null +++ b/test/Readme.txt @@ -0,0 +1,88 @@ +Testing MPQ set for StormLib test suite +======================================= + +The path to the directory could be anywhere. Path to this directory +is set on a command line. If there is no command line argument passed, +one of the hard-coded paths is used. + +The testing directory must contain the following files: + +Path File Size +------------------------------------------------- ----------- +patches\wow-update-13164.MPQ 480 028 668 // Original patch files from WoW +patches\wow-update-13205.MPQ 8 814 +patches\wow-update-13287.MPQ 333 363 917 +patches\wow-update-13329.MPQ 159 285 434 +patches\wow-update-13596.MPQ 148 022 182 +patches\wow-update-13623.MPQ 11 561 +patches\wow-update-base-13914.MPQ 293 580 556 +patches\wow-update-base-14007.MPQ 262 906 +patches\wow-update-base-14333.MPQ 368 972 365 +patches\wow-update-base-14480.MPQ 75 897 +patches\wow-update-base-14545.MPQ 1 224 415 +patches\wow-update-base-14946.MPQ 749 246 163 +patches\wow-update-base-15005.MPQ 7 121 223 +patches\wow-update-base-15050.MPQ 4 497 +patches\wow-update-enGB-16016.MPQ 17 117 318 +patches\wow-update-enGB-16048.MPQ 78 180 +patches\wow-update-enGB-16057.MPQ 4 072 +patches\wow-update-enGB-16309.MPQ 28 187 877 +patches\wow-update-enGB-16357.MPQ 3 621 +patches\wow-update-enGB-16516.MPQ 42 934 402 +patches\wow-update-enGB-16650.MPQ 426 683 +patches\wow-update-enGB-16844.MPQ 18 366 460 +patches\wow-update-enGB-16965.MPQ 305 202 +patches\wow-update-oldworld-13154.MPQ 54 068 663 +patches\wow-update-oldworld-13286.MPQ 2 328 773 + +AddFile.bin 0 // Just an empty file +AddFile.exe 432 640 // An arbitraty binary file +AddFile.wav 311 340 // An arbitrary WAVE file +ListFile_Blizzard.txt 3 904 784 // Listfile for Blizzard games (pre-WoW) +ListFile_WarOfTheImmortals.txt 3 904 858 // Listfile for War of the Immortals game +MPQ_1997_v1_Diablo1_DIABDAT.MPQ 517 501 282 // Originally DIABDAT.MPQ from Diablo I +MPQ_1997_v1_Diablo1_DIABDAT.sha 206 +MPQ_1997_v1_Diablo1_single_0.sha 201 // Single player savegame from Diablo I +MPQ_1997_v1_Diablo1_single_0.sv 98 980 +MPQ_1999_v1_WeakSignature.exe 1 031 826 // War2Patch_202.exe from Warcraft II Bnet Edition +MPQ_1999_v1_WeakSignature.sha 260 +MPQ_2002_v1_BlockTableCut.MPQ 27 765 301 // Cut file War3Patch.mpq from Warcraft III +MPQ_2002_v1_BlockTableCut.sha 250 +MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.sha 1 067 +MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.w3x 1 089 638 // Protected Warcraft III map +MPQ_2002_v1_ProtectedMap_InvalidMpqFormat.sha 507 +MPQ_2002_v1_ProtectedMap_InvalidMpqFormat.w3x 2 429 195 // Protected Warcraft III map +MPQ_2002_v1_ProtectedMap_InvalidUserData.sha 293 +MPQ_2002_v1_ProtectedMap_InvalidUserData.w3x 596 486 // Protected Warcraft III map +MPQ_2002_v1_StrongSignature.sha 250 +MPQ_2002_v1_StrongSignature.w3m 306 818 // (10)DustwallowKeys.w3m from Warcraft III +MPQ_2009_v2_WoW_patch.MPQ.part 31 396 380 // patch.MPQ.part from trial WoW build 10958 +MPQ_2009_v2_WoW_patch.MPQ.sha 226 +MPQ_2010_v2_HasUserData.s2ma 1 972 177 // (4) - AI - Kulas Ravine (1x).s2ma from Starcraft II Beta +MPQ_2010_v2_HasUserData.sha 261 +MPQ_2010_v3_expansion-locale-frFR.MPQ 2 980 489 // expansion-locale-frFR.MPQ from WoW 12911 +MPQ_2010_v3_expansion-locale-frFR.sha 274 +MPQ_2011_v2_EncryptedMpq.MPQE 41 631 764 // Installer UI 2 esES.MPQE from Starcraft II Installer (build 15404) +MPQ_2011_v2_EncryptedMpq.sha 255 +MPQ_2011_v4_InvalidHetEntryCount.MPQ 206 164 // An invalid MPQ created by a previous Stormlib version +MPQ_2011_v4_InvalidHetEntryCount.sha 312 +MPQ_2012_v2_EmptyMpq.MPQ 44 // Empty MPQ (Cache from WoW 11723) +MPQ_2012_v2_EmptyMpq.sha 212 +MPQ_2012_v4_OldWorld.MPQ 1 854 547 587 // OldWorld.MPQ from WoW 12911 +MPQ_2012_v4_OldWorld.sha 270 +MPQ_2013_v4_EmptyMpq.MPQ 208 // Empty MPQ created by StormLib +MPQ_2013_v4_EmptyMpq.sha 183 +MPQ_2013_v4_expansion1.MPQ 1 426 926 736 // expansion1.MPQ from WoW build 17538 +MPQ_2013_v4_expansion1.sha 221 +MPQ_2013_v4_locale-enGB.MPQ 446 491 866 // locale-enGB.MPQ from WoW build 16965 +MPQ_2013_v4_locale-enGB.sha 273 +MPQ_2013_v4_patch-base-16357.MPQ 249 // patch-base-16357.MPQ from WoW build 17538 +MPQ_2013_v4_patch-base-16357.sha 229 +MPQ_2013_v4_SC2_EmptyMap.SC2Map 25 058 // Custom Starcraft II Map +MPQ_2013_v4_SC2_EmptyMap.sha 243 +MPQ_2013_v4_world.MPQ 2 617 175 835 // world.MPQ from WoW build 12911 +MPQ_2013_v4_world.sha 252 +MPx_2013_v1_LongwuOnline.mpk 2 146 549 // Scp.mpk from Longwu Online +MPx_2013_v1_LongwuOnline.sha 277 +MPx_2013_v1_WarOfTheImmortals.sha 248 +MPx_2013_v1_WarOfTheImmortals.sqp 56 775 675 // Modified Other.sqp from War of the Immortals diff --git a/test/TLogHelper.cpp b/test/TLogHelper.cpp new file mode 100644 index 0000000..47a4909 --- /dev/null +++ b/test/TLogHelper.cpp @@ -0,0 +1,278 @@ +/*****************************************************************************/ +/* TLogHelper.cpp Copyright (c) Ladislav Zezula 2013 */ +/*---------------------------------------------------------------------------*/ +/* Helper class for reporting StormLib tests */ +/* This file should be included directly from Test.cpp using #include */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 26.11.13 1.00 Lad The first version of TLogHelper.cpp */ +/*****************************************************************************/ + +//----------------------------------------------------------------------------- +// Definition of the TLogHelper class + +class TLogHelper +{ + public: + + TLogHelper(const char * szTestName); + ~TLogHelper(); + +#if defined(UNICODE) || defined(UNICODE) + // TCHAR-based functions. They are only needed on UNICODE builds. + // On ANSI builds is TCHAR = char, so we don't need them at all + int PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine); + int PrintErrorVa(const TCHAR * szFormat, ...); + int PrintError(const TCHAR * szFormat, const TCHAR * szFileName = NULL); +#endif // defined(UNICODE) || defined(UNICODE) + + // ANSI functions + int PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine); + void PrintProgress(const char * szFormat, ...); + void PrintMessage(const char * szFormat, ...); + int PrintErrorVa(const char * szFormat, ...); + int PrintError(const char * szFormat, const char * szFileName = NULL); + + protected: + + int GetConsoleWidth(); + + const char * szTestName; // Title of the text + size_t nTextLength; // Length of the previous progress message + bool bMessagePrinted; +}; + +//----------------------------------------------------------------------------- +// Constructor and destructor + +TLogHelper::TLogHelper(const char * szName) +{ + // Fill the test line structure + szTestName = szName; + nTextLength = 0; + bMessagePrinted = false; + + // Show the user that a test is running + PrintProgress("Running test \"%s\" ...", szTestName); +} + +TLogHelper::~TLogHelper() +{ + // If no message has been printed, show "OK" + if(bMessagePrinted == false) + PrintMessage("Running test \"%s\" ... OK", szTestName); +} + +//----------------------------------------------------------------------------- +// TCHAR-based functions. They are only needed on UNICODE builds. +// On ANSI builds is TCHAR = char, so we don't need them at all + +#if defined(UNICODE) || defined(UNICODE) +int TLogHelper::PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine) +{ + TCHAR szOneLineBuff[0x200]; + TCHAR * szSaveBuffer; + TCHAR * szBuffer = szOneLineBuff; + int nRemainingWidth; + int nConsoleWidth = GetConsoleWidth(); + int nLength = 0; + int nError = GetLastError(); + + // Always start the buffer with '\r' + *szBuffer++ = '\r'; + szSaveBuffer = szBuffer; + + // Print the prefix, if needed + if(szTestName != NULL && bPrintPrefix) + { + while(szTestName[nLength] != 0) + *szBuffer++ = szTestName[nLength++]; + + *szBuffer++ = ':'; + *szBuffer++ = ' '; + } + + // Format the message itself + if(szFormat != NULL) + { + nLength = _vstprintf(szBuffer, szFormat, argList); + szBuffer += nLength; + } + + // Print the last error, if needed + if(bPrintLastError) + { + nLength = _stprintf(szBuffer, _T(" (error code: %u)"), nError); + szBuffer += nLength; + } + + // Shall we pad the string? + if((szBuffer - szSaveBuffer) < nConsoleWidth) + { + // Calculate the remaining width + nRemainingWidth = GetConsoleWidth() - (int)(szBuffer - szSaveBuffer) - 1; + + // Pad the string with spaces to fill it up to the end of the line + for(int i = 0; i < nRemainingWidth; i++) + *szBuffer++ = 0x20; + + // Pad the buffer with backslashes to fill it up to the end of the line + for(int i = 0; i < nRemainingWidth; i++) + *szBuffer++ = 0x08; + } + + // Put the newline, if requested + *szBuffer++ = bPrintEndOfLine ? '\n' : 0; + *szBuffer = 0; + + // Remember if we printed a message + if(bPrintEndOfLine) + bMessagePrinted = true; + + // Spit out the text in one single printf + _tprintf(szOneLineBuff); + return nError; +} + +int TLogHelper::PrintErrorVa(const TCHAR * szFormat, ...) +{ + va_list argList; + int nResult; + + va_start(argList, szFormat); + nResult = PrintWithClreol(szFormat, argList, true, true, true); + va_end(argList); + + return nResult; +} + +int TLogHelper::PrintError(const TCHAR * szFormat, const TCHAR * szFileName) +{ + return PrintErrorVa(szFormat, szFileName); +} +#endif // defined(UNICODE) || defined(UNICODE) + +//----------------------------------------------------------------------------- +// ANSI functions + +int TLogHelper::PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine) +{ + char szOneLineBuff[0x200]; + char * szSaveBuffer; + char * szBuffer = szOneLineBuff; + int nRemainingWidth; + int nConsoleWidth = GetConsoleWidth(); + int nLength = 0; + int nError = GetLastError(); + + // Always start the buffer with '\r' + *szBuffer++ = '\r'; + szSaveBuffer = szBuffer; + + // Print the prefix, if needed + if(szTestName != NULL && bPrintPrefix) + { + while(szTestName[nLength] != 0) + *szBuffer++ = szTestName[nLength++]; + + *szBuffer++ = ':'; + *szBuffer++ = ' '; + } + + // Format the message itself + if(szFormat != NULL) + { + nLength = vsprintf(szBuffer, szFormat, argList); + szBuffer += nLength; + } + + // Print the last error, if needed + if(bPrintLastError) + { + nLength = sprintf(szBuffer, " (error code: %u)", nError); + szBuffer += nLength; + } + + // Shall we pad the string? + if((szBuffer - szSaveBuffer) < nConsoleWidth) + { + // Calculate the remaining width + nRemainingWidth = GetConsoleWidth() - (int)(szBuffer - szSaveBuffer) - 1; + + // Pad the string with spaces to fill it up to the end of the line + for(int i = 0; i < nRemainingWidth; i++) + *szBuffer++ = 0x20; + + // Pad the buffer with backslashes to fill it up to the end of the line + for(int i = 0; i < nRemainingWidth; i++) + *szBuffer++ = 0x08; + } + + // Put the newline, if requested + *szBuffer++ = bPrintEndOfLine ? '\n' : 0; + *szBuffer = 0; + + // Remember if we printed a message + if(bPrintEndOfLine) + bMessagePrinted = true; + + // Spit out the text in one single printf + printf(szOneLineBuff); + return nError; +} + +void TLogHelper::PrintProgress(const char * szFormat, ...) +{ + va_list argList; + + va_start(argList, szFormat); + PrintWithClreol(szFormat, argList, true, false, false); + va_end(argList); +} + +void TLogHelper::PrintMessage(const char * szFormat, ...) +{ + va_list argList; + + va_start(argList, szFormat); + PrintWithClreol(szFormat, argList, true, false, true); + va_end(argList); +} + +int TLogHelper::PrintErrorVa(const char * szFormat, ...) +{ + va_list argList; + int nResult; + + va_start(argList, szFormat); + nResult = PrintWithClreol(szFormat, argList, true, true, true); + va_end(argList); + + return nResult; +} + +int TLogHelper::PrintError(const char * szFormat, const char * szFileName) +{ + return PrintErrorVa(szFormat, szFileName); +} + +//----------------------------------------------------------------------------- +// Protected functions + +int TLogHelper::GetConsoleWidth() +{ +#ifdef PLATFORM_WINDOWS + + CONSOLE_SCREEN_BUFFER_INFO ScreenInfo; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ScreenInfo); + return (int)(ScreenInfo.srWindow.Right - ScreenInfo.srWindow.Left); + +#else + + // On non-Windows platforms, we assume that width of the console line + // is 80 characters + return 80; + +#endif +} |