server/dep/StormLib/src/SFileReadFile.cpp
Salja f4be15a7af Some missing from merge.
Signed-off-by: Salja <salja2012@hotmail.de>
2020-02-16 02:50:23 +00:00

1186 lines
41 KiB
C++

/*****************************************************************************/
/* 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 structures
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
};
//-----------------------------------------------------------------------------
// Local functions
static void CopyFileName(char * szTarget, const TCHAR * szSource)
{
while(*szSource != 0)
*szTarget++ = (char)*szSource++;
*szTarget = 0;
}
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 bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded)
{
TMPQFile * hfTemp;
TCHAR * szPatchChain = (TCHAR *)pvFileInfo;
TCHAR * szFileName;
size_t cchCharsNeeded = 1;
size_t nLength;
DWORD cbLengthNeeded;
// Check if the "hf" is a MPQ file
if(hf->pStream != NULL)
{
// Calculate the length needed
szFileName = FileStream_GetFileName(hf->pStream);
cchCharsNeeded += _tcslen(szFileName) + 1;
cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
// If we have enough space, copy the file name
if(cbFileInfo >= cbLengthNeeded)
{
nLength = _tcslen(szFileName) + 1;
memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR));
szPatchChain += nLength;
// Terminate the multi-string
*szPatchChain = 0;
}
}
else
{
// Calculate number of characters needed
for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile)
cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1;
cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
// If we have enough space, the copy the patch chain
if(cbFileInfo >= cbLengthNeeded)
{
for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile)
{
szFileName = FileStream_GetFileName(hfTemp->ha->pStream);
nLength = _tcslen(szFileName) + 1;
memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR));
szPatchChain += nLength;
}
// Terminate the multi-string
*szPatchChain = 0;
}
}
// Give result length, terminate multi-string and return
*pcbLengthNeeded = cbLengthNeeded;
return true;
}
// 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) && 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_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 Blizzard's multiple compression ?
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
{
if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2)
nResult = SCompDecompress2((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector);
else
nResult = SCompDecompress((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector);
}
// Is the file compressed by PKWARE Data Compression Library ?
else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
{
nResult = SCompExplode((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)
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_COMPRESSED)
{
// 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_COMPRESSED)
{
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((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, cbInBuffer);
else
nResult = SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)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((char *)hf->pbFileSector, &cbOutBuffer, (char *)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 ReadMpqFile(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 = ReadMpqFile(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;
}
//-----------------------------------------------------------------------------
// 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(hf))
{
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)
{
ULONGLONG FilePosition1;
ULONGLONG FilePosition2;
// 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
FileStream_GetPos(hf->pStream, &FilePosition1);
if(!FileStream_Read(hf->pStream, NULL, 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
{
nError = GetLastError();
}
}
else
{
dwBytesRead = dwToRead;
}
}
else
{
// If the file is a patch file, we have to read it special way
if(hf->hfPatchFile != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
{
nError = ReadMpqFilePatchFile(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 = ReadMpqFile(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(hf))
{
// 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(hf))
{
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
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
};
bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
{
TFileEntry * pFileEntry;
TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
char szPseudoName[20];
DWORD FirstBytes[2]; // The first 4 bytes of the file
DWORD dwFilePos; // Saved file position
int nError = ERROR_SUCCESS;
int i;
// Pre-zero the output buffer
if(szFileName != NULL)
*szFileName = 0;
// Check valid parameters
if(!IsValidFileHandle(hf))
nError = ERROR_INVALID_HANDLE;
pFileEntry = hf->pFileEntry;
// Only do something if the file name is not filled
if(nError == ERROR_SUCCESS && pFileEntry != NULL && pFileEntry->szFileName == NULL)
{
// Read the first 2 DWORDs bytes from the file
FirstBytes[0] = FirstBytes[1] = 0;
dwFilePos = SFileSetFilePointer(hf, 0, NULL, FILE_CURRENT);
SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), NULL, NULL);
BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));
SFileSetFilePointer(hf, dwFilePos, NULL, FILE_BEGIN);
// Try to guess file extension from those 2 DWORDs
for(i = 0; data2ext[i].szExt != NULL; i++)
{
if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data &&
(FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data)
{
sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt);
break;
}
}
// Put the file name to the file table
AllocateFileName(pFileEntry, szPseudoName);
}
// Now put the file name to the file structure
if(nError == ERROR_SUCCESS && szFileName != NULL)
{
if(pFileEntry != NULL && pFileEntry->szFileName != NULL)
strcpy(szFileName, pFileEntry->szFileName);
else if(hf->pStream != NULL)
CopyFileName(szFileName, FileStream_GetFileName(hf->pStream));
}
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
#define VERIFY_MPQ_HANDLE(h) \
if(!IsValidMpqHandle(h)) \
{ \
nError = ERROR_INVALID_HANDLE; \
break; \
}
#define VERIFY_FILE_HANDLE(h) \
if(!IsValidFileHandle(h)) \
{ \
nError = ERROR_INVALID_HANDLE; \
break; \
}
bool WINAPI SFileGetFileInfo(
HANDLE hMpqOrFile,
DWORD dwInfoType,
void * pvFileInfo,
DWORD cbFileInfo,
LPDWORD pcbLengthNeeded)
{
TMPQArchive * ha = (TMPQArchive *)hMpqOrFile;
TMPQBlock * pBlock;
TMPQFile * hf = (TMPQFile *)hMpqOrFile;
void * pvSrcFileInfo = NULL;
DWORD cbLengthNeeded = 0;
DWORD dwIsReadOnly;
DWORD dwFileCount = 0;
DWORD dwFileIndex;
DWORD dwFileKey;
DWORD i;
int nError = ERROR_SUCCESS;
switch(dwInfoType)
{
case SFILE_INFO_ARCHIVE_NAME:
VERIFY_MPQ_HANDLE(ha);
// pvFileInfo receives the name of the archive, terminated by 0
pvSrcFileInfo = FileStream_GetFileName(ha->pStream);
cbLengthNeeded = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR);
break;
case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &ha->pHeader->dwArchiveSize;
break;
case SFILE_INFO_MAX_FILE_COUNT: // Max. number of files in the MPQ
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &ha->dwMaxFileCount;
break;
case SFILE_INFO_HASH_TABLE_SIZE: // Size of the hash table
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &ha->pHeader->dwHashTableSize;
break;
case SFILE_INFO_BLOCK_TABLE_SIZE: // Size of the block table
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &ha->pHeader->dwBlockTableSize;
break;
case SFILE_INFO_SECTOR_SIZE:
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &ha->dwSectorSize;
break;
case SFILE_INFO_HASH_TABLE:
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
pvSrcFileInfo = ha->pHashTable;
break;
case SFILE_INFO_BLOCK_TABLE:
VERIFY_MPQ_HANDLE(ha);
cbLengthNeeded = ha->dwFileTableSize * sizeof(TMPQBlock);
if(cbFileInfo < cbLengthNeeded)
{
nError = ERROR_INSUFFICIENT_BUFFER;
break;
}
// Construct block table from file table size
pBlock = (TMPQBlock *)pvFileInfo;
for(i = 0; i < ha->dwFileTableSize; i++)
{
pBlock->dwFilePos = (DWORD)ha->pFileTable[i].ByteOffset;
pBlock->dwFSize = ha->pFileTable[i].dwFileSize;
pBlock->dwCSize = ha->pFileTable[i].dwCmpSize;
pBlock->dwFlags = ha->pFileTable[i].dwFlags;
pBlock++;
}
break;
case SFILE_INFO_NUM_FILES:
VERIFY_MPQ_HANDLE(ha);
dwFileCount = GetMpqFileCount(ha);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &dwFileCount;
break;
case SFILE_INFO_STREAM_FLAGS:
VERIFY_MPQ_HANDLE(ha);
FileStream_GetFlags(ha->pStream, &dwFileKey);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &dwFileKey;
break;
case SFILE_INFO_IS_READ_ONLY:
VERIFY_MPQ_HANDLE(ha);
dwIsReadOnly = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY));
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &dwIsReadOnly;
break;
case SFILE_INFO_HASH_INDEX:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->pFileEntry->dwHashIndex;
break;
case SFILE_INFO_CODENAME1:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->pFileEntry->dwHashIndex;
if(ha->pHashTable != NULL)
pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName1;
break;
case SFILE_INFO_CODENAME2:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
if(ha->pHashTable != NULL)
pvSrcFileInfo = &ha->pHashTable[hf->pFileEntry->dwHashIndex].dwName2;
break;
case SFILE_INFO_LOCALEID:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->pFileEntry->lcLocale;
break;
case SFILE_INFO_BLOCKINDEX:
VERIFY_FILE_HANDLE(hf);
dwFileIndex = (DWORD)(hf->pFileEntry - hf->ha->pFileTable);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &dwFileIndex;
break;
case SFILE_INFO_FILE_SIZE:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->pFileEntry->dwFileSize;
break;
case SFILE_INFO_COMPRESSED_SIZE:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->pFileEntry->dwCmpSize;
break;
case SFILE_INFO_FLAGS:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->pFileEntry->dwFlags;
break;
case SFILE_INFO_POSITION:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(ULONGLONG);
pvSrcFileInfo = &hf->pFileEntry->ByteOffset;
break;
case SFILE_INFO_KEY:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &hf->dwFileKey;
break;
case SFILE_INFO_KEY_UNFIXED:
VERIFY_FILE_HANDLE(hf);
dwFileKey = hf->dwFileKey;
if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY)
dwFileKey = (dwFileKey ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos;
cbLengthNeeded = sizeof(DWORD);
pvSrcFileInfo = &dwFileKey;
break;
case SFILE_INFO_FILETIME:
VERIFY_FILE_HANDLE(hf);
cbLengthNeeded = sizeof(ULONGLONG);
pvSrcFileInfo = &hf->pFileEntry->FileTime;
break;
case SFILE_INFO_PATCH_CHAIN:
VERIFY_FILE_HANDLE(hf);
GetFilePatchChain(hf, pvFileInfo, cbFileInfo, &cbLengthNeeded);
break;
default:
nError = ERROR_INVALID_PARAMETER;
break;
}
// If everything is OK so far, copy the information
if(nError == ERROR_SUCCESS)
{
// Is the output buffer large enough?
if(cbFileInfo >= cbLengthNeeded)
{
// Copy the data
if(pvSrcFileInfo != NULL)
memcpy(pvFileInfo, pvSrcFileInfo, cbLengthNeeded);
}
else
{
nError = ERROR_INSUFFICIENT_BUFFER;
}
// Give the size to the caller
if(pcbLengthNeeded != NULL)
*pcbLengthNeeded = cbLengthNeeded;
}
// Set the last error value, if needed
if(nError != ERROR_SUCCESS)
SetLastError(nError);
return (nError == ERROR_SUCCESS);
}