diff options
author | Shauren <shauren.trinity@gmail.com> | 2012-07-05 14:16:44 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2012-07-05 14:16:44 +0200 |
commit | 32ba32c4ebe56ba931c8638460c24cd57ae29a75 (patch) | |
tree | 2f757648b85ec8989a7dfc573b954b9b4d718650 /dep/StormLib/src | |
parent | c95905ddbb22e2b5b5362b790aa851ef10d4e27e (diff) | |
parent | ed6f3e2deff55f913f9646db5f540b7704088478 (diff) |
Merge branch '4.x' of github.com:TrinityCore/TrinityCore into 4.3.4
Diffstat (limited to 'dep/StormLib/src')
26 files changed, 3121 insertions, 2038 deletions
diff --git a/dep/StormLib/src/FileStream.cpp b/dep/StormLib/src/FileStream.cpp index b8de102cd2e..413f2acb3a2 100644 --- a/dep/StormLib/src/FileStream.cpp +++ b/dep/StormLib/src/FileStream.cpp @@ -16,6 +16,11 @@ #define __STORMLIB_SELF__ #include "StormLib.h" #include "StormCommon.h" +#include "FileStream.h" + +#ifdef _MSC_VER +#pragma comment(lib, "wininet.lib") +#endif //----------------------------------------------------------------------------- // Local defines @@ -25,77 +30,11 @@ #endif #ifdef _MSC_VER -#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) +#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) #endif //----------------------------------------------------------------------------- -// Local structures - -// Structure describing the PART file header -typedef struct _PART_FILE_HEADER -{ - DWORD PartialVersion; // Always set to 2 - char GameBuildNumber[8]; // Minimum build number of the game that can use this MPQ - DWORD Unknown0C; - DWORD Unknown10; - DWORD Unknown14; // Often contains 0x1C (size of the rest of the header ?) - DWORD Unknown18; - DWORD ZeroValue1C; // Seems to always be zero - DWORD ZeroValue20; // Seems to always be zero - DWORD ZeroValue24; // Seems to always be zero - DWORD FileSizeLo; // Low 32 bits of the file size - DWORD FileSizeHi; // High 32 bits of the file size - DWORD BlockSize; // Size of one file block, in bytes - -} PART_FILE_HEADER, *PPART_FILE_HEADER; - -// Structure describing the block-to-file map entry -typedef struct _PART_FILE_MAP_ENTRY -{ - DWORD Flags; // 3 = the block is present in the file - DWORD BlockOffsLo; // Low 32 bits of the block position in the file - DWORD BlockOffsHi; // High 32 bits of the block position in the file - DWORD Unknown0C; - DWORD Unknown10; - -} PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY; - -struct TPartFileStream : public TFileStream -{ - ULONGLONG VirtualSize; // Virtual size of the file - ULONGLONG VirtualPos; // Virtual position in the file - DWORD BlockCount; // Number of file blocks. Used by partial file stream - DWORD BlockSize; // Size of one block. Used by partial file stream - - PART_FILE_MAP_ENTRY PartMap[1]; // File map, variable length -}; - -#define MPQE_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted - -struct TEncryptedStream : public TFileStream -{ - BYTE Key[MPQE_CHUNK_SIZE]; // File key -}; - -static bool IsPartHeader(PPART_FILE_HEADER pPartHdr) -{ - // Version number must be 2 - if(pPartHdr->PartialVersion == 2) - { - // GameBuildNumber must be anm ASCII number - if(isdigit(pPartHdr->GameBuildNumber[0]) && isdigit(pPartHdr->GameBuildNumber[1]) && isdigit(pPartHdr->GameBuildNumber[2])) - { - // Block size must be power of 2 - if((pPartHdr->BlockSize & (pPartHdr->BlockSize - 1)) == 0) - return true; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// Non-Windows support for LastError +// Local functions - platform-specific functions #ifndef PLATFORM_WINDOWS static int nLastError = ERROR_SUCCESS; @@ -111,639 +50,1087 @@ void SetLastError(int nError) } #endif -//----------------------------------------------------------------------------- -// Local functions - platform-specific functions - #ifndef PLATFORM_LITTLE_ENDIAN void ConvertPartHeader(void * partHeader) { PPART_FILE_HEADER theHeader = (PPART_FILE_HEADER)partHeader; theHeader->PartialVersion = SwapUInt32(theHeader->PartialVersion); - theHeader->Unknown0C = SwapUInt32(theHeader->Unknown0C); - theHeader->Unknown10 = SwapUInt32(theHeader->Unknown10); - theHeader->Unknown14 = SwapUInt32(theHeader->Unknown14); - theHeader->Unknown18 = SwapUInt32(theHeader->Unknown18); - theHeader->Unknown1C = SwapUInt32(theHeader->Unknown1C); - theHeader->Unknown20 = SwapUInt32(theHeader->Unknown20); - theHeader->ZeroValue = SwapUInt32(theHeader->ZeroValue); + theHeader->Flags = SwapUInt32(theHeader->Flags); theHeader->FileSizeLo = SwapUInt32(theHeader->FileSizeLo); theHeader->FileSizeHi = SwapUInt32(theHeader->FileSizeHi); theHeader->BlockSize = SwapUInt32(theHeader->BlockSize); } #endif -#ifdef PLATFORM_MAC -static void ConvertUTCDateTimeToFileTime(const UTCDateTimePtr inTime, ULONGLONG * pFT) -{ - UInt64 intTime = ((UInt64)inTime->highSeconds << 32) + inTime->lowSeconds; - intTime *= 10000000; - intTime += 0x0153b281e0fb4000ull; +//----------------------------------------------------------------------------- +// Preparing file bitmap for a complete file of a given size - *pFT = intTime; -} +#define DEFAULT_BLOCK_SIZE 0x4000 -static OSErr FSOpenDFCompat(FSRef *ref, char permission, short *refNum) +static bool Dummy_GetBitmap( + TFileStream * pStream, + TFileBitmap * pBitmap, + DWORD Length, + LPDWORD LengthNeeded) { - HFSUniStr255 forkName; - OSErr theErr; - Boolean isFolder, wasChanged; - - theErr = FSResolveAliasFile(ref, true, &isFolder, &wasChanged); - if (theErr != noErr) + ULONGLONG FileSize = 0; + DWORD TotalLength; + DWORD BlockCount; + DWORD BitmapSize; + DWORD LastByte; + bool bResult = false; + + // Get file size and calculate bitmap length + FileStream_GetSize(pStream, FileSize); + BlockCount = (DWORD)(((FileSize - 1) / DEFAULT_BLOCK_SIZE) + 1); + BitmapSize = (DWORD)(((BlockCount - 1) / 8) + 1); + + // Calculate and give the total length + TotalLength = sizeof(TFileBitmap) + BitmapSize; + if(LengthNeeded != NULL) + *LengthNeeded = TotalLength; + + // Has the caller given enough space for storing the structure? + if(Length >= sizeof(TFileBitmap)) { - return theErr; + memset(pBitmap, 0, sizeof(TFileBitmap)); + pBitmap->EndOffset = FileSize; + pBitmap->IsComplete = 1; + pBitmap->BitmapSize = BitmapSize; + pBitmap->BlockSize = DEFAULT_BLOCK_SIZE; + bResult = true; } - - FSGetDataForkName(&forkName); -#ifdef PLATFORM_64BIT - theErr = FSOpenFork(ref, forkName.length, forkName.unicode, permission, (FSIORefNum *)refNum); -#else - theErr = FSOpenFork(ref, forkName.length, forkName.unicode, permission, refNum); -#endif - return theErr; -} -#endif -#ifdef PLATFORM_LINUX -// time_t is number of seconds since 1.1.1970, UTC. -// 1 second = 10000000 (decimal) in FILETIME -static void ConvertTimeTToFileTime(ULONGLONG * pFileTime, time_t crt_time) -{ - // Set the start to 1.1.1970 00:00:00 - *pFileTime = 0x019DB1DED53E8000ULL + (10000000 * crt_time); + // Do we have enough space to fill the bitmap as well? + if(Length >= TotalLength) + { + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); + + // Fill the full blocks + memset(pbBitmap, 0xFF, (BlockCount / 8)); + pbBitmap += (BlockCount / 8); + bResult = true; + + // Supply the last block + if(BlockCount & 7) + { + LastByte = (1 << (BlockCount & 7)) - 1; + pbBitmap[0] = (BYTE)LastByte; + } + } + + return bResult; } -#endif -static HANDLE CreateNewFile( - const TCHAR * szFileName) // Name of the file to open +//----------------------------------------------------------------------------- +// Local functions - base file support + +static bool BaseFile_Read( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file { - HANDLE hFile = INVALID_HANDLE_VALUE; // Pre-set the file handle to INVALID_HANDLE_VALUE + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + DWORD dwBytesRead = 0; // Must be set by platform-specific code #ifdef PLATFORM_WINDOWS { - DWORD dwShareMode = FILE_SHARE_READ; - - if(dwGlobalFlags & SFILE_FLAG_ALLOW_WRITE_SHARE) - dwShareMode |= FILE_SHARE_WRITE; - - hFile = CreateFile(szFileName, - GENERIC_READ | GENERIC_WRITE, - dwShareMode, - NULL, - CREATE_ALWAYS, - 0, - NULL); - } -#endif + // Note: StormLib no longer supports Windows 9x. + // Thus, we can use the OVERLAPPED structure to specify + // file offset to read from file. This allows us to skip + // one system call to SetFilePointer -#ifdef PLATFORM_MAC - { - FSRef theParentRef; - FSRef theFileRef; - OSErr theErr; - short fileRef; - - theErr = FSPathMakeRef((const UInt8 *)szFileName, &theFileRef, NULL); - - if (theErr == noErr) - FSDeleteObject(&theFileRef); - - // Create the FSRef for the parent directory. - UInt8 folderName[MAX_PATH]; - memset(&theFileRef, 0, sizeof(FSRef)); - CFStringRef filePathCFString = CFStringCreateWithCString(NULL, szFileName, kCFStringEncodingUTF8); - CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, filePathCFString, kCFURLPOSIXPathStyle, false); - CFURLRef folderURL = CFURLCreateCopyDeletingLastPathComponent(NULL, fileURL); - CFURLGetFileSystemRepresentation(folderURL, true, folderName, MAX_PATH); - theErr = FSPathMakeRef(folderName, &theParentRef, NULL); - CFRelease(fileURL); - CFRelease(folderURL); - - if (theErr != noErr) + // Update the byte offset + pStream->Base.File.FilePos = ByteOffset; + + // Read the data + if(dwBytesToRead != 0) { - nLastError = theErr; - return INVALID_HANDLE_VALUE; + OVERLAPPED Overlapped; + + Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32); + Overlapped.Offset = (DWORD)ByteOffset; + Overlapped.hEvent = NULL; + if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, &Overlapped)) + return false; } - - // Create the file - UniChar unicodeFileName[256]; - fileURL = CFURLCreateWithFileSystemPath(NULL, filePathCFString, kCFURLPOSIXPathStyle, false); - CFStringRef fileNameCFString = CFURLCopyLastPathComponent(fileURL); - CFStringGetCharacters(fileNameCFString, CFRangeMake(0, CFStringGetLength(fileNameCFString)), - unicodeFileName); - theErr = FSCreateFileUnicode(&theParentRef, CFStringGetLength(fileNameCFString), unicodeFileName, - kFSCatInfoNone, NULL, &theFileRef, NULL); - CFRelease(fileNameCFString); - CFRelease(filePathCFString); - CFRelease(fileURL); - if (theErr != noErr) +/* + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) { - nLastError = theErr; - return INVALID_HANDLE_VALUE; + LONG ByteOffsetHi = (LONG)(ByteOffset >> 32); + + SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN); + pStream->Base.File.FilePos = ByteOffset; } - theErr = FSOpenDFCompat(&theFileRef, fsRdWrPerm, &fileRef); - if(theErr != noErr) + // Read the data + if(dwBytesToRead != 0) { - nLastError = theErr; - return INVALID_HANDLE_VALUE; + if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL)) + return false; } - - hFile = (HANDLE)(int)fileRef; +*/ } #endif -#ifdef PLATFORM_LINUX +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) { - intptr_t handle; + ssize_t bytes_read; - handle = open(szFileName, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if(handle == -1) + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) { - nLastError = errno; - return INVALID_HANDLE_VALUE; + lseek((intptr_t)pStream->Base.File.hFile, (off_t)(ByteOffset), SEEK_SET); + pStream->Base.File.FilePos = ByteOffset; } - hFile = (HANDLE)handle; + // Perform the read operation + if(dwBytesToRead != 0) + { + bytes_read = read((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToRead); + if(bytes_read == -1) + { + nLastError = errno; + return false; + } + + dwBytesRead = (DWORD)(size_t)bytes_read; + } } #endif - // Return the file handle - return hFile; + // Increment the current file position by number of bytes read + // If the number of bytes read doesn't match to required amount, return false + pStream->Base.File.FilePos = ByteOffset + dwBytesRead; + if(dwBytesRead != dwBytesToRead) + SetLastError(ERROR_HANDLE_EOF); + return (dwBytesRead == dwBytesToRead); } -static HANDLE OpenExistingFile( - const TCHAR * szFileName, // Name of the file to open - bool bWriteAccess) // false = read-only, true = read/write +/** + * \a pStream Pointer to an open stream + * \a pByteOffset Pointer to file byte offset. If NULL, writes to current position + * \a pvBuffer Pointer to data to be written + * \a dwBytesToWrite Number of bytes to write to the file + */ + +static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) { - HANDLE hFile = INVALID_HANDLE_VALUE; // Pre-set the file handle to INVALID_HANDLE_VALUE + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + DWORD dwBytesWritten = 0; // Must be set by platform-specific code #ifdef PLATFORM_WINDOWS { - DWORD dwShareMode = FILE_SHARE_READ; - - if(dwGlobalFlags & SFILE_FLAG_ALLOW_WRITE_SHARE) - dwShareMode |= FILE_SHARE_WRITE; - - hFile = CreateFile(szFileName, - bWriteAccess ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ, - dwShareMode, - NULL, - OPEN_EXISTING, - 0, - NULL); - } -#endif + // Note: StormLib no longer supports Windows 9x. + // Thus, we can use the OVERLAPPED structure to specify + // file offset to read from file. This allows us to skip + // one system call to SetFilePointer -#ifdef PLATFORM_MAC - { - FSRef theFileRef; - OSErr theErr; - short fileRef; - char permission = bWriteAccess ? fsRdWrPerm : fsRdPerm; + // Update the byte offset + pStream->Base.File.FilePos = ByteOffset; - theErr = FSPathMakeRef((const UInt8 *)szFileName, &theFileRef, NULL); - if(theErr != noErr) + // Read the data + if(dwBytesToWrite != 0) { - nLastError = theErr; - return INVALID_HANDLE_VALUE; - } + OVERLAPPED Overlapped; - theErr = FSOpenDFCompat(&theFileRef, permission, &fileRef); - if (theErr != noErr) + Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32); + Overlapped.Offset = (DWORD)ByteOffset; + Overlapped.hEvent = NULL; + if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, &Overlapped)) + return false; + } +/* + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) { - nLastError = theErr; - return INVALID_HANDLE_VALUE; + LONG ByteOffsetHi = (LONG)(ByteOffset >> 32); + + SetFilePointer(pStream->Base.File.hFile, (LONG)ByteOffset, &ByteOffsetHi, FILE_BEGIN); + pStream->Base.File.FilePos = ByteOffset; } - hFile = (HANDLE)(int)fileRef; + // Read the data + if(dwBytesToWrite != 0) + { + if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL)) + return false; + } +*/ } #endif -#ifdef PLATFORM_LINUX +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) { - int oflag = bWriteAccess ? O_RDWR : O_RDONLY; - intptr_t handle; + ssize_t bytes_written; - handle = open(szFileName, oflag | O_LARGEFILE); - if(handle == -1) + // If the byte offset is different from the current file position, + // we have to update the file position + if(ByteOffset != pStream->Base.File.FilePos) { - nLastError = errno; - return INVALID_HANDLE_VALUE; + lseek((intptr_t)pStream->Base.File.hFile, (off_t)(ByteOffset), SEEK_SET); + pStream->Base.File.FilePos = ByteOffset; } - hFile = (HANDLE)handle; + // Perform the read operation + bytes_written = write((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToWrite); + if(bytes_written == -1) + { + nLastError = errno; + return false; + } + + dwBytesWritten = (DWORD)(size_t)bytes_written; } #endif - // Return the file handle - return hFile; + // Increment the current file position by number of bytes read + pStream->Base.File.FilePos = ByteOffset + dwBytesWritten; + + // Also modify the file size, if needed + if(pStream->Base.File.FilePos > pStream->Base.File.FileSize) + pStream->Base.File.FileSize = pStream->Base.File.FilePos; + + if(dwBytesWritten != dwBytesToWrite) + SetLastError(ERROR_DISK_FULL); + return (dwBytesWritten == dwBytesToWrite); } -static void CloseTheFile(HANDLE hFile) +static bool BaseFile_GetPos( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG & ByteOffset) // Pointer to file byte offset { -#ifdef PLATFORM_WINDOWS - CloseHandle(hFile); -#endif - -#ifdef PLATFORM_MAC - FSCloseFork((short)(long)hFile); -#endif + ByteOffset = pStream->Base.File.FilePos; + return true; +} -#ifdef PLATFORM_LINUX - close((intptr_t)hFile); -#endif +static bool BaseFile_GetSize( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG & FileSize) // Pointer where to store file size +{ + FileSize = pStream->Base.File.FileSize; + return true; } /** - * Renames a file to another name. - * Note that the "szNewFile" file usually exists when this function is called, - * so the function must deal with it properly + * \a pStream Pointer to an open stream + * \a NewFileSize New size of the file */ -static bool RenameFile(const TCHAR * szExistingFile, const TCHAR * szNewFile) +static bool BaseFile_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) { #ifdef PLATFORM_WINDOWS - // Delete the original stream file. Don't check the result value, - // because if the file doesn't exist, it would fail - DeleteFile(szNewFile); + { + LONG FileSizeHi = (LONG)(NewFileSize >> 32); + LONG FileSizeLo; + DWORD dwNewPos; + bool bResult; - // Rename the new file to the old stream's file - return (bool)MoveFile(szExistingFile, szNewFile); -#endif + // Set the position at the new file size + dwNewPos = SetFilePointer(pStream->Base.File.hFile, (LONG)NewFileSize, &FileSizeHi, FILE_BEGIN); + if(dwNewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS) + return false; -#ifdef PLATFORM_MAC - OSErr theErr; - FSRef fromFileRef; - FSRef toFileRef; - - if (FSPathMakeRef((const UInt8 *)szNewFile, &toFileRef, NULL) == noErr) - FSDeleteObject(&toFileRef); - - // Get the path to the old file - theErr = FSPathMakeRef((const UInt8 *)szExistingFile, &fromFileRef, NULL); - if (theErr != noErr) - { - nLastError = theErr; - return false; + // Set the current file pointer as the end of the file + bResult = (bool)SetEndOfFile(pStream->Base.File.hFile); + + // Restore the file position + FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32); + FileSizeLo = (LONG)(pStream->Base.File.FilePos); + SetFilePointer(pStream->Base.File.hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN); + return bResult; } +#endif - // Get a CFString for the new file name - CFStringRef newFileNameCFString = CFStringCreateWithCString(NULL, szNewFile, kCFStringEncodingUTF8); - CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, newFileNameCFString, kCFURLPOSIXPathStyle, false); - CFRelease(newFileNameCFString); - newFileNameCFString = CFURLCopyLastPathComponent(fileURL); - CFRelease(fileURL); - - // Convert CFString to Unicode and rename the file - UniChar unicodeFileName[256]; - CFStringGetCharacters(newFileNameCFString, CFRangeMake(0, CFStringGetLength(newFileNameCFString)), - unicodeFileName); - theErr = FSRenameUnicode(&fromFileRef, CFStringGetLength(newFileNameCFString), unicodeFileName, - kTextEncodingUnknown, NULL); - if (theErr != noErr) +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) { - CFRelease(newFileNameCFString); - nLastError = theErr; - return false; + if(ftruncate((intptr_t)pStream->Base.File.hFile, (off_t)NewFileSize) == -1) + { + nLastError = errno; + return false; + } + + return true; } - - CFRelease(newFileNameCFString); - +#endif +} + +static bool BaseFile_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) +{ + *pFileTime = pStream->Base.File.FileTime; return true; +} + +// Renames the file pointed by pStream so that it contains data from pNewStream +static bool BaseFile_Switch(TFileStream * pStream, TFileStream * pNewStream) +{ +#ifdef PLATFORM_WINDOWS + // Delete the original stream file. Don't check the result value, + // because if the file doesn't exist, it would fail + DeleteFile(pStream->szFileName); + + // Rename the new file to the old stream's file + return (bool)MoveFile(pNewStream->szFileName, pStream->szFileName); #endif -#ifdef PLATFORM_LINUX +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) // "rename" on Linux also works if the target file exists - if(rename(szExistingFile, szNewFile) == -1) + if(rename(pNewStream->szFileName, pStream->szFileName) == -1) { nLastError = errno; return false; } - + return true; #endif } -//----------------------------------------------------------------------------- -// Stream functions - normal file stream - -static bool File_GetPos( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG & ByteOffset) // Pointer to file byte offset +static void BaseFile_Close(TFileStream * pStream) { - ByteOffset = pStream->RawFilePos; - return true; + if(pStream->Base.File.hFile != INVALID_HANDLE_VALUE) + { +#ifdef PLATFORM_WINDOWS + CloseHandle(pStream->Base.File.hFile); +#endif + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + close((intptr_t)pStream->Base.File.hFile); +#endif + } + + // Also invalidate the handle + pStream->Base.File.hFile = INVALID_HANDLE_VALUE; } -static bool File_Read( - TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position - void * pvBuffer, // Pointer to data to be read - DWORD dwBytesToRead) // Number of bytes to read from the file +static bool BaseFile_Create( + TFileStream * pStream, + const TCHAR * szFileName, + DWORD dwStreamFlags) { - DWORD dwBytesRead = 0; // Must be set by platform-specific code +#ifdef PLATFORM_WINDOWS + { + DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; + + pStream->Base.File.hFile = CreateFile(szFileName, + GENERIC_READ | GENERIC_WRITE, + dwWriteShare | FILE_SHARE_READ, + NULL, + CREATE_ALWAYS, + 0, + NULL); + if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) + return false; + } +#endif - // If the byte offset is not entered, use the current position - if(pByteOffset == NULL) - pByteOffset = &pStream->RawFilePos; +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + { + intptr_t handle; + + handle = open(szFileName, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if(handle == -1) + { + nLastError = errno; + return false; + } + + pStream->Base.File.hFile = (HANDLE)handle; + } +#endif + + // Fill-in the entry points + pStream->BaseRead = BaseFile_Read; + pStream->BaseWrite = BaseFile_Write; + pStream->BaseGetPos = BaseFile_GetPos; + pStream->BaseGetSize = BaseFile_GetSize; + pStream->BaseSetSize = BaseFile_SetSize; + pStream->BaseSetSize = BaseFile_SetSize; + pStream->BaseGetTime = BaseFile_GetTime; + pStream->BaseClose = BaseFile_Close; + + // Reset the file position + pStream->Base.File.FileSize = 0; + pStream->Base.File.FilePos = 0; + pStream->dwFlags = dwStreamFlags; + return true; +} +static bool BaseFile_Open( + TFileStream * pStream, + const TCHAR * szFileName, + DWORD dwStreamFlags) +{ #ifdef PLATFORM_WINDOWS { - // If the byte offset is different from the current file position, - // we have to update the file position - if(*pByteOffset != pStream->RawFilePos) - { - LONG ByteOffsetHi = (LONG)(*pByteOffset >> 32); - LONG ByteOffsetLo = (LONG)(*pByteOffset); + ULARGE_INTEGER FileSize; + DWORD dwDesiredAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? GENERIC_READ : GENERIC_ALL; + DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; + + // Open the file + pStream->Base.File.hFile = CreateFile(szFileName, + dwDesiredAccess, + dwWriteShare | FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + 0, + NULL); + if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) + return false; - SetFilePointer(pStream->hFile, ByteOffsetLo, &ByteOffsetHi, FILE_BEGIN); - pStream->RawFilePos = *pByteOffset; - } + // Query the file size + FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart); + pStream->Base.File.FileSize = FileSize.QuadPart; - // Read the data - if(dwBytesToRead != 0) - { - if(!ReadFile(pStream->hFile, pvBuffer, dwBytesToRead, &dwBytesRead, NULL)) - return false; - } + // Query last write time + GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime); } #endif -#ifdef PLATFORM_MAC +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) { - ByteCount nBytesToRead = (ByteCount)dwBytesToRead; - ByteCount nBytesRead = 0; - OSErr theErr; + struct stat fileinfo; + int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR; + intptr_t handle; - // If the byte offset is different from the current file position, - // we have to update the file position - if(*pByteOffset != pStream->RawFilePos) + // Open the file + handle = open(szFileName, oflag); + if(handle == -1) { - FSSetForkPosition((short)(long)pStream->hFile, fsFromStart, (SInt64)(*pByteOffset)); - pStream->RawFilePos = *pByteOffset; + nLastError = errno; + return false; } - // Read the data - if(nBytesToRead != 0) + // Get the file size + if(fstat(handle, &fileinfo) == -1) { - theErr = FSReadFork((short)(long)pStream->hFile, fsAtMark, 0, nBytesToRead, pvBuffer, &nBytesRead); - if (theErr != noErr && theErr != eofErr) - { - nLastError = theErr; - return false; - } - dwBytesRead = (DWORD)nBytesRead; + nLastError = errno; + return false; } + + // time_t is number of seconds since 1.1.1970, UTC. + // 1 second = 10000000 (decimal) in FILETIME + // Set the start to 1.1.1970 00:00:00 + pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); + pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size; + pStream->Base.File.hFile = (HANDLE)handle; } #endif -#ifdef PLATFORM_LINUX - { - ssize_t bytes_read; + // Fill-in the entry points + pStream->BaseRead = BaseFile_Read; + pStream->BaseWrite = BaseFile_Write; + pStream->BaseGetPos = BaseFile_GetPos; + pStream->BaseGetSize = BaseFile_GetSize; + pStream->BaseSetSize = BaseFile_SetSize; + pStream->BaseGetTime = BaseFile_GetTime; + pStream->BaseClose = BaseFile_Close; + + // Reset the file position + pStream->Base.File.FilePos = 0; + pStream->dwFlags = dwStreamFlags; + return true; +} - // If the byte offset is different from the current file position, - // we have to update the file position - if(*pByteOffset != pStream->RawFilePos) - { - lseek64((intptr_t)pStream->hFile, (off64_t)(*pByteOffset), SEEK_SET); - pStream->RawFilePos = *pByteOffset; - } +//----------------------------------------------------------------------------- +// Local functions - base memory-mapped file support - // Perform the read operation - if(dwBytesToRead != 0) - { - bytes_read = read((intptr_t)pStream->hFile, pvBuffer, (size_t)dwBytesToRead); - if(bytes_read == -1) - { - nLastError = errno; - return false; - } +static bool BaseMap_Read( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file +{ + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos; - dwBytesRead = (DWORD)(size_t)bytes_read; - } + // Do we have to read anything at all? + if(dwBytesToRead != 0) + { + // Don't allow reading past file size + if((ByteOffset + dwBytesToRead) > pStream->Base.Map.FileSize) + return false; + + // Copy the required data + memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead); } -#endif - // Increment the current file position by number of bytes read - // If the number of bytes read doesn't match to required amount, return false - pStream->RawFilePos = *pByteOffset + dwBytesRead; - if(dwBytesRead != dwBytesToRead) - SetLastError(ERROR_HANDLE_EOF); - return (dwBytesRead == dwBytesToRead); + // Move the current file position + pStream->Base.Map.FilePos += dwBytesToRead; + return true; } -/** - * \a pStream Pointer to an open stream - * \a pByteOffset Pointer to file byte offset. If NULL, writes to current position - * \a pvBuffer Pointer to data to be written - * \a dwBytesToWrite Number of bytes to write to the file - */ +static bool BaseMap_GetPos( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG & ByteOffset) // Pointer to file byte offset +{ + ByteOffset = pStream->Base.Map.FilePos; + return true; +} -static bool File_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) +static bool BaseMap_GetSize( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG & FileSize) // Pointer where to store file size { - DWORD dwBytesWritten = 0; // Must be set by platform-specific code + FileSize = pStream->Base.Map.FileSize; + return true; +} - // If the byte offset is not entered, use the current position - if(pByteOffset == NULL) - pByteOffset = &pStream->RawFilePos; +static bool BaseMap_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) +{ + *pFileTime = pStream->Base.Map.FileTime; + return true; +} +static void BaseMap_Close(TFileStream * pStream) +{ +#ifdef PLATFORM_WINDOWS + if(pStream->Base.Map.pbFile != NULL) + UnmapViewOfFile(pStream->Base.Map.pbFile); +#endif + +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + if(pStream->Base.Map.pbFile != NULL) + munmap(pStream->Base.Map.pbFile, (size_t )pStream->Base.Map.FileSize); +#endif + + pStream->Base.Map.pbFile = NULL; +} + +static bool BaseMap_Open( + TFileStream * pStream, + const TCHAR * szFileName, + DWORD dwStreamFlags) +{ #ifdef PLATFORM_WINDOWS + + ULARGE_INTEGER FileSize; + HANDLE hFile; + HANDLE hMap; + bool bResult = false; + + // Open the file for read access + hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if(hFile != NULL) { - // If the byte offset is different from the current file position, - // we have to update the file position - if(*pByteOffset != pStream->RawFilePos) + // Retrieve file size. Don't allow mapping file of a zero size. + FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart); + if(FileSize.QuadPart != 0) { - LONG ByteOffsetHi = (LONG)(*pByteOffset >> 32); - LONG ByteOffsetLo = (LONG)(*pByteOffset); + // Retrieve file time + GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime); + + // Now create mapping object + hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if(hMap != NULL) + { + // Map the entire view into memory + // Note that this operation will fail if the file can't fit + // into usermode address space + pStream->Base.Map.pbFile = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); + if(pStream->Base.Map.pbFile != NULL) + { + pStream->Base.Map.FileSize = FileSize.QuadPart; + pStream->Base.Map.FilePos = 0; + bResult = true; + } - SetFilePointer(pStream->hFile, ByteOffsetLo, &ByteOffsetHi, FILE_BEGIN); - pStream->RawFilePos = *pByteOffset; + // Close the map handle + CloseHandle(hMap); + } } - // Read the data - if(!WriteFile(pStream->hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, NULL)) - return false; + // Close the file handle + CloseHandle(hFile); } + + // If the file is not there and is not available for random access, + // report error + if(bResult == false) + return false; #endif -#ifdef PLATFORM_MAC - { - ByteCount nBytesToWrite = (ByteCount)dwBytesToWrite; - ByteCount nBytesWritten = 0; - OSErr theErr; +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) + struct stat fileinfo; + intptr_t handle; + bool bResult = false; - // If the byte offset is different from the current file position, - // we have to update the file position - if(*pByteOffset != pStream->RawFilePos) + // Open the file + handle = open(szFileName, O_RDONLY); + if(handle != -1) + { + // Get the file size + if(fstat(handle, &fileinfo) != -1) { - FSSetForkPosition((short)(long)pStream->hFile, fsFromStart, (SInt64)(*pByteOffset)); - pStream->RawFilePos = *pByteOffset; + pStream->Base.Map.pbFile = (LPBYTE)mmap(NULL, (size_t)fileinfo.st_size, PROT_READ, MAP_PRIVATE, handle, 0); + if(pStream->Base.Map.pbFile != NULL) + { + // time_t is number of seconds since 1.1.1970, UTC. + // 1 second = 10000000 (decimal) in FILETIME + // Set the start to 1.1.1970 00:00:00 + pStream->Base.Map.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime); + pStream->Base.Map.FileSize = (ULONGLONG)fileinfo.st_size; + pStream->Base.Map.FilePos = 0; + bResult = true; + } } + close(handle); + } - theErr = FSWriteFork((short)(long)pStream->hFile, fsAtMark, 0, nBytesToWrite, pvBuffer, &nBytesWritten); - if (theErr != noErr) - { - nLastError = theErr; - return false; - } - dwBytesWritten = (DWORD)nBytesWritten; + // Did the mapping fail? + if(bResult == false) + { + nLastError = errno; + return false; } #endif -#ifdef PLATFORM_LINUX + // Fill-in entry points + pStream->BaseRead = BaseMap_Read; + pStream->BaseGetPos = BaseMap_GetPos; + pStream->BaseGetSize = BaseMap_GetSize; + pStream->BaseGetTime = BaseMap_GetTime; + pStream->BaseClose = BaseMap_Close; + pStream->dwFlags = dwStreamFlags; + return true; +} + +//----------------------------------------------------------------------------- +// Local functions - base HTTP file support + +static const TCHAR * BaseHttp_ExtractServerName(const TCHAR * szFileName, TCHAR * szServerName) +{ + // Check for HTTP + if(!_tcsnicmp(szFileName, _T("http://"), 7)) + szFileName += 7; + + // Cut off the server name + if(szServerName != NULL) { - ssize_t bytes_written; + while(szFileName[0] != 0 && szFileName[0] != _T('/')) + *szServerName++ = *szFileName++; + *szServerName = 0; + } + else + { + while(szFileName[0] != 0 && szFileName[0] != _T('/')) + *szFileName++; + } - // If the byte offset is different from the current file position, - // we have to update the file position - if(*pByteOffset != pStream->RawFilePos) - { - lseek64((intptr_t)pStream->hFile, (off64_t)(*pByteOffset), SEEK_SET); - pStream->RawFilePos = *pByteOffset; - } + // Return the remainder + return szFileName; +} - // Perform the read operation - bytes_written = write((intptr_t)pStream->hFile, pvBuffer, (size_t)dwBytesToWrite); - if(bytes_written == -1) +static bool BaseHttp_Read( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file +{ +#ifdef PLATFORM_WINDOWS + ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos; + DWORD dwTotalBytesRead = 0; + + // Do we have to read anything at all? + if(dwBytesToRead != 0) + { + HINTERNET hRequest; + LPCTSTR szFileName; + LPBYTE pbBuffer = (LPBYTE)pvBuffer; + TCHAR szRangeRequest[0x80]; + DWORD dwStartOffset = (DWORD)ByteOffset; + DWORD dwEndOffset = dwStartOffset + dwBytesToRead; + BYTE Buffer[0x200]; + + // Open HTTP request to the file + szFileName = BaseHttp_ExtractServerName(pStream->szFileName, NULL); + hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); + if(hRequest != NULL) { - nLastError = errno; - return false; - } + // Add range request to the HTTP headers + // http://www.clevercomponents.com/articles/article015/resuming.asp + _stprintf(szRangeRequest, _T("Range: bytes=%d-%d"), dwStartOffset, dwEndOffset); + HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW); - dwBytesWritten = (DWORD)(size_t)bytes_written; + // Send the request to the server + if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) + { + while(dwTotalBytesRead < dwBytesToRead) + { + DWORD dwBlockBytesToRead = dwBytesToRead - dwTotalBytesRead; + DWORD dwBlockBytesRead = 0; + + // Read the block from the file + if(dwBlockBytesToRead > sizeof(Buffer)) + dwBlockBytesToRead = sizeof(Buffer); + InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead); + + // Check for end + if(dwBlockBytesRead == 0) + break; + + // Move buffers + dwTotalBytesRead += dwBlockBytesRead; + pbBuffer += dwBlockBytesRead; + } + } + InternetCloseHandle(hRequest); + } } -#endif // Increment the current file position by number of bytes read - pStream->RawFilePos = *pByteOffset + dwBytesWritten; - if(dwBytesWritten != dwBytesToWrite) - SetLastError(ERROR_DISK_FULL); - return (dwBytesWritten == dwBytesToWrite); + pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead; + + // If the number of bytes read doesn't match the required amount, return false + if(dwTotalBytesRead != dwBytesToRead) + SetLastError(ERROR_HANDLE_EOF); + return (dwTotalBytesRead == dwBytesToRead); + +#else + + // Not supported + pStream = pStream; + pByteOffset = pByteOffset; + pvBuffer = pvBuffer; + dwBytesToRead = dwBytesToRead; + SetLastError(ERROR_NOT_SUPPORTED); + return false; + +#endif +} + +static bool BaseHttp_GetPos( + TFileStream * pStream, // Pointer to an open stream + ULONGLONG & ByteOffset) // Pointer to file byte offset +{ + ByteOffset = pStream->Base.Http.FilePos; + return true; } -static bool File_GetSize( +static bool BaseHttp_GetSize( TFileStream * pStream, // Pointer to an open stream ULONGLONG & FileSize) // Pointer where to store file size { -#ifdef PLATFORM_WINDOWS - DWORD FileSizeHi = 0; - DWORD FileSizeLo; - - FileSizeLo = GetFileSize(pStream->hFile, &FileSizeHi); - if(FileSizeLo == INVALID_FILE_SIZE && GetLastError() != ERROR_SUCCESS) - return false; + FileSize = pStream->Base.Http.FileSize; + return true; +} - FileSize = MAKE_OFFSET64(FileSizeHi, FileSizeLo); +static bool BaseHttp_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) +{ + *pFileTime = pStream->Base.Http.FileTime; return true; +} + +static void BaseHttp_Close(TFileStream * pStream) +{ +#ifdef PLATFORM_WINDOWS + if(pStream->Base.Http.hConnect != NULL) + InternetCloseHandle(pStream->Base.Http.hConnect); + pStream->Base.Http.hConnect = NULL; + + if(pStream->Base.Http.hInternet != NULL) + InternetCloseHandle(pStream->Base.Http.hInternet); + pStream->Base.Http.hInternet = NULL; +#else + pStream = pStream; #endif +} + +static bool BaseHttp_Open( + TFileStream * pStream, + const TCHAR * szFileName, + DWORD dwStreamFlags) +{ +#ifdef PLATFORM_WINDOWS + + HINTERNET hRequest; + DWORD dwTemp = 0; + bool bFileAvailable = false; + int nError = ERROR_SUCCESS; -#ifdef PLATFORM_MAC - SInt64 fileLength = 0; - OSErr theErr; + // Don't connect to the internet + if(!InternetGetConnectedState(&dwTemp, 0)) + nError = GetLastError(); - theErr = FSGetForkSize((short)(long)pStream->hFile, &fileLength); - if(theErr != noErr) + // Initiate the connection to the internet + if(nError == ERROR_SUCCESS) { - nLastError = theErr; - return false; + pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0); + if(pStream->Base.Http.hInternet == NULL) + nError = GetLastError(); } - FileSize = (ULONGLONG)fileLength; - return true; -#endif + // Connect to the server + if(nError == ERROR_SUCCESS) + { + TCHAR szServerName[MAX_PATH]; + DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; + + // Initiate connection with the server + szFileName = BaseHttp_ExtractServerName(szFileName, szServerName); + pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, + szServerName, + INTERNET_DEFAULT_HTTP_PORT, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + dwFlags, + 0); + if(pStream->Base.Http.hConnect == NULL) + nError = GetLastError(); + } -#ifdef PLATFORM_LINUX - struct stat64 fileinfo; + // Now try to query the file size + if(nError == ERROR_SUCCESS) + { + // Open HTTP request to the file + hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); + if(hRequest != NULL) + { + if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) + { + ULONGLONG FileTime = 0; + DWORD dwFileSize = 0; + DWORD dwDataSize; + DWORD dwIndex = 0; + + // Check if the MPQ has Last Modified field + dwDataSize = sizeof(ULONGLONG); + if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) + pStream->Base.Http.FileTime = FileTime; + + // Verify if the server supports random access + dwDataSize = sizeof(DWORD); + if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) + { + if(dwFileSize != 0) + { + pStream->Base.Http.FileSize = dwFileSize; + pStream->Base.Http.FilePos = 0; + bFileAvailable = true; + } + } + } + InternetCloseHandle(hRequest); + } + } - if(fstat64((intptr_t)pStream->hFile, &fileinfo) == -1) + // If the file is not there and is not available for random access, + // report error + if(bFileAvailable == false) { - nLastError = errno; + BaseHttp_Close(pStream); return false; } - FileSize = (ULONGLONG)fileinfo.st_size; + // Fill-in entry points + pStream->BaseRead = BaseHttp_Read; + pStream->BaseGetPos = BaseHttp_GetPos; + pStream->BaseGetSize = BaseHttp_GetSize; + pStream->BaseGetTime = BaseHttp_GetTime; + pStream->BaseClose = BaseHttp_Close; + pStream->dwFlags = dwStreamFlags; return true; + +#else + + // Not supported + pStream = pStream; + szFileName = szFileName; + SetLastError(ERROR_NOT_SUPPORTED); + return false; + #endif } -/** - * \a pStream Pointer to an open stream - * \a NewFileSize New size of the file - */ -static bool File_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) +//----------------------------------------------------------------------------- +// Local functions - linear stream support + +static bool LinearStream_Read( + TLinearStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead) // Number of bytes to read from the file { -#ifdef PLATFORM_WINDOWS + ULONGLONG ByteOffset; + ULONGLONG EndOffset; + LPBYTE pbBitmap; + DWORD BlockIndex; + DWORD ByteIndex; + DWORD BitMask; + + // At this point, we must have a bitmap set + assert(pStream->pBitmap != NULL); + + // If we have data map, we must check if the data block is present in the MPQ + if(dwBytesToRead != 0) { - LONG FileSizeHi = (LONG)(NewFileSize >> 32); - LONG FileSizeLo = (LONG)(NewFileSize); - DWORD dwNewPos; - bool bResult; + DWORD BlockSize = pStream->pBitmap->BlockSize; - // Set the position at the new file size - dwNewPos = SetFilePointer(pStream->hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN); - if(dwNewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS) - return false; + // Get the offset where we read it from + if(pByteOffset == NULL) + pStream->BaseGetPos(pStream, ByteOffset); + else + ByteOffset = *pByteOffset; + EndOffset = ByteOffset + dwBytesToRead; - // Set the current file pointer as the end of the file - bResult = (bool)SetEndOfFile(pStream->hFile); + // If the start of the area is within the region + // protected by data map, check each block + if(ByteOffset < pStream->pBitmap->EndOffset) + { + // Cut the end of the stream protected by the data map + EndOffset = STORMLIB_MIN(EndOffset, pStream->pBitmap->EndOffset); - // Restore the file position - FileSizeHi = (LONG)(pStream->RawFilePos >> 32); - FileSizeLo = (LONG)(pStream->RawFilePos); - SetFilePointer(pStream->hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN); - return bResult; - } -#endif - -#ifdef PLATFORM_MAC - { - OSErr theErr; + // Calculate the initial block index + BlockIndex = (DWORD)(ByteOffset / BlockSize); + pbBitmap = (LPBYTE)(pStream->pBitmap + 1); - theErr = FSSetForkSize((short)(long)pStream->hFile, fsFromStart, (SInt64)NewFileSize); - if(theErr != noErr) - { - nLastError = theErr; - return false; + // Parse each block + while(ByteOffset < EndOffset) + { + // Prepare byte index and bit mask + ByteIndex = BlockIndex / 8; + BitMask = 1 << (BlockIndex & 0x07); + + // If that bit is not set, it means that the block is not present + if((pbBitmap[ByteIndex] & BitMask) == 0) + { + SetLastError(ERROR_FILE_CORRUPT); + return false; + } + + // Move to tne next block + ByteOffset += BlockSize; + BlockIndex++; + } } - - return true; } -#endif -#ifdef PLATFORM_LINUX + // Now if all tests passed, we can call the base read function + return pStream->BaseRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); +} + +static bool LinearStream_Switch(TLinearStream * pStream, TLinearStream * pNewStream) +{ + // Sanity checks + assert((pNewStream->dwFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_LINEAR); + assert((pNewStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_FILE); + assert((pStream->dwFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_LINEAR); + assert((pStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_FILE); + + // Close the new stream + pNewStream->BaseClose(pNewStream); + + // Close the source stream + pStream->BaseClose(pStream); + + // Rename the new data source file to the existing file + if(!BaseFile_Switch(pStream, pNewStream)) + return false; + + // Now we have to open the "pStream" again + if(!BaseFile_Open(pStream, pStream->szFileName, pNewStream->dwFlags)) + return false; + + // We need to cleanup the new data stream + FileStream_Close(pNewStream); + return true; +} + +static bool LinearStream_GetBitmap( + TLinearStream * pStream, + TFileBitmap * pBitmap, + DWORD Length, + LPDWORD LengthNeeded) +{ + DWORD TotalLength; + bool bResult = false; + + // Assumed that we have bitmap now + assert(pStream->pBitmap != NULL); + + // Give the bitmap length + TotalLength = sizeof(TFileBitmap) + pStream->pBitmap->BitmapSize; + if(LengthNeeded != NULL) + *LengthNeeded = TotalLength; + + // Do we have enough space to fill at least the bitmap structure? + if(Length >= sizeof(TFileBitmap)) { - if(ftruncate((intptr_t)pStream->hFile, (off_t)NewFileSize) == -1) + // Enough space for complete bitmap? + if(Length >= TotalLength) { - nLastError = errno; - return false; + memcpy(pBitmap, pStream->pBitmap, TotalLength); + bResult = true; + } + else + { + memcpy(pBitmap, pStream->pBitmap, sizeof(TFileBitmap)); + bResult = true; } - - return true; } -#endif + + return bResult; } -//----------------------------------------------------------------------------- -// Stream functions - partial normal file stream +static void LinearStream_Close(TLinearStream * pStream) +{ + // Free the data map, if any + if(pStream->pBitmap != NULL) + STORM_FREE(pStream->pBitmap); + pStream->pBitmap = NULL; + + // Call the base class for closing the stream + return pStream->BaseClose(pStream); +} -/** - * \a pStream Pointer to an open stream - * \a ByteOffset File byte offset - */ -static bool PartFile_GetPos(TPartFileStream * pStream, ULONGLONG & ByteOffset) +static bool LinearStream_Open(TLinearStream * pStream) { - ByteOffset = pStream->VirtualPos; + // No extra work here really; just set entry points + pStream->StreamRead = pStream->BaseRead; + pStream->StreamWrite = pStream->BaseWrite; + pStream->StreamGetPos = pStream->BaseGetPos; + pStream->StreamGetSize = pStream->BaseGetSize; + pStream->StreamSetSize = pStream->BaseSetSize; + pStream->StreamGetTime = pStream->BaseGetTime; + pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap; + pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch; + pStream->StreamClose = (STREAM_CLOSE)LinearStream_Close; return true; } -/** - * \a pStream Pointer to an open stream - * \a pByteOffset Pointer to file byte offset. If NULL, reads from the current position - * \a pvBuffer Pointer to data to be read - * \a dwBytesToRead Number of bytes to read from the file - */ -static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead) +//----------------------------------------------------------------------------- +// Local functions - partial stream support + +static bool IsPartHeader(PPART_FILE_HEADER pPartHdr) +{ + // Version number must be 2 + if(pPartHdr->PartialVersion == 2) + { + // GameBuildNumber must be an ASCII number + if(isdigit(pPartHdr->GameBuildNumber[0]) && isdigit(pPartHdr->GameBuildNumber[1]) && isdigit(pPartHdr->GameBuildNumber[2])) + { + // Block size must be power of 2 + if((pPartHdr->BlockSize & (pPartHdr->BlockSize - 1)) == 0) + return true; + } + } + + return false; +} + +static bool PartialStream_Read( + TPartialStream * pStream, + ULONGLONG * pByteOffset, + void * pvBuffer, + DWORD dwBytesToRead) { ULONGLONG RawByteOffset; LPBYTE pbBuffer = (LPBYTE)pvBuffer; @@ -789,7 +1176,7 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo // If the part is not present in the file, we fail the read if((PartMap->Flags & 3) == 0) { - nFailReason = ERROR_CAN_NOT_COMPLETE; + nFailReason = ERROR_FILE_CORRUPT; bResult = false; break; } @@ -805,7 +1192,7 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo RawByteOffset = MAKE_OFFSET64(PartMap->BlockOffsHi, PartMap->BlockOffsLo); if(RawByteOffset == 0) { - nFailReason = ERROR_CAN_NOT_COMPLETE; + nFailReason = ERROR_FILE_CORRUPT; bResult = false; break; } @@ -816,9 +1203,9 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo // Append the offset within the part RawByteOffset += dwPartOffset; - if(!File_Read(pStream, &RawByteOffset, pbBuffer, dwBytesInPart)) + if(!pStream->BaseRead(pStream, &RawByteOffset, pbBuffer, dwBytesInPart)) { - nFailReason = ERROR_CAN_NOT_COMPLETE; + nFailReason = ERROR_FILE_CORRUPT; bResult = false; break; } @@ -840,69 +1227,237 @@ static bool PartFile_Read(TPartFileStream * pStream, ULONGLONG * pByteOffset, vo return (dwBytesRead == dwBytesToRead); } -static bool PartFile_Write( - TPartFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position - const void * pvBuffer, // Pointer to data to be read - DWORD dwBytesToRead) // Number of bytes to read from the file +static bool PartialStream_GetPos( + TPartialStream * pStream, + ULONGLONG & ByteOffset) { - // Keep compiler happy - dwBytesToRead = dwBytesToRead; - pByteOffset = pByteOffset; - pvBuffer = pvBuffer; - pStream = pStream; - - // Not allowed - return false; + ByteOffset = pStream->VirtualPos; + return true; } -static bool PartFile_GetSize( - TPartFileStream * pStream, // Pointer to an open stream +static bool PartialStream_GetSize( + TPartialStream * pStream, // Pointer to an open stream ULONGLONG & FileSize) // Pointer where to store file size { FileSize = pStream->VirtualSize; return true; } -static bool PartFile_SetSize( - TPartFileStream * pStream, // Pointer to an open stream - ULONGLONG NewSize) // new size of the file +static bool PartialStream_GetBitmap( + TPartialStream * pStream, + TFileBitmap * pBitmap, + DWORD Length, + LPDWORD LengthNeeded) { - // Keep compiler happy - pStream = pStream; - NewSize = NewSize; + LPBYTE pbBitmap; + DWORD TotalLength; + DWORD BitmapSize = 0; + DWORD ByteOffset; + DWORD BitMask; + bool bResult = false; - // Not allowed - return false; + // Do we have stream bitmap? + BitmapSize = ((pStream->BlockCount - 1) / 8) + 1; + + // Give the bitmap length + TotalLength = sizeof(TFileBitmap) + BitmapSize; + if(LengthNeeded != NULL) + *LengthNeeded = TotalLength; + + // Do we have enough to fill at least the header? + if(Length >= sizeof(TFileBitmap)) + { + // Fill the bitmap header + pBitmap->StartOffset = 0; + pBitmap->EndOffset = pStream->VirtualSize; + pBitmap->IsComplete = 1; + pBitmap->BitmapSize = BitmapSize; + pBitmap->BlockSize = pStream->BlockSize; + pBitmap->Reserved = 0; + + // Is there at least one incomplete block? + for(DWORD i = 0; i < pStream->BlockCount; i++) + { + if(pStream->PartMap[i].Flags != 3) + { + pBitmap->IsComplete = 0; + break; + } + } + + bResult = true; + } + + // Do we have enough space for supplying the bitmap? + if(Length >= TotalLength) + { + // Fill the file bitmap + pbBitmap = (LPBYTE)(pBitmap + 1); + for(DWORD i = 0; i < pStream->BlockCount; i++) + { + // Is the block there? + if(pStream->PartMap[i].Flags == 3) + { + ByteOffset = i / 8; + BitMask = 1 << (i & 7); + pbBitmap[ByteOffset] |= BitMask; + } + } + bResult = true; + } + + return bResult; } -/* - * Stream functions - encrypted stream - * - * Note: In original Starcraft II Installer.exe: Suffix derived from battle.net auth. code - * Address of decryption routine: 0053A3D0 http://us.battle.net/static/mediakey/sc2-authenticationcode-enUS.txt - * Pointer to decryptor object: ECX Numbers mean offset of 4-char group of auth code - * Pointer to key: ECX+0x5C -0C- -1C--08- -18--04- -14--00- -10- - */ -static const char * MpqeKey_Starcraft2_Install_enUS = "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG"; -static const char * MpqeKey_Starcraft2_Install_enGB = "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D"; -static const char * MpqeKey_Starcraft2_Install_deDE = "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V"; -static const char * MpqeKey_Starcraft2_Install_esES = "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P"; -static const char * MpqeKey_Starcraft2_Install_frFR = "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76"; -static const char * MpqeKey_Starcraft2_Install_itIT = "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B"; -static const char * MpqeKey_Starcraft2_Install_plPL = "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3"; -static const char * MpqeKey_Starcraft2_Install_ruRU = "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V"; +static void PartialStream_Close(TPartialStream * pStream) +{ + // Free the part map + if(pStream->PartMap != NULL) + STORM_FREE(pStream->PartMap); + pStream->PartMap = NULL; + + // Clear variables + pStream->VirtualSize = 0; + pStream->VirtualPos = 0; + + // Close the base stream + assert(pStream->BaseClose != NULL); + pStream->BaseClose(pStream); +} + +static bool PartialStream_Open(TPartialStream * pStream) +{ + PART_FILE_HEADER PartHdr; + ULONGLONG VirtualSize; // Size of the file stored in part file + ULONGLONG ByteOffset = {0}; + DWORD BlockCount; + + // Sanity check + assert(pStream->BaseRead != NULL); + // Attempt to read PART file header + if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER))) + { + // We need to swap PART file header on big-endian platforms + BSWAP_PART_HEADER(&PartHdr); + + // Verify the PART file header + if(IsPartHeader(&PartHdr)) + { + // Calculate the number of parts in the file + VirtualSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo); + assert(VirtualSize != 0); + BlockCount = (DWORD)((VirtualSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize); + + // Allocate the map entry array + pStream->PartMap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount); + if(pStream->PartMap != NULL) + { + // Load the block map + if(pStream->BaseRead(pStream, NULL, pStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY))) + { + // Swap the array of file map entries + BSWAP_ARRAY32_UNSIGNED(pStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY)); + + // Fill the members of PART file stream + pStream->VirtualSize = ((ULONGLONG)PartHdr.FileSizeHi) + PartHdr.FileSizeLo; + pStream->VirtualPos = 0; + pStream->BlockCount = BlockCount; + pStream->BlockSize = PartHdr.BlockSize; + + // Set new function pointers + pStream->StreamRead = (STREAM_READ)PartialStream_Read; + pStream->StreamGetPos = (STREAM_GETPOS)PartialStream_GetPos; + pStream->StreamGetSize = (STREAM_GETSIZE)PartialStream_GetSize; + pStream->StreamGetTime = pStream->BaseGetTime; + pStream->StreamGetTime = pStream->BaseGetTime; + pStream->StreamGetBmp = (STREAM_GETBMP)PartialStream_GetBitmap; + pStream->StreamClose = (STREAM_CLOSE)PartialStream_Close; + return true; + } + + // Free the part map + STORM_FREE(pStream->PartMap); + pStream->PartMap = NULL; + } + } + } + + SetLastError(ERROR_BAD_FORMAT); + return false; +} + +//----------------------------------------------------------------------------- +// Local functions - encrypted stream support + +// Note: Starcraft II - Installer.exe (4.1.1.4219): Suffix derived from battle.net auth. code +// Address of decryption routine: 0053A3D0 http://us.battle.net/static/mediakey/sc2-authenticationcode-enUS.txt +// Pointer to decryptor object: ECX Numbers mean offset of 4-char group of auth code (follows in comment) +// Pointer to key: ECX+0x5C -0C- -1C--08- -18--04- -14--00- -10- +static const char * MpqeKey_Starcraft2_Install_deDE = "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V"; // Y45MD3CAK4KXSSXHYD9VY64Z8EKJ4XFX +static const char * MpqeKey_Starcraft2_Install_enGB = "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D"; // G8MN8UDG6NA2ANGY6A3DNY82HRGF29ZH +static const char * MpqeKey_Starcraft2_Install_enSG = "expand 32-byte kWW5B0000F7HWFDU90000FWZSHLB20000BLRSW9RR00003ECE"; // W9RRHLB2FDU9WW5B3ECEBLRSFWZSF7HW +static const char * MpqeKey_Starcraft2_Install_enUS = "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG"; // 3DH5RE5NVM5GTFD85LXGWT6FK859ETR5 +static const char * MpqeKey_Starcraft2_Install_esES = "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P"; // 8WLKUAXE94PFQU4Y249PAZ24N4R4XKTQ +static const char * MpqeKey_Starcraft2_Install_esMX = "expand 32-byte kSQBR00004G54HGGX0000MF9GXX3V0000FFDXA34D0000FE5U"; // A34DXX3VHGGXSQBRFE5UFFDXMF9G4G54 +static const char * MpqeKey_Starcraft2_Install_frFR = "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76"; // ZG7J9K938HJEFWPQUA768MA2PFER6EAJ +static const char * MpqeKey_Starcraft2_Install_itIT = "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B"; // NE7CUNNNTVAPXV7E3G2BSVBWGVMW8BL2 +static const char * MpqeKey_Starcraft2_Install_koKR = "expand 32-byte kQWK70000838FBM9Q0000WQDB2FTM0000MWAZ3V9E0000U6MA"; // 3V9E2FTMBM9QQWK7U6MAMWAZWQDB838F +static const char * MpqeKey_Starcraft2_Install_plPL = "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3"; // 2NSFB8MELULJ83U6YHA3UP6K4MQD48L6 +static const char * MpqeKey_Starcraft2_Install_ptBR = "expand 32-byte kU8BM0000SW4EZ4CU00005F9CZ9EW0000CTY6QA2T0000B5WX"; // QA2TZ9EWZ4CUU8BMB5WXCTY65F9CSW4E +static const char * MpqeKey_Starcraft2_Install_ruRU = "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V"; // VHB378W64BAT9SH7D68VV9NLQDK9YEGT +static const char * MpqeKey_Starcraft2_Install_zhTW = "expand 32-byte k7KBN0000D9NEM6GC0000N3PLQJV400003BRDU3NF00009XQJ"; // U3NFQJV4M6GC7KBN9XQJ3BRDN3PLD9NE + +// Note: Diablo III: Agent.exe (1.0.0.954): Suffix derived from battle.net auth. code +// Address of decryption routine: 00502b00 http://dist.blizzard.com/mediakey/d3-authenticationcode-enGB.txt +// Pointer to decryptor object: ECX Numbers mean offset of 4-char group of auth code (follows in comment) +// Pointer to key: ECX+0x5C -0C- -1C--08- -18--04- -14--00- -10- +static const char * MpqeKey_Diablo3_Install_deDE = "expand 32-byte kEFH40000QRZKY3520000XC9MF6EJ0000CFH2UCMX0000XFRX"; // UCMXF6EJY352EFH4XFRXCFH2XC9MQRZK +static const char * MpqeKey_Diablo3_Install_enGB = "expand 32-byte kXP4G0000PHBPRP7W0000J9UNHY4800007SL9MMKV0000HYBQ"; // MMKVHY48RP7WXP4GHYBQ7SL9J9UNPHBP +static const char * MpqeKey_Diablo3_Install_enSG = "expand 32-byte kTZ9M00003CPPVGGL0000JYETWHQ70000FDCL8MXL0000QZQS"; // 8MXLWHQ7VGGLTZ9MQZQSFDCLJYET3CPP +static const char * MpqeKey_Diablo3_Install_enUS = "expand 32-byte kGUNG0000WZSZXFE20000UAKP5TM60000HKQ9EJ2R00005QDG"; // EJ2R5TM6XFE2GUNG5QDGHKQ9UAKPWZSZ +static const char * MpqeKey_Diablo3_Install_esES = "expand 32-byte kK65U0000HQQTZ6LN0000CLP4BE420000WZVMPBGF0000GJQ3"; // PBGFBE42Z6LNK65UGJQ3WZVMCLP4HQQT +static const char * MpqeKey_Diablo3_Install_esMX = "expand 32-byte kW5P200008VU2TSGC0000JPEYJJS90000C47AX7SE00008EBS"; // X7SEJJS9TSGCW5P28EBSC47AJPEY8VU2 +static const char * MpqeKey_Diablo3_Install_frFR = "expand 32-byte kRY3D00007YA2YE6X0000XS4PQA8V0000ZDE45KVB0000LGC5"; // 5KVBQA8VYE6XRY3DLGC5ZDE4XS4P7YA2 +static const char * MpqeKey_Diablo3_Install_itIT = "expand 32-byte kVVY40000B2546EVN00005B8KD2K50000DWYT478J0000XX8T"; // 478JD2K56EVNVVY4XX8TDWYT5B8KB254 +static const char * MpqeKey_Diablo3_Install_koKR = "expand 32-byte k6YWH0000474ARZTN0000NVWDVNFQ0000VDH98TS40000E9CH"; // 8TS4VNFQRZTN6YWHE9CHVDH9NVWD474A +static const char * MpqeKey_Diablo3_Install_plPL = "expand 32-byte k4ZJJ0000BLJBF4LZ0000A6GAZ32D00003AZQLJ520000XVKK"; // LJ52Z32DF4LZ4ZJJXVKK3AZQA6GABLJB +static const char * MpqeKey_Diablo3_Install_ptBR = "expand 32-byte k545Y0000XYAGCUE20000WHE7HY2E0000JPVYK6BD0000KNLB"; // K6BDHY2ECUE2545YKNLBJPVYWHE7XYAG +static const char * MpqeKey_Diablo3_Install_ruRU = "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // +static const char * MpqeKey_Diablo3_Install_zhTW = "expand 32-byte kMRUC0000AA8HV3ZZ0000UX2TQTN80000A8CG6VWC0000ZXV8"; // 6VWCQTN8V3ZZMRUCZXV8A8CGUX2TAA8H +static const char * MpqeKey_Diablo3_Install_zhCN = "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // + static const char * MpqKeyArray[] = { - MpqeKey_Starcraft2_Install_enUS, - MpqeKey_Starcraft2_Install_enGB, MpqeKey_Starcraft2_Install_deDE, + MpqeKey_Starcraft2_Install_enGB, + MpqeKey_Starcraft2_Install_enSG, + MpqeKey_Starcraft2_Install_enUS, MpqeKey_Starcraft2_Install_esES, + MpqeKey_Starcraft2_Install_esMX, MpqeKey_Starcraft2_Install_frFR, MpqeKey_Starcraft2_Install_itIT, + MpqeKey_Starcraft2_Install_koKR, MpqeKey_Starcraft2_Install_plPL, + MpqeKey_Starcraft2_Install_ptBR, MpqeKey_Starcraft2_Install_ruRU, + MpqeKey_Starcraft2_Install_zhTW, + + MpqeKey_Diablo3_Install_deDE, + MpqeKey_Diablo3_Install_enGB, + MpqeKey_Diablo3_Install_enSG, + MpqeKey_Diablo3_Install_enUS, + MpqeKey_Diablo3_Install_esES, + MpqeKey_Diablo3_Install_esMX, + MpqeKey_Diablo3_Install_frFR, + MpqeKey_Diablo3_Install_itIT, + MpqeKey_Diablo3_Install_koKR, + MpqeKey_Diablo3_Install_plPL, + MpqeKey_Diablo3_Install_ptBR, +// MpqeKey_Diablo3_Install_ruRU, + MpqeKey_Diablo3_Install_zhTW, +// MpqeKey_Diablo3_Install_zhCN, + NULL }; @@ -1026,40 +1581,35 @@ static void DecryptFileChunk( } } - -static bool DetectFileKey(TEncryptedStream * pStream) +static const char * DetectFileKey(LPBYTE pbEncryptedHeader) { ULONGLONG ByteOffset = 0; - BYTE EncryptedHeader[MPQE_CHUNK_SIZE]; BYTE FileHeader[MPQE_CHUNK_SIZE]; - - // Load the chunk from the file - if(!FileStream_Read(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader))) - return false; + BYTE Key[MPQE_CHUNK_SIZE]; // We just try all known keys one by one for(int i = 0; MpqKeyArray[i] != NULL; i++) { // Copy the key there - memcpy(pStream->Key, MpqKeyArray[i], MPQE_CHUNK_SIZE); - BSWAP_ARRAY32_UNSIGNED(pStream->Key, MPQE_CHUNK_SIZE); + memcpy(Key, MpqKeyArray[i], MPQE_CHUNK_SIZE); + BSWAP_ARRAY32_UNSIGNED(Key, MPQE_CHUNK_SIZE); // Try to decrypt with the given key - memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE); - DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE); + memcpy(FileHeader, pbEncryptedHeader, MPQE_CHUNK_SIZE); + DecryptFileChunk((LPDWORD)FileHeader, Key, ByteOffset, MPQE_CHUNK_SIZE); - // We check the decrypoted data + // We check the decrypted data // All known encrypted MPQs have header at the begin of the file, // so we check for MPQ signature there. if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q') - return true; + return MpqKeyArray[i]; } // Key not found, sorry - return false; + return NULL; } -static bool EncryptedFile_Read( +static bool EncryptedStream_Read( TEncryptedStream * pStream, // Pointer to an open stream ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position void * pvBuffer, // Pointer to data to be read @@ -1075,10 +1625,10 @@ static bool EncryptedFile_Read( bool bResult = false; // Get the byte offset - if(pByteOffset != NULL) - ByteOffset = *pByteOffset; + if(pByteOffset == NULL) + pStream->BaseGetPos(pStream, ByteOffset); else - ByteOffset = pStream->RawFilePos; + ByteOffset = *pByteOffset; // Cut it down to MPQE chunk size StartOffset = ByteOffset; @@ -1097,7 +1647,7 @@ static bool EncryptedFile_Read( dwOffsetInCache = (DWORD)(ByteOffset - StartOffset); // Read the file from the stream as-is - if(File_Read(pStream, &StartOffset, pbMpqData, dwBytesToDecrypt)) + if(pStream->BaseRead(pStream, &StartOffset, pbMpqData, dwBytesToDecrypt)) { // Decrypt the data DecryptFileChunk((LPDWORD)pbMpqData, pStream->Key, StartOffset, dwBytesToAllocate); @@ -1119,31 +1669,42 @@ static bool EncryptedFile_Read( return bResult; } -static bool EncryptedFile_Write( - TEncryptedStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position - const void * pvBuffer, // Pointer to data to be read - DWORD dwBytesToRead) // Number of bytes to read from the file +static bool EncryptedStream_Open(TEncryptedStream * pStream) { - // Keep compiler happy - dwBytesToRead = dwBytesToRead; - pByteOffset = pByteOffset; - pvBuffer = pvBuffer; - pStream = pStream; + ULONGLONG ByteOffset = 0; + BYTE EncryptedHeader[MPQE_CHUNK_SIZE]; + const char * szKey; - // Not allowed - return false; -} + // Sanity check + assert(pStream->BaseRead != NULL); -static bool EncryptedFile_SetSize( - TEncryptedStream * pStream, // Pointer to an open stream - ULONGLONG NewSize) // new size of the file -{ - // Keep compiler happy - pStream = pStream; - NewSize = NewSize; + // Load one MPQE chunk and try to detect the file key + if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader))) + { + // Attempt to decrypt the MPQ header with all known keys + szKey = DetectFileKey(EncryptedHeader); + if(szKey != NULL) + { + // Copy the key for the file + memcpy(pStream->Key, szKey, MPQE_CHUNK_SIZE); + BSWAP_ARRAY32_UNSIGNED(pStream->Key, MPQE_CHUNK_SIZE); + + // Assign functions + pStream->StreamRead = (STREAM_READ)EncryptedStream_Read; + pStream->StreamGetPos = pStream->BaseGetPos; + pStream->StreamGetSize = pStream->BaseGetSize; + pStream->StreamGetTime = pStream->BaseGetTime; + pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap; + pStream->StreamClose = pStream->BaseClose; + + // We need to reset the position back to the begin of the file + pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, 0); + return true; + } - // Not allowed + // An unknown key + SetLastError(ERROR_UNKNOWN_FILE_KEY); + } return false; } @@ -1151,7 +1712,7 @@ static bool EncryptedFile_SetSize( // Public functions /** - * This function creates a new file for read or read-write access + * This function creates a new file for read-write access * * - If the current platform supports file sharing, * the file must be created for read sharing (i.e. another application @@ -1168,35 +1729,45 @@ static bool EncryptedFile_SetSize( */ TFileStream * FileStream_CreateFile( - const TCHAR * szFileName) // Name of the file to create + const TCHAR * szFileName, + DWORD dwStreamFlags) { - TFileStream * pStream = NULL; - HANDLE hFile; + TFileStream * pStream; - // Create the file - hFile = CreateNewFile(szFileName); - if(hFile != INVALID_HANDLE_VALUE) + // We only support creation of linear, local file + if((dwStreamFlags & (STREAM_PROVIDER_MASK | BASE_PROVIDER_MASK)) != (STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE)) { - // Allocate the FileStream structure and fill it - pStream = STORM_ALLOC(TFileStream, 1); - if(pStream != NULL) - { - // Reset entire structure to zero - memset(pStream, 0, sizeof(TFileStream)); - - // Save file name and set function pointers - _tcscpy(pStream->szFileName, szFileName); - pStream->StreamGetPos = File_GetPos; - pStream->StreamRead = File_Read; - pStream->StreamWrite = File_Write; - pStream->StreamGetSize = File_GetSize; - pStream->StreamSetSize = File_SetSize; - pStream->hFile = hFile; - } - else + SetLastError(ERROR_NOT_SUPPORTED); + return NULL; + } + + // Allocate file stream structure for linear stream + pStream = STORM_ALLOC(TFileStream, 1); + if(pStream != NULL) + { + // Reset entire structure to zero + memset(pStream, 0, sizeof(TFileStream)); + _tcscpy(pStream->szFileName, szFileName); + + // Attempt to create the disk file + if(BaseFile_Create(pStream, szFileName, dwStreamFlags)) { - CloseTheFile(hFile); + // Fill the stream provider functions + pStream->StreamRead = pStream->BaseRead; + pStream->StreamWrite = pStream->BaseWrite; + pStream->StreamGetPos = pStream->BaseGetPos; + pStream->StreamGetSize = pStream->BaseGetSize; + pStream->StreamSetSize = pStream->BaseSetSize; + pStream->StreamGetTime = pStream->BaseGetTime; + pStream->StreamGetBmp = (STREAM_GETBMP)Dummy_GetBitmap;; + pStream->StreamSwitch = (STREAM_SWITCH)LinearStream_Switch; + pStream->StreamClose = pStream->BaseClose; + return pStream; } + + // File create failed, delete the stream + STORM_FREE(pStream); + pStream = NULL; } // Return the stream @@ -1212,185 +1783,106 @@ TFileStream * FileStream_CreateFile( * - If the file exists but cannot be open, then function must return NULL * - The parameters of the function must be validate by the caller * - The function must check if the file is a PART file, - * and create TPartFileStream object if so. + * and create TPartialStream object if so. * - The function must initialize all stream function pointers in TFileStream * - If the function fails from any reason, it must close all handles * and free all memory that has been allocated in the process of stream creation, * including the TFileStream structure itself * * \a szFileName Name of the file to open - * \a bWriteAccess false for read only, true for read+write + * \a dwStreamFlags specifies the provider and base storage type */ -TFileStream * FileStream_OpenRawFile( - const TCHAR * szFileName, // Name of the file to create - bool bWriteAccess) // false = read-only, true = read+write +TFileStream * FileStream_OpenFile( + const TCHAR * szFileName, + DWORD dwStreamFlags) { - TFileStream * pStream; - HANDLE hFile; - - // Create the file - hFile = OpenExistingFile(szFileName, bWriteAccess); - if(hFile == INVALID_HANDLE_VALUE) - return NULL; + TFileStream * pStream = NULL; + size_t StreamSize = 0; + bool bStreamResult = false; + bool bBaseResult = false; - // Initialize the file as normal file stream - pStream = STORM_ALLOC(TFileStream, 1); - if(pStream != NULL) + // Allocate file stream for each stream provider + switch(dwStreamFlags & STREAM_PROVIDER_MASK) { - // Reset entire structure to zero - memset(pStream, 0, sizeof(TFileStream)); - - // Save file name and set function pointers - _tcscpy(pStream->szFileName, szFileName); - pStream->StreamGetPos = File_GetPos; - pStream->StreamRead = File_Read; - pStream->StreamWrite = File_Write; - pStream->StreamGetSize = File_GetSize; - pStream->StreamSetSize = File_SetSize; - if(bWriteAccess == false) - pStream->StreamFlags |= STREAM_FLAG_READ_ONLY; - pStream->hFile = hFile; - return pStream; - } + case STREAM_PROVIDER_LINEAR: // Allocate structure for linear stream + StreamSize = sizeof(TLinearStream); + break; - CloseTheFile(hFile); - return NULL; -} + case STREAM_PROVIDER_PARTIAL: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + StreamSize = sizeof(TPartialStream); + break; -/** - * Opens a file - * - * \a szFileName Name of the file to open - * \a bWriteAccess false for read only, true for read+write - */ + case STREAM_PROVIDER_ENCRYPTED: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + StreamSize = sizeof(TEncryptedStream); + break; -TFileStream * FileStream_OpenFile(const TCHAR * szFileName, bool bWriteAccess) -{ - PART_FILE_HEADER PartHdr; - ULONGLONG VirtualSize; // Size of the file stored in part file - ULONGLONG ByteOffset = {0}; - TFileStream * pStream; - size_t nStructLength; - DWORD BlockCount; + default: + return NULL; + } - // Open the file as normal stream - pStream = FileStream_OpenRawFile(szFileName, bWriteAccess); + // Allocate the stream for each type + pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize); if(pStream == NULL) return NULL; - // Attempt to read PART file header - if(FileStream_Read(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER))) - { - // We need to swap PART file header on big-endian platforms - BSWAP_PART_HEADER(&PartHdr); - - // Verify the PART file header - if(IsPartHeader(&PartHdr)) - { - TPartFileStream * pPartStream; + // Fill the stream structure with zeros + memset(pStream, 0, StreamSize); + _tcscpy(pStream->szFileName, szFileName); - // Calculate the number of parts in the file - VirtualSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo); - BlockCount = (DWORD)((VirtualSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize); + // Now initialize the respective base provider + switch(dwStreamFlags & BASE_PROVIDER_MASK) + { + case BASE_PROVIDER_FILE: + bBaseResult = BaseFile_Open(pStream, szFileName, dwStreamFlags); + break; - // Calculate the size of the entire structure - // Note that we decrement number of parts by one, - // because there already is one entry in the TPartFileStream structure - nStructLength = sizeof(TPartFileStream) + (BlockCount - 1) * sizeof(PART_FILE_MAP_ENTRY); - pPartStream = (TPartFileStream *)STORM_ALLOC(char, nStructLength); - if(pPartStream != NULL) - { - // Initialize the part file stream - memset(pPartStream, 0, nStructLength); - memcpy(pPartStream, pStream, sizeof(TFileStream)); + case BASE_PROVIDER_MAP: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + bBaseResult = BaseMap_Open(pStream, szFileName, dwStreamFlags); + break; - // Load the block map - if(!FileStream_Read(pPartStream, NULL, pPartStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY))) - { - FileStream_Close(pStream); - STORM_FREE(pPartStream); - return NULL; - } + case BASE_PROVIDER_HTTP: + dwStreamFlags |= STREAM_FLAG_READ_ONLY; + bBaseResult = BaseHttp_Open(pStream, szFileName, dwStreamFlags); + break; + } - // Swap the array of file map entries - BSWAP_ARRAY32_UNSIGNED(pPartStream->PartMap, BlockCount * sizeof(PART_FILE_MAP_ENTRY)); + // If we failed to open the base storage, fail the operation + if(bBaseResult == false) + { + STORM_FREE(pStream); + return NULL; + } - // Set new function pointers - pPartStream->StreamGetPos = (STREAM_GETPOS)PartFile_GetPos; - pPartStream->StreamRead = (STREAM_READ)PartFile_Read; - pPartStream->StreamWrite = (STREAM_WRITE)PartFile_Write; - pPartStream->StreamGetSize = (STREAM_GETSIZE)PartFile_GetSize; - pPartStream->StreamSetSize = (STREAM_SETSIZE)PartFile_SetSize; - pPartStream->StreamFlags |= (STREAM_FLAG_READ_ONLY | STREAM_FLAG_PART_FILE); + // Now initialize the stream provider + switch(dwStreamFlags & STREAM_PROVIDER_MASK) + { + case STREAM_PROVIDER_LINEAR: + bStreamResult = LinearStream_Open((TLinearStream *)pStream); + break; - // Fill the members of PART file stream - pPartStream->VirtualSize = ((ULONGLONG)PartHdr.FileSizeHi) + PartHdr.FileSizeLo; - pPartStream->VirtualPos = 0; - pPartStream->BlockCount = BlockCount; - pPartStream->BlockSize = PartHdr.BlockSize; + case STREAM_PROVIDER_PARTIAL: + bStreamResult = PartialStream_Open((TPartialStream *)pStream); + break; - STORM_FREE(pStream); - } - return pPartStream; - } + case STREAM_PROVIDER_ENCRYPTED: + bStreamResult = EncryptedStream_Open((TEncryptedStream *)pStream); + break; } - // If the file doesn't contain PART file header, - // reset the file position to begin of the file - FileStream_Read(pStream, &ByteOffset, NULL, 0); - return pStream; -} - -TFileStream * FileStream_OpenEncrypted(const TCHAR * szFileName) -{ - TEncryptedStream * pEncryptedStream; - TFileStream * pStream; - - // Open the file as raw stream - pStream = FileStream_OpenRawFile(szFileName, false); - if(pStream) + // If the operation failed, free the stream and set it to NULL + if(bStreamResult == false) { - // Allocate new stream for handling encryption - pEncryptedStream = STORM_ALLOC(TEncryptedStream, 1); - if(pEncryptedStream != NULL) - { - // Copy the file stream to the encrypted stream - memset(pEncryptedStream, 0, sizeof(TEncryptedStream)); - memcpy(pEncryptedStream, pStream, sizeof(TFileStream)); - - // Assign functions - pEncryptedStream->StreamRead = (STREAM_READ)EncryptedFile_Read; - pEncryptedStream->StreamWrite = (STREAM_WRITE)EncryptedFile_Write; - pEncryptedStream->StreamSetSize = (STREAM_SETSIZE)EncryptedFile_SetSize; - pEncryptedStream->StreamFlags |= (STREAM_FLAG_READ_ONLY | STREAM_FLAG_ENCRYPTED_FILE); - - // Get the file key - if(DetectFileKey(pEncryptedStream)) - return pEncryptedStream; - - // Close the encrypted stream - STORM_FREE(pEncryptedStream); - pEncryptedStream = NULL; - } - - FileStream_Close(pStream); + // Only close the base stream + pStream->BaseClose(pStream); + STORM_FREE(pStream); pStream = NULL; } - SetLastError(ERROR_UNKNOWN_FILE_KEY); - return NULL; -} - -/** - * This function returns the current file position - * \a pStream - * \a ByteOffset - */ -bool FileStream_GetPos(TFileStream * pStream, ULONGLONG & ByteOffset) -{ - assert(pStream->StreamGetPos != NULL); - return pStream->StreamGetPos(pStream, ByteOffset); + return pStream; } /** @@ -1432,67 +1924,22 @@ bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBu */ bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) { - if(pStream->StreamFlags & STREAM_FLAG_READ_ONLY) + if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) return false; - assert(pStream->StreamWrite != NULL); + assert(pStream->StreamWrite != NULL); return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite); } - /** - * Returns the last write time of a file - * - * \a pStream Pointer to an open stream - * \a pFileType Pointer where to store the file last write time + * This function returns the current file position + * \a pStream + * \a ByteOffset */ -bool FileStream_GetLastWriteTime(TFileStream * pStream, ULONGLONG * pFileTime) +bool FileStream_GetPos(TFileStream * pStream, ULONGLONG & ByteOffset) { -#ifdef PLATFORM_WINDOWS - FILETIME ft; - - if(!GetFileTime(pStream->hFile, NULL, NULL, &ft)) - return false; - - *pFileTime = MAKE_OFFSET64(ft.dwHighDateTime, ft.dwLowDateTime); - return true; -#endif - -#ifdef PLATFORM_MAC - OSErr theErr; - FSRef theFileRef; - FSCatalogInfo theCatInfo; - - theErr = FSGetForkCBInfo((short)(long)pStream->hFile, 0, NULL, NULL, NULL, &theFileRef, NULL); - if(theErr != noErr) - { - nLastError = theErr; - return false; - } - - theErr = FSGetCatalogInfo(&theFileRef, kFSCatInfoContentMod, &theCatInfo, NULL, NULL, NULL); - if(theErr != noErr) - { - nLastError = theErr; - return false; - } - - ConvertUTCDateTimeToFileTime(&theCatInfo.contentModDate, pFileTime); - return true; -#endif - -#ifdef PLATFORM_LINUX - struct stat file_stats; - - if(fstat((int)(size_t)pStream->hFile, &file_stats) == -1) - { - nLastError = errno; - return false; - } - - ConvertTimeTToFileTime(pFileTime, file_stats.st_mtime); - return true; -#endif + assert(pStream->StreamGetPos != NULL); + return pStream->StreamGetPos(pStream, ByteOffset); } /** @@ -1515,14 +1962,26 @@ bool FileStream_GetSize(TFileStream * pStream, ULONGLONG & FileSize) */ bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) { - if(pStream->StreamFlags & STREAM_FLAG_READ_ONLY) + if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) return false; - assert(pStream->StreamSetSize != NULL); + assert(pStream->StreamSetSize != NULL); return pStream->StreamSetSize(pStream, NewFileSize); } /** + * Returns the last write time of a file + * + * \a pStream Pointer to an open stream + * \a pFileType Pointer where to store the file last write time + */ +bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFileTime) +{ + assert(pStream->StreamGetTime != NULL); + return pStream->StreamGetTime(pStream, pFileTime); +} + +/** * Switches a stream with another. Used for final phase of archive compacting. * Performs these steps: * @@ -1533,37 +1992,86 @@ bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) * \a pStream Pointer to an open stream * \a pTempStream Temporary ("working") stream (created during archive compacting) */ -bool FileStream_MoveFile(TFileStream * pStream, TFileStream * pTempStream) +bool FileStream_Switch(TFileStream * pStream, TFileStream * pNewStream) +{ + if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) + return false; + + assert(pStream->StreamSwitch != NULL); + return pStream->StreamSwitch(pStream, pNewStream); +} + +/** + * Returns the file name of the stream + * + * \a pStream Pointer to an open stream + */ +TCHAR * FileStream_GetFileName(TFileStream * pStream) { - bool bWriteAccess; + assert(pStream != NULL); + return pStream->szFileName; +} - // Close the handle to the temporary file - CloseTheFile(pTempStream->hFile); - pTempStream->hFile = INVALID_HANDLE_VALUE; +/** + * Returns true if the stream is read-only + * + * \a pStream Pointer to an open stream + */ +bool FileStream_IsReadOnly(TFileStream * pStream) +{ + return (pStream->dwFlags & STREAM_FLAG_READ_ONLY) ? true : false; +} + +/** + * This function enabled a linear stream to include data bitmap. + * Used by MPQs v 4.0 from WoW. Each file block is represented by + * a bit in the bitmap. 1 means the block is present, 0 means it's not. + * + * \a pStream Pointer to an open stream + * \a pBitmap Pointer to file bitmap + */ - // Close the handle to the source file - CloseTheFile(pStream->hFile); - pStream->hFile = INVALID_HANDLE_VALUE; +bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap) +{ + TLinearStream * pLinearStream; - // Rename the temp file to the final file - if(!RenameFile(pTempStream->szFileName, pStream->szFileName)) + // It must be a linear stream. + if((pStream->dwFlags & STREAM_PROVIDER_MASK) != STREAM_PROVIDER_LINEAR) return false; + pLinearStream = (TLinearStream *)pStream; - // Now open the renamed file again, and store its handle to the old stream - bWriteAccess = (pStream->StreamFlags & STREAM_FLAG_READ_ONLY) ? false : true; - pStream->hFile = OpenExistingFile(pStream->szFileName, bWriteAccess); - if(pStream->hFile == INVALID_HANDLE_VALUE) + // Two bitmaps are not allowed + if(pLinearStream->pBitmap != NULL) return false; - // Delete the temporary file stream - FileStream_Close(pTempStream); + // We need to change some entry points + pLinearStream->StreamRead = (STREAM_READ)LinearStream_Read; + pLinearStream->StreamGetBmp = (STREAM_GETBMP)LinearStream_GetBitmap; - // The file position has been reset to zero by reopening the file - pStream->RawFilePos = 0; + // Using data bitmap renders the stream to be read only. + pLinearStream->dwFlags |= STREAM_FLAG_READ_ONLY; + pLinearStream->pBitmap = pBitmap; return true; } /** + * This function retrieves the file bitmap. A file bitmap is an array + * of bits, each bit representing one file block. A value of 1 means + * that the block is present in the file, a value of 0 means that the + * block is not present. + * + * \a pStream Pointer to an open stream + * \a pBitmap Pointer to buffer where to store the file bitmap + * \a Length Size of buffer pointed by pBitmap, in bytes + * \a LengthNeeded If non-NULL, the function supplies the necessary byte size of the buffer + */ +bool FileStream_GetBitmap(TFileStream * pStream, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded) +{ + assert(pStream->StreamGetBmp != NULL); + return pStream->StreamGetBmp(pStream, pBitmap, Length, LengthNeeded); +} + +/** * This function closes an archive file and frees any data buffers * that have been allocated for stream management. The function must also * support partially allocated structure, i.e. one or more buffers @@ -1576,9 +2084,10 @@ void FileStream_Close(TFileStream * pStream) // Check if the stream structure is allocated at all if(pStream != NULL) { - // Close the file handle - if(pStream->hFile != INVALID_HANDLE_VALUE) - CloseTheFile(pStream->hFile); + // Close the stream provider. + // This will also close the base stream + assert(pStream->StreamClose != NULL); + pStream->StreamClose(pStream); // Free the stream itself STORM_FREE(pStream); @@ -1587,275 +2096,199 @@ void FileStream_Close(TFileStream * pStream) //----------------------------------------------------------------------------- // main - for testing purposes -/* -int main(void) + +#ifdef __STORMLIB_TEST__ +int FileStream_Test(const TCHAR * szFileName, DWORD dwStreamFlags) { - ULONGLONG FilePos; - ULONGLONG FileSize; - TMPQFileTime * pFT; - TFileStream * pTempStream; TFileStream * pStream; + TMPQHeader MpqHeader; + ULONGLONG FilePos; TMPQBlock * pBlock; TMPQHash * pHash; - TMPQHeader2 MpqHeader; - char szString1[100] = "This is a single line\n\r"; - char szString2[100]; - char Buffer[0x80]; - DWORD dwLength = strlen(szString1); - // - // Test 1: Write to a stream - // + InitializeMpqCryptography(); - pStream = FileStream_CreateFile("E:\\Stream.bin"); + pStream = FileStream_OpenFile(szFileName, dwStreamFlags); if(pStream == NULL) + return GetLastError(); + + // Read the MPQ header + FileStream_Read(pStream, NULL, &MpqHeader, MPQ_HEADER_SIZE_V2); + if(MpqHeader.dwID != ID_MPQ) + return ERROR_FILE_CORRUPT; + + // Read the hash table + pHash = STORM_ALLOC(TMPQHash, MpqHeader.dwHashTableSize); + if(pHash != NULL) { - printf("Failed to create new file\n"); - return -1; + FilePos = MpqHeader.dwHashTablePos; + FileStream_Read(pStream, &FilePos, pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash)); + DecryptMpqBlock(pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash), MPQ_KEY_HASH_TABLE); + STORM_FREE(pHash); } - for(int i = 0; i < 10; i++) + // Read the block table + pBlock = STORM_ALLOC(TMPQBlock, MpqHeader.dwBlockTableSize); + if(pBlock != NULL) { - if(!FileStream_Write(pStream, NULL, szString1, dwLength)) - { - printf("Failed to write to the stream\n"); - return -1; - } + FilePos = MpqHeader.dwBlockTablePos; + FileStream_Read(pStream, &FilePos, pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock)); + DecryptMpqBlock(pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock), MPQ_KEY_BLOCK_TABLE); + STORM_FREE(pBlock); } + FileStream_Close(pStream); + return ERROR_SUCCESS; +} +#endif + +/* +int FileStream_Test() +{ + TFileStream * pStream; + + InitializeMpqCryptography(); // - // Test2: Read from the stream + // Test 1: Write to a stream // - pStream = FileStream_OpenFile("E:\\Stream.bin", false); - if(pStream == NULL) + pStream = FileStream_CreateFile("E:\\Stream.bin", 0); + if(pStream != NULL) { - printf("Failed to open existing file\n"); - return -1; - } + char szString1[100] = "This is a single line\n\r"; + DWORD dwLength = strlen(szString1); - // This call must end with an error - if(FileStream_Write(pStream, NULL, "aaa", 3)) - { - printf("Write succeeded while it should fail\n"); - return -1; + for(int i = 0; i < 10; i++) + { + if(!FileStream_Write(pStream, NULL, szString1, dwLength)) + { + printf("Failed to write to the stream\n"); + return ERROR_CAN_NOT_COMPLETE; + } + } + FileStream_Close(pStream); } - for(int i = 0; i < 10; i++) + // + // Test2: Read from the stream + // + + pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + if(pStream != NULL) { - if(!FileStream_Read(pStream, NULL, szString2, dwLength)) + char szString1[100] = "This is a single line\n\r"; + char szString2[100]; + DWORD dwLength = strlen(szString1); + + // This call must end with an error + if(FileStream_Write(pStream, NULL, "aaa", 3)) { - printf("Failed to read from the stream\n"); + printf("Write succeeded while it should fail\n"); return -1; } - szString2[dwLength] = 0; - if(strcmp(szString1, szString2)) + for(int i = 0; i < 10; i++) { - printf("Data read from file are different from data written\n"); - return -1; + if(!FileStream_Read(pStream, NULL, szString2, dwLength)) + { + printf("Failed to read from the stream\n"); + return -1; + } + + szString2[dwLength] = 0; + if(strcmp(szString1, szString2)) + { + printf("Data read from file are different from data written\n"); + return -1; + } } + FileStream_Close(pStream); } - FileStream_Close(pStream); // // Test3: Open the temp stream, write some data and switch it to the original stream // - pStream = FileStream_OpenFile("E:\\Stream.bin", false); - if(pStream == NULL) - { - printf("Failed to open existing file\n"); - return -1; - } - - pTempStream = FileStream_CreateFile("E:\\TempStream.bin"); - if(pTempStream == NULL) - { - printf("Failed to create temp stream\n"); - return -1; - } - - // Copy the original stream to the temp - if(!FileStream_GetSize(pStream, &FileSize)) - { - printf("Failed to get the file size\n"); - return -1; - } - - while(FileSize.QuadPart != 0) + pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); + if(pStream != NULL) { - DWORD dwBytesToRead = FileSize.LowPart; + TFileStream * pTempStream; + ULONGLONG FileSize; - if(dwBytesToRead > sizeof(Buffer)) - dwBytesToRead = sizeof(Buffer); - - if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead)) + pTempStream = FileStream_CreateFile("E:\\TempStream.bin", 0); + if(pTempStream == NULL) { - printf("CopyStream: Read source file failed\n"); + printf("Failed to create temp stream\n"); return -1; } - if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead)) + // Copy the original stream to the temp + if(!FileStream_GetSize(pStream, FileSize)) { - printf("CopyStream: Write target file failed\n"); + printf("Failed to get the file size\n"); return -1; } - FileSize.QuadPart -= dwBytesToRead; - } + while(FileSize != 0) + { + DWORD dwBytesToRead = (DWORD)FileSize; + char Buffer[0x80]; - // Switch the streams - // Note that the pTempStream is closed by the operation - FileStream_MoveFile(pStream, pTempStream); - FileStream_Close(pStream); + if(dwBytesToRead > sizeof(Buffer)) + dwBytesToRead = sizeof(Buffer); - // - // Test4: Read from the stream again - // + if(!FileStream_Read(pStream, NULL, Buffer, dwBytesToRead)) + { + printf("CopyStream: Read source file failed\n"); + return -1; + } - pStream = FileStream_OpenFile("E:\\Stream.bin", false); - if(pStream == NULL) - { - printf("Failed to open existing file\n"); - return -1; - } + if(!FileStream_Write(pTempStream, NULL, Buffer, dwBytesToRead)) + { + printf("CopyStream: Write target file failed\n"); + return -1; + } - for(int i = 0; i < 10; i++) - { - if(!FileStream_Read(pStream, NULL, szString2, dwLength)) - { - printf("Failed to read from the stream\n"); - return -1; + FileSize -= dwBytesToRead; } - szString2[dwLength] = 0; - if(strcmp(szString1, szString2)) - { - printf("Data read from file are different from data written\n"); - return -1; - } + // Switch the streams + // Note that the pTempStream is closed by the operation + FileStream_Switch(pStream, pTempStream); + FileStream_Close(pStream); } - FileStream_Close(pStream); // - // Test5: Open partial MPQ stream + // Test4: Read from the stream again // -// InitializeMpqCryptography(); - pStream = FileStream_OpenFile("e:\\Multimedia\\MPQs\\PartialMPQs\\patch.MPQ.part", false); + pStream = FileStream_OpenFile("E:\\Stream.bin", STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pStream != NULL) { - // Read the MPQ header - FileStream_Read(pStream, NULL, &MpqHeader, MPQ_HEADER_SIZE_V2); - - // Read the hash table - pHash = STORM_ALLOC(TMPQHash, MpqHeader.dwHashTableSize); - FilePos.HighPart = 0; - FilePos.LowPart = MpqHeader.dwHashTablePos; - FileStream_Read(pStream, &FilePos, pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash)); - - // - // At this point, the encrypted hash table should be like this: - // - // 416c7175 5ddfb61b 84bb75c8 c0399515 - // a9793eca 9ec773d7 ed8a54d6 74fb2adf - // 6acd4ae5 b13816b5 ffad2341 2f2b2a54 - // 614339c7 5fd0adf0 62434e91 d62439e3 - // 8f317aa5 f12706d6 bd83d2ca 97d7f108 - // 7586d373 51d85b05 8540beca f37ef3d7 - // d931d4d6 d592aadf 9044e960 c4592e92 - // 47dc03f7 0982dea4 afb31943 7c3c7cec - // 0c28fd0d bcbfb7df 4d13b6e4 b5b0ef31 - // e1a33b70 ec30e4b9 7aaa5e7a fb6d46ec - // 61732791 55fe757e 8ba18b5d d5f93246 - // 6d275f38 a89b5781 c34189a9 654c6472 - // 07e1d4e1 814bc8ee c72d2730 815afd43 - // 40bd2a92 640a9391 d868f813 0f61b73d - // 6d202746 2c5124ca 65db3ad0 5b1c3e39 - // b731013c 73776405 eac0c746 6e50c938 - // a4a7fd00 56db3805 6d6dbab7 44fed28a - // 2383394b bf617bdd a3edfaa2 e7d3aaaf - // - - // Decrypt the hash table -// DecryptMpqBlock(pHash, MpqHeader.dwHashTableSize * sizeof(TMPQHash), MPQ_KEY_HASH_TABLE); - - // - // At this point, the hash table should be like this: - // - // c750beb9 72c2538a 00000000 00000466 - // ffffffff ffffffff ffffffff ffffffff - // 898fdc7a 18963b5d 00000000 000005e1 - // ffffffff ffffffff ffffffff ffffffff - // e3c6fc32 d8afff2b 00000000 000001ea - // ffffffff ffffffff ffffffff ffffffff - // ffffffff ffffffff ffffffff ffffffff - // ffffffff ffffffff ffffffff ffffffff - // ffffffff ffffffff ffffffff ffffffff - // ffffffff ffffffff ffffffff ffffffff - // ffffffff ffffffff ffffffff ffffffff - // 0fa4fd60 3fbe8626 00000000 0000076f - // 9ee5bccf 031b277b 00000000 0000095c - // f4e154c5 0aadd1c1 00000000 00000876 - // 9e1ce9e7 e12d575d 00000000 0000071d - // - - // Read the block table - pBlock = STORM_ALLOC(TMPQBlock, MpqHeader.dwBlockTableSize); - FilePos.HighPart = 0; - FilePos.LowPart = MpqHeader.dwBlockTablePos; - FileStream_Read(pStream, &FilePos, pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock)); + char szString1[100] = "This is a single line\n\r"; + char szString2[100]; + DWORD dwLength = strlen(szString1); - // - // At this point, the encrypted block table should be like this: - // - // 3d4867a7 ca0f533e f82c54d6 ed3c9dec - // d8d607dc d9ad13ab f4588b46 8d058704 - // e8084fc8 63bc8064 b058c777 3683e9e3 - // 6c0da998 7703be0d 91ce3607 c14e29b9 - // 481b5c0d 42d902d2 8302acb7 e8f3e715 - // c9cdfc91 7cc38c15 ea3dfd22 ad20c856 - // b6450c7f 08522866 4cedb064 e03e3a86 - // 4509c7cc ddffbfc3 82fc8c66 e82a4424 - // afc4a982 23169037 5af6a3e2 34e1d24e - // 362c9e34 846cfc3d 4c611fcd d645fe8f - // f4061640 6d08d196 f330a975 66e30993 - // fd96a033 2b16def6 62ff30af 3e190b0b - // 664a5b91 b8558235 fd631825 a7807be7 - // ec906b9b 76d8b32e 36f3ea0b 1b0f5391 - // - - // Decrypt the block table -// DecryptMpqBlock(pBlock, MpqHeader.dwBlockTableSize * sizeof(TMPQBlock), MPQ_KEY_BLOCK_TABLE); - - // - // At this point, the block table should be like this: - // - // 0000002c 00078093 00116824 84000200 - // 000780bf 000002d5 00008044 84000200 - // 00078394 00001516 0000874c 84000200 - // 000798aa 00003797 0000af4e 84000200 - // 0007d041 000001db 00008044 84000200 - // 0007d21c 0000005e 0000005e 84000200 - // 0007d27a 000022fb 00009674 84000200 - // 0007f575 00002389 00009c64 84000200 - // 000818fe 000023cb 00009d58 84000200 - // 00083cc9 000024d9 0000a0d8 84000200 - // 000861a2 00002356 00009c70 84000200 - // 000884f8 000023d3 00009da4 84000200 - // 0008a8cb 000022d6 00009cd4 84000200 - // 0008cba1 00002339 00009714 84000200 - // 0008eeda 000023dc 00009b24 84000200 - // 000912b6 00002481 00009eac 84000200 - // 00093737 00002444 0000a028 84000200 - // 00095b7b 00002440 00009fc4 84000200 - // + for(int i = 0; i < 10; i++) + { + if(!FileStream_Read(pStream, NULL, szString2, dwLength)) + { + printf("Failed to read from the stream\n"); + return -1; + } + szString2[dwLength] = 0; + if(strcmp(szString1, szString2)) + { + printf("Data read from file are different from data written\n"); + return -1; + } + } FileStream_Close(pStream); } return 0; } */ + diff --git a/dep/StormLib/src/FileStream.h b/dep/StormLib/src/FileStream.h new file mode 100644 index 00000000000..31fe757514d --- /dev/null +++ b/dep/StormLib/src/FileStream.h @@ -0,0 +1,189 @@ +/*****************************************************************************/ +/* FileStream.h Copyright (c) Ladislav Zezula 2012 */ +/*---------------------------------------------------------------------------*/ +/* Description: Definitions for FileStream object */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 14.04.12 1.00 Lad The first version of FileStream.h */ +/*****************************************************************************/ + +#ifndef __FILESTREAM_H__ +#define __FILESTREAM_H__ + +//----------------------------------------------------------------------------- +// Function prototypes + +typedef bool (*STREAM_READ)( + struct TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position + void * pvBuffer, // Pointer to data to be read + DWORD dwBytesToRead // Number of bytes to read from the file + ); + +typedef bool (*STREAM_WRITE)( + struct TFileStream * pStream, // Pointer to an open stream + ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it writes to the current position + const void * pvBuffer, // Pointer to data to be written + DWORD dwBytesToWrite // Number of bytes to read from the file + ); + +typedef bool (*STREAM_GETPOS)( + struct TFileStream * pStream, // Pointer to an open stream + ULONGLONG & ByteOffset // Pointer to store current file position + ); + +typedef bool (*STREAM_GETSIZE)( + struct TFileStream * pStream, // Pointer to an open stream + ULONGLONG & FileSize // Receives the file size, in bytes + ); + +typedef bool (*STREAM_SETSIZE)( + struct TFileStream * pStream, // Pointer to an open stream + ULONGLONG FileSize // New size for the file, in bytes + ); + +typedef bool (*STREAM_GETTIME)( + struct TFileStream * pStream, + ULONGLONG * pFT + ); + +typedef bool (*STREAM_SWITCH)( + struct TFileStream * pStream, + struct TFileStream * pNewStream + ); + +typedef bool (*STREAM_GETBMP)( + TFileStream * pStream, + TFileBitmap * pBitmap, + DWORD Length, + LPDWORD LengthNeeded + ); + +typedef void (*STREAM_CLOSE)( + struct TFileStream * pStream + ); + +//----------------------------------------------------------------------------- +// Local structures - part file structure + +typedef struct _PART_FILE_HEADER +{ + DWORD PartialVersion; // Always set to 2 + char GameBuildNumber[0x20]; // Minimum build number of the game that can use this MPQ + DWORD Flags; // Flags (details unknown) + DWORD FileSizeLo; // Low 32 bits of the contained file size + DWORD FileSizeHi; // High 32 bits of the contained file size + DWORD BlockSize; // Size of one file block, in bytes + +} PART_FILE_HEADER, *PPART_FILE_HEADER; + +// Structure describing the block-to-file map entry +typedef struct _PART_FILE_MAP_ENTRY +{ + DWORD Flags; // 3 = the block is present in the file + DWORD BlockOffsLo; // Low 32 bits of the block position in the file + DWORD BlockOffsHi; // High 32 bits of the block position in the file + DWORD LargeValueLo; // 64-bit value, meaning is unknown + DWORD LargeValueHi; + +} PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY; + +//----------------------------------------------------------------------------- +// Local structures + +union TBaseData +{ + struct + { + ULONGLONG FileSize; // Size of the file + ULONGLONG FilePos; // Current file position + ULONGLONG FileTime; // Date/time of last modification of the file + HANDLE hFile; // File handle + } File; + + struct + { + ULONGLONG FileSize; // Mapped file size + ULONGLONG FilePos; // Current stream position + ULONGLONG FileTime; // Date/time of last modification of the file + LPBYTE pbFile; // Pointer to mapped view + } Map; + + struct + { + ULONGLONG FileSize; // Size of the internet file + ULONGLONG FilePos; // Current position in the file + ULONGLONG FileTime; // Date/time of last modification of the file + HANDLE hInternet; // Internet handle + HANDLE hConnect; // Connection to the internet server + } Http; +}; + +//----------------------------------------------------------------------------- +// Structure for linear stream + +struct TFileStream +{ + // Stream provider functions + STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly. + STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly. + STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position + STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size + STREAM_SETSIZE StreamSetSize; // Pointer to function changing file size + STREAM_GETTIME StreamGetTime; // Pointer to function retrieving the file time + STREAM_GETBMP StreamGetBmp; // Pointer to function that retrieves the file bitmap + STREAM_SWITCH StreamSwitch; // Pointer to function changing the stream to another file + STREAM_CLOSE StreamClose; // Pointer to function closing the stream + + // Stream provider data members + TCHAR szFileName[MAX_PATH]; // File name + DWORD dwFlags; // Stream flags + + // Base provider functions + STREAM_READ BaseRead; + STREAM_WRITE BaseWrite; + STREAM_GETPOS BaseGetPos; // Pointer to function that returns current file position + STREAM_GETSIZE BaseGetSize; // Pointer to function returning file size + STREAM_SETSIZE BaseSetSize; // Pointer to function changing file size + STREAM_GETTIME BaseGetTime; // Pointer to function retrieving the file time + STREAM_CLOSE BaseClose; // Pointer to function closing the stream + + // Base provider data members + TBaseData Base; // Base provider data + + // Followed by stream provider data, with variable length +}; + +//----------------------------------------------------------------------------- +// Structure for linear stream + +struct TLinearStream : public TFileStream +{ + TFileBitmap * pBitmap; // Pointer to the stream bitmap +}; + +//----------------------------------------------------------------------------- +// Structure for partial stream + +struct TPartialStream : public TFileStream +{ + ULONGLONG VirtualSize; // Virtual size of the file + ULONGLONG VirtualPos; // Virtual position in the file + DWORD BlockCount; // Number of file blocks. Used by partial file stream + DWORD BlockSize; // Size of one block. Used by partial file stream + + PPART_FILE_MAP_ENTRY PartMap; // File map, variable length +}; + +//----------------------------------------------------------------------------- +// Structure for encrypted stream + +#define MPQE_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted + +struct TEncryptedStream : public TFileStream +{ + BYTE Key[MPQE_CHUNK_SIZE]; // File key +}; + +#endif // __FILESTREAM_H__ diff --git a/dep/StormLib/src/SBaseCommon.cpp b/dep/StormLib/src/SBaseCommon.cpp index 9db708a2fe7..65818784521 100644 --- a/dep/StormLib/src/SBaseCommon.cpp +++ b/dep/StormLib/src/SBaseCommon.cpp @@ -15,12 +15,11 @@ #include "StormLib.h" #include "StormCommon.h" -char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2011"; +char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2012"; //----------------------------------------------------------------------------- // The buffer for decryption engine. -DWORD dwGlobalFlags = 0; // Global flags LCID lcFileLocale = LANG_NEUTRAL; // File locale USHORT wPlatform = 0; // File platform @@ -319,6 +318,20 @@ int ConvertMpqHeaderToFormat4( } //----------------------------------------------------------------------------- +// Default flags for (attributes) and (listfile) + +DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize) +{ + // Fixed for format 1.0 + if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) + return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY; + + // Size-dependent for formats 2.0-4.0 + return (dwFileSize > 0x4000) ? (MPQ_FILE_COMPRESS | MPQ_FILE_SECTOR_CRC) : (MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT); +} + + +//----------------------------------------------------------------------------- // Encrypting and decrypting MPQ file data void EncryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwSeed1) @@ -692,11 +705,9 @@ void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos) FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize; // Add the MD5 chunks, if present - if(pHeader->dwRawChunkSize != 0) + if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0) { - dwChunkCount = pFileEntry->dwCmpSize / pHeader->dwRawChunkSize; - if(pFileEntry->dwCmpSize % pHeader->dwRawChunkSize) - dwChunkCount++; + dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1; FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE; } } @@ -773,7 +784,7 @@ int LoadMpqTable( int cbOutBuffer = (int)dwRealSize; int cbInBuffer = (int)dwCompressedSize; - if(!SCompDecompress((char *)pvTable, &cbOutBuffer, (char *)pbCompressed, cbInBuffer)) + if(!SCompDecompress2((char *)pvTable, &cbOutBuffer, (char *)pbCompressed, cbInBuffer)) nError = GetLastError(); // Free the temporary buffer @@ -828,13 +839,11 @@ unsigned char * AllocateMd5Buffer( DWORD cbMd5Size; // Sanity check + assert(dwRawDataSize != 0); assert(dwChunkSize != 0); // Calculate how many MD5's we will calculate - cbMd5Size = dwRawDataSize / dwChunkSize; - if(dwRawDataSize % dwChunkSize) - cbMd5Size++; - cbMd5Size *= MD5_DIGEST_SIZE; + cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE; // Allocate space for array or MD5s md5_array = STORM_ALLOC(BYTE, cbMd5Size); @@ -853,9 +862,12 @@ int AllocateSectorBuffer(TMPQFile * hf) // Caller of AllocateSectorBuffer must ensure these assert(hf->pbFileSector == NULL); assert(hf->pFileEntry != NULL); - assert(hf->dwDataSize != 0); assert(hf->ha != NULL); + // Don't allocate anything if the file has zero size + if(hf->pFileEntry->dwFileSize == 0 || hf->dwDataSize == 0) + return ERROR_SUCCESS; + // Determine the file sector size and allocate buffer for it hf->dwSectorSize = (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) ? hf->dwDataSize : ha->dwSectorSize; hf->pbFileSector = STORM_ALLOC(BYTE, hf->dwSectorSize); @@ -889,7 +901,7 @@ __AllocateAndLoadPatchInfo: // Load the patch header if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength)) { - // Free the sector offsets + // Free the patch info STORM_FREE(hf->pPatchInfo); hf->pPatchInfo = NULL; return GetLastError(); @@ -904,10 +916,14 @@ __AllocateAndLoadPatchInfo: // If it's not default size, we have to reload them if(hf->pPatchInfo->dwLength > dwLength) { + // Free the patch info dwLength = hf->pPatchInfo->dwLength; STORM_FREE(hf->pPatchInfo); hf->pPatchInfo = NULL; + // If the length is out of all possible ranges, fail the operation + if(dwLength > 0x400) + return ERROR_FILE_CORRUPT; goto __AllocateAndLoadPatchInfo; } @@ -947,6 +963,7 @@ int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile) } // Calculate the number of data sectors + // Note that this doesn't work if the file size is zero hf->dwSectorCount = ((hf->dwDataSize - 1) / hf->dwSectorSize) + 1; // Calculate the number of file sectors @@ -961,6 +978,8 @@ int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile) // Only allocate and load the table if the file is compressed if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) { + __LoadSectorOffsets: + // Allocate the sector offset table hf->SectorOffsets = (DWORD *)STORM_ALLOC(BYTE, dwSectorOffsLen); if(hf->SectorOffsets == NULL) @@ -1044,10 +1063,18 @@ int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile) // There may be various extra DWORDs loaded after the sector offset table. // They are mostly empty on WoW release MPQs, but on MPQs from PTR, // they contain random non-zero data. Their meaning is unknown. - // At this point, we completely ignore them + // + // These extra values are, however, include in the dwCmpSize in the file + // table. We cannot ignore them, because compacting archive would fail // -// assert(dwSectorOffsLen == hf->SectorOffsets[0]); + if(hf->SectorOffsets[0] > dwSectorOffsLen) + { + dwSectorOffsLen = hf->SectorOffsets[0]; + STORM_FREE(hf->SectorOffsets); + hf->SectorOffsets = NULL; + goto __LoadSectorOffsets; + } } else { @@ -1388,6 +1415,10 @@ void FreeMPQArchive(TMPQArchive *& ha) if(ha->haPatch != NULL) FreeMPQArchive(ha->haPatch); + // Close the file stream + FileStream_Close(ha->pStream); + ha->pStream = NULL; + // Free the file names from the file table if(ha->pFileTable != NULL) { @@ -1402,11 +1433,12 @@ void FreeMPQArchive(TMPQArchive *& ha) STORM_FREE(ha->pFileTable); } + if(ha->pBitmap != NULL) + STORM_FREE(ha->pBitmap); if(ha->pHashTable != NULL) STORM_FREE(ha->pHashTable); if(ha->pHetTable != NULL) FreeHetTable(ha->pHetTable); - FileStream_Close(ha->pStream); STORM_FREE(ha); ha = NULL; } diff --git a/dep/StormLib/src/SBaseDumpData.cpp b/dep/StormLib/src/SBaseDumpData.cpp index f2b5fad1e8f..c9dcf0a3366 100644 --- a/dep/StormLib/src/SBaseDumpData.cpp +++ b/dep/StormLib/src/SBaseDumpData.cpp @@ -17,18 +17,18 @@ void DumpMpqHeader(TMPQHeader * pHeader) { printf("== MPQ Header =================================\n"); - printf("DWORD dwID = %08lX\n", pHeader->dwID); - printf("DWORD dwHeaderSize = %08lX\n", pHeader->dwHeaderSize); - printf("DWORD dwArchiveSize = %08lX\n", pHeader->dwArchiveSize); - printf("USHORT wFormatVersion = %04lX\n", pHeader->wFormatVersion); - printf("USHORT wSectorSize = %04lX\n", pHeader->wSectorSize); - printf("DWORD dwHashTablePos = %08lX\n", pHeader->dwHashTablePos); - printf("DWORD dwBlockTablePos = %08lX\n", pHeader->dwBlockTablePos); - printf("DWORD dwHashTableSize = %08lX\n", pHeader->dwHashTableSize); - printf("DWORD dwBlockTableSize = %08lX\n", pHeader->dwBlockTableSize); + printf("DWORD dwID = %08X\n", pHeader->dwID); + printf("DWORD dwHeaderSize = %08X\n", pHeader->dwHeaderSize); + printf("DWORD dwArchiveSize = %08X\n", pHeader->dwArchiveSize); + printf("USHORT wFormatVersion = %04X\n", pHeader->wFormatVersion); + printf("USHORT wSectorSize = %04X\n", pHeader->wSectorSize); + printf("DWORD dwHashTablePos = %08X\n", pHeader->dwHashTablePos); + printf("DWORD dwBlockTablePos = %08X\n", pHeader->dwBlockTablePos); + printf("DWORD dwHashTableSize = %08X\n", pHeader->dwHashTableSize); + printf("DWORD dwBlockTableSize = %08X\n", pHeader->dwBlockTableSize); printf("ULONGLONG HiBlockTablePos64 = %016llX\n", pHeader->HiBlockTablePos64); - printf("USHORT wHashTablePosHi = %04lX\n", pHeader->wHashTablePosHi); - printf("USHORT wBlockTablePosHi = %04lX\n", pHeader->wBlockTablePosHi); + printf("USHORT wHashTablePosHi = %04X\n", pHeader->wHashTablePosHi); + printf("USHORT wBlockTablePosHi = %04X\n", pHeader->wBlockTablePosHi); printf("ULONGLONG ArchiveSize64 = %016llX\n", pHeader->ArchiveSize64); printf("ULONGLONG BetTablePos64 = %016llX\n", pHeader->BetTablePos64); printf("ULONGLONG HetTablePos64 = %016llX\n", pHeader->HetTablePos64); @@ -37,7 +37,7 @@ void DumpMpqHeader(TMPQHeader * pHeader) printf("ULONGLONG HiBlockTableSize64 = %016llX\n", pHeader->HiBlockTableSize64); printf("ULONGLONG HetTableSize64 = %016llX\n", pHeader->HetTableSize64); printf("ULONGLONG BetTableSize64 = %016llX\n", pHeader->BetTableSize64); - printf("DWORD dwRawChunkSize = %08lX\n", pHeader->dwRawChunkSize); + printf("DWORD dwRawChunkSize = %08X\n", pHeader->dwRawChunkSize); printf("-----------------------------------------------\n\n"); } @@ -51,31 +51,31 @@ void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable) printf("== HET Header =================================\n"); printf("ULONGLONG AndMask64 = %016llX\n", pHetTable->AndMask64); printf("ULONGLONG OrMask64 = %016llX\n", pHetTable->OrMask64); - printf("DWORD dwIndexSizeTotal = %08lX\n", pHetTable->dwIndexSizeTotal); - printf("DWORD dwIndexSizeExtra = %08lX\n", pHetTable->dwIndexSizeExtra); - printf("DWORD dwIndexSize = %08lX\n", pHetTable->dwIndexSize); - printf("DWORD dwMaxFileCount = %08lX\n", pHetTable->dwMaxFileCount); - printf("DWORD dwHashTableSize = %08lX\n", pHetTable->dwHashTableSize); - printf("DWORD dwHashBitSize = %08lX\n", pHetTable->dwHashBitSize); + printf("DWORD dwIndexSizeTotal = %08X\n", pHetTable->dwIndexSizeTotal); + printf("DWORD dwIndexSizeExtra = %08X\n", pHetTable->dwIndexSizeExtra); + printf("DWORD dwIndexSize = %08X\n", pHetTable->dwIndexSize); + printf("DWORD dwMaxFileCount = %08X\n", pHetTable->dwMaxFileCount); + printf("DWORD dwHashTableSize = %08X\n", pHetTable->dwHashTableSize); + printf("DWORD dwHashBitSize = %08X\n", pHetTable->dwHashBitSize); printf("-----------------------------------------------\n\n"); printf("== BET Header =================================\n"); - printf("DWORD dwTableEntrySize = %08lX\n", pBetTable->dwTableEntrySize); - printf("DWORD dwBitIndex_FilePos = %08lX\n", pBetTable->dwBitIndex_FilePos); - printf("DWORD dwBitIndex_FileSize = %08lX\n", pBetTable->dwBitIndex_FileSize); - printf("DWORD dwBitIndex_CmpSize = %08lX\n", pBetTable->dwBitIndex_CmpSize); - printf("DWORD dwBitIndex_FlagIndex = %08lX\n", pBetTable->dwBitIndex_FlagIndex); - printf("DWORD dwBitIndex_Unknown = %08lX\n", pBetTable->dwBitIndex_Unknown); - printf("DWORD dwBitCount_FilePos = %08lX\n", pBetTable->dwBitCount_FilePos); - printf("DWORD dwBitCount_FileSize = %08lX\n", pBetTable->dwBitCount_FileSize); - printf("DWORD dwBitCount_CmpSize = %08lX\n", pBetTable->dwBitCount_CmpSize); - printf("DWORD dwBitCount_FlagIndex = %08lX\n", pBetTable->dwBitCount_FlagIndex); - printf("DWORD dwBitCount_Unknown = %08lX\n", pBetTable->dwBitCount_Unknown); - printf("DWORD dwBetHashSizeTotal = %08lX\n", pBetTable->dwBetHashSizeTotal); - printf("DWORD dwBetHashSizeExtra = %08lX\n", pBetTable->dwBetHashSizeExtra); - printf("DWORD dwBetHashSize = %08lX\n", pBetTable->dwBetHashSize); - printf("DWORD dwMaxFileCount = %08lX\n", pBetTable->dwMaxFileCount); - printf("DWORD dwFlagCount = %08lX\n", pBetTable->dwFlagCount); + printf("DWORD dwTableEntrySize = %08X\n", pBetTable->dwTableEntrySize); + printf("DWORD dwBitIndex_FilePos = %08X\n", pBetTable->dwBitIndex_FilePos); + printf("DWORD dwBitIndex_FileSize = %08X\n", pBetTable->dwBitIndex_FileSize); + printf("DWORD dwBitIndex_CmpSize = %08X\n", pBetTable->dwBitIndex_CmpSize); + printf("DWORD dwBitIndex_FlagIndex = %08X\n", pBetTable->dwBitIndex_FlagIndex); + printf("DWORD dwBitIndex_Unknown = %08X\n", pBetTable->dwBitIndex_Unknown); + printf("DWORD dwBitCount_FilePos = %08X\n", pBetTable->dwBitCount_FilePos); + printf("DWORD dwBitCount_FileSize = %08X\n", pBetTable->dwBitCount_FileSize); + printf("DWORD dwBitCount_CmpSize = %08X\n", pBetTable->dwBitCount_CmpSize); + printf("DWORD dwBitCount_FlagIndex = %08X\n", pBetTable->dwBitCount_FlagIndex); + printf("DWORD dwBitCount_Unknown = %08X\n", pBetTable->dwBitCount_Unknown); + printf("DWORD dwBetHashSizeTotal = %08X\n", pBetTable->dwBetHashSizeTotal); + printf("DWORD dwBetHashSizeExtra = %08X\n", pBetTable->dwBetHashSizeExtra); + printf("DWORD dwBetHashSize = %08X\n", pBetTable->dwBetHashSize); + printf("DWORD dwMaxFileCount = %08X\n", pBetTable->dwMaxFileCount); + printf("DWORD dwFlagCount = %08X\n", pBetTable->dwFlagCount); printf("-----------------------------------------------\n\n"); printf("== HET & Bet Table ======================================================================\n\n"); @@ -128,7 +128,7 @@ void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable) dwFlags = pBetTable->pFileFlags[dwFlagIndex]; } - printf(" %04lX %02lX %04lX %016llX %016llX %08lX %08lX %04lX %08lX\n", i, + printf(" %04X %02lX %04X %016llX %016llX %08X %08X %04X %08X\n", i, pHetTable->pHetHashes[i], dwBetIndex, BetHash, diff --git a/dep/StormLib/src/SBaseFileTable.cpp b/dep/StormLib/src/SBaseFileTable.cpp index 83d31bbac94..09379ca5da7 100644 --- a/dep/StormLib/src/SBaseFileTable.cpp +++ b/dep/StormLib/src/SBaseFileTable.cpp @@ -15,7 +15,8 @@ //----------------------------------------------------------------------------- // Local defines -#define MAX_FLAG_INDEX 256 +#define INVALID_FLAG_VALUE 0xCCCCCCCC +#define MAX_FLAG_INDEX 512 //----------------------------------------------------------------------------- // Local structures @@ -62,63 +63,10 @@ typedef struct _BET_TABLE_HEADER //----------------------------------------------------------------------------- // Support for calculating bit sizes -static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) -{ - DWORD dwBitCount = 0; - - while(MaxValue > 0) - { - MaxValue >>= 1; - dwBitCount++; - } - - return dwBitCount; -} - -static BYTE GetFlagsComboIndex(DWORD dwFlags) +static void InitFileFlagArray(LPDWORD FlagArray) { - BYTE FlagComboIndex = 0; - - // - // We only use 8 different bits. This allows us to - // construct 256 unique values for every possible combination of MPQ file flags. - // - - if(dwFlags & MPQ_FILE_IMPLODE) - FlagComboIndex |= 0x01; - dwFlags &= ~MPQ_FILE_IMPLODE; - - if(dwFlags & MPQ_FILE_COMPRESS) - FlagComboIndex |= 0x02; - dwFlags &= ~MPQ_FILE_COMPRESS; - - if(dwFlags & MPQ_FILE_ENCRYPTED) - FlagComboIndex |= 0x04; - dwFlags &= ~MPQ_FILE_ENCRYPTED; - - if(dwFlags & MPQ_FILE_FIX_KEY) - FlagComboIndex |= 0x08; - dwFlags &= ~MPQ_FILE_FIX_KEY; - - if(dwFlags & MPQ_FILE_PATCH_FILE) - FlagComboIndex |= 0x10; - dwFlags &= ~MPQ_FILE_PATCH_FILE; - - if(dwFlags & MPQ_FILE_SINGLE_UNIT) - FlagComboIndex |= 0x20; - dwFlags &= ~MPQ_FILE_SINGLE_UNIT; - - if(dwFlags & MPQ_FILE_DELETE_MARKER) - FlagComboIndex |= 0x40; - dwFlags &= ~MPQ_FILE_DELETE_MARKER; - - if(dwFlags & MPQ_FILE_SECTOR_CRC) - FlagComboIndex |= 0x80; - dwFlags &= ~MPQ_FILE_SECTOR_CRC; - - // Sanity check - the flags must now be zero - assert((dwFlags & 0x7FFFFFFF) == 0); - return FlagComboIndex; + for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) + FlagArray[dwFlagIndex] = INVALID_FLAG_VALUE; } static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) @@ -126,7 +74,7 @@ static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) // Find free or equal entry in the flag array for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++) { - if(FlagArray[dwFlagIndex] == 0 || FlagArray[dwFlagIndex] == dwFlags) + if(FlagArray[dwFlagIndex] == INVALID_FLAG_VALUE || FlagArray[dwFlagIndex] == dwFlags) { FlagArray[dwFlagIndex] = dwFlags; return dwFlagIndex; @@ -138,6 +86,19 @@ static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags) return 0xFFFFFFFF; } +static DWORD GetNecessaryBitCount(ULONGLONG MaxValue) +{ + DWORD dwBitCount = 0; + + while(MaxValue > 0) + { + MaxValue >>= 1; + dwBitCount++; + } + + return dwBitCount; +} + //----------------------------------------------------------------------------- // Support functions for BIT_ARRAY @@ -420,8 +381,7 @@ static TMPQBlock * TranslateBlockTable( BlockTableSize = sizeof(TMPQBlock) * ha->dwFileTableSize; for(DWORD i = 0; i < ha->dwFileTableSize; i++) { - if(pFileEntry->ByteOffset >> 32) - bNeedHiBlockTable = true; + bNeedHiBlockTable = (pFileEntry->ByteOffset >> 32) ? true : false; pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset; pBlock->dwFSize = pFileEntry->dwFileSize; pBlock->dwCSize = pFileEntry->dwCmpSize; @@ -519,11 +479,15 @@ TMPQExtTable * LoadExtTable( int cbOutBuffer = (int)pCompressed->dwDataSize; int cbInBuffer = (int)Size; - // Decompress the XXX block + // Decompress the extended table pExtTable->dwSignature = pCompressed->dwSignature; pExtTable->dwVersion = pCompressed->dwVersion; pExtTable->dwDataSize = pCompressed->dwDataSize; - SCompDecompress((char *)(pExtTable + 1), &cbOutBuffer, (char *)(pCompressed + 1), cbInBuffer); + if(!SCompDecompress2((char *)(pExtTable + 1), &cbOutBuffer, (char *)(pCompressed + 1), cbInBuffer)) + { + STORM_FREE(pExtTable); + pExtTable = NULL; + } } // Free the compressed block @@ -537,9 +501,9 @@ TMPQExtTable * LoadExtTable( } // Used in MPQ Editor -void FreeExtTable(TMPQExtTable * pExtTable) +void FreeMpqBuffer(void * pvBuffer) { - STORM_FREE(pExtTable); + STORM_FREE(pvBuffer); } static int SaveMpqTable( @@ -1018,26 +982,18 @@ static void CreateBetHeader( TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; ULONGLONG MaxByteOffset = 0; + DWORD FlagArray[MAX_FLAG_INDEX]; + DWORD dwMaxFlagIndex = 0; DWORD dwMaxFileSize = 0; DWORD dwMaxCmpSize = 0; - DWORD dwFlagCount = 0; - BYTE FlagComboArray[MAX_FLAG_INDEX]; - BYTE FlagComboIndex; + DWORD dwFlagIndex; // Initialize array of flag combinations - memset(FlagComboArray, 0, sizeof(FlagComboArray)); + InitFileFlagArray(FlagArray); // Get the maximum values for the BET table for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { - // We don't allow the file table to have free entries in the middle - // This must be a bug in the library somewhere. - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - pFileEntry->dwFlags = MPQ_FILE_EXISTS | MPQ_FILE_DELETE_MARKER; - assert(false); - } - // Highest file position in the MPQ if(pFileEntry->ByteOffset > MaxByteOffset) MaxByteOffset = pFileEntry->ByteOffset; @@ -1051,10 +1007,9 @@ static void CreateBetHeader( dwMaxCmpSize = pFileEntry->dwCmpSize; // Check if this flag was there before - FlagComboIndex = GetFlagsComboIndex(pFileEntry->dwFlags); - if(FlagComboArray[FlagComboIndex] == 0) - dwFlagCount++; - FlagComboArray[FlagComboIndex] = 1; + dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags); + if(dwFlagIndex > dwMaxFlagIndex) + dwMaxFlagIndex = dwFlagIndex; } // Now save bit count for every piece of file information @@ -1068,7 +1023,7 @@ static void CreateBetHeader( pBetHeader->dwBitCount_CmpSize = GetNecessaryBitCount(dwMaxCmpSize); pBetHeader->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_CmpSize + pBetHeader->dwBitCount_CmpSize; - pBetHeader->dwBitCount_FlagIndex = GetNecessaryBitCount(dwFlagCount); + pBetHeader->dwBitCount_FlagIndex = GetNecessaryBitCount(dwMaxFlagIndex + 1); pBetHeader->dwBitIndex_Unknown = pBetHeader->dwBitIndex_FlagIndex + pBetHeader->dwBitCount_FlagIndex; pBetHeader->dwBitCount_Unknown = 0; @@ -1082,7 +1037,7 @@ static void CreateBetHeader( // Save the file count and flag count pBetHeader->dwFileCount = ha->dwFileTableSize; - pBetHeader->dwFlagCount = dwFlagCount; + pBetHeader->dwFlagCount = dwMaxFlagIndex + 1; pBetHeader->dwUnknown08 = 0x10; // Save the total size of the BET hash @@ -1224,8 +1179,8 @@ TMPQExtTable * TranslateBetTable( DWORD i; // Calculate the bit sizes of various entries + InitFileFlagArray(FlagArray); CreateBetHeader(ha, &BetHeader); - memset(FlagArray, 0, sizeof(FlagArray)); // Calculate the size of the BET table BetTableSize = sizeof(BET_TABLE_HEADER) + @@ -1260,30 +1215,32 @@ TMPQExtTable * TranslateBetTable( // Construct the array of flag values and bit-based file table for(i = 0; i < BetHeader.dwFileCount; i++, pFileEntry++) { - // Only count files that exist - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // Save the byte offset - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FilePos, - BetHeader.dwBitCount_FilePos, - &pFileEntry->ByteOffset, - 8); - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FileSize, - BetHeader.dwBitCount_FileSize, - &pFileEntry->dwFileSize, - 4); - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_CmpSize, - BetHeader.dwBitCount_CmpSize, - &pFileEntry->dwCmpSize, - 4); - - // Get the flag array for the file - dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags); - pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FlagIndex, - BetHeader.dwBitCount_FlagIndex, - &dwFlagIndex, - 4); - } + // + // Note: Blizzard MPQs contain valid values even for non-existant files + // (FilePos, FileSize, CmpSize and FlagIndex) + // Note: If flags is zero, it must be in the flag table too !!! + // + + // Save the byte offset + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FilePos, + BetHeader.dwBitCount_FilePos, + &pFileEntry->ByteOffset, + 8); + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FileSize, + BetHeader.dwBitCount_FileSize, + &pFileEntry->dwFileSize, + 4); + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_CmpSize, + BetHeader.dwBitCount_CmpSize, + &pFileEntry->dwCmpSize, + 4); + + // Save the flag index + dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags); + pBitArray->SetBits(nBitOffset + BetHeader.dwBitIndex_FlagIndex, + BetHeader.dwBitCount_FlagIndex, + &dwFlagIndex, + 4); // Move the bit offset nBitOffset += BetHeader.dwTableEntrySize; @@ -1480,7 +1437,7 @@ void AllocateFileName(TFileEntry * pFileEntry, const char * szFileName) TFileEntry * FindFreeFileEntry(TMPQArchive * ha) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pDeletedEntry = NULL; + TFileEntry * pFreeEntry = NULL; TFileEntry * pFileEntry; // Try to find a free entry @@ -1488,18 +1445,22 @@ TFileEntry * FindFreeFileEntry(TMPQArchive * ha) { // If that entry is free, we reuse it if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - return pFileEntry; + { + pFreeEntry = pFileEntry; + break; + } - // If that entry is deleted, remember it - if(pFileEntry->dwFlags & MPQ_FILE_DELETE_MARKER) - pDeletedEntry = pFileEntry; + // + // Note: Files with "delete marker" are not deleted. + // Don't consider them free entries + // } // Do we have a deleted entry? - if(pDeletedEntry != NULL) + if(pFreeEntry != NULL) { - ClearFileEntry(ha, pDeletedEntry); - return pDeletedEntry; + ClearFileEntry(ha, pFreeEntry); + return pFreeEntry; } // If no file entry within the existing file table is free, @@ -1702,9 +1663,11 @@ void ClearFileEntry( 4); } - // Free the file name, and zero the entire entry + // Free the file name, and set the file entry as deleted if(pFileEntry->szFileName != NULL) STORM_FREE(pFileEntry->szFileName); + + // Invalidate the file entry memset(pFileEntry, 0, sizeof(TFileEntry)); } @@ -1718,7 +1681,7 @@ int FreeFileEntry( // // If we have HET table, we cannot just get rid of the file - // Doing so would lead to empty gaps in the HET and BET tables + // Doing so would lead to empty gaps in the HET table // We have to keep BET hash, hash index, HET index, locale, platform and file name // @@ -1750,14 +1713,10 @@ int FreeFileEntry( } else { - memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE); - pFileEntry->ByteOffset = 0; - pFileEntry->FileTime = 0; - pFileEntry->dwFileSize = 0; - pFileEntry->dwCmpSize = 0; - pFileEntry->dwFlags = MPQ_FILE_EXISTS | MPQ_FILE_DELETE_MARKER; - pFileEntry->dwCrc32 = 0; - nError = ERROR_MARKED_FOR_DELETE; + // Note: Deleted entries in Blizzard MPQs version 4.0 + // normally contain valid byte offset and length + pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS; + nError = ERROR_SUCCESS; } return nError; @@ -1785,49 +1744,104 @@ void InvalidateInternalFiles(TMPQArchive * ha) ha->dwFlags |= MPQ_FLAG_INV_ATTRIBUTES; } - // Remember that the MPQ has been changed and it will ne necessary + // Remember that the MPQ has been changed and it will be necessary // to update the tables ha->dwFlags |= MPQ_FLAG_CHANGED; } //----------------------------------------------------------------------------- -// Support for file tables - hash table, block table, hi-block table, -// (attributes) and (listfile) +// Functions that loads and verify MPQ data bitmap -static void FixBlockTableSize( - TMPQArchive * ha, - TMPQBlock * pBlockTable, - DWORD dwClaimedSize) +int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComplete) { - TMPQHeader * pHeader = ha->pHeader; - ULONGLONG BlockTableStart; - ULONGLONG BlockTableEnd; - ULONGLONG FileDataStart; + TMPQBitmap * pBitmap = NULL; + TMPQBitmap DataBitmap; + ULONGLONG BitmapOffset; + ULONGLONG EndOfMpq; + DWORD DataBlockCount = 0; + DWORD BitmapByteSize; + DWORD WholeByteCount; + DWORD ExtraBitsCount; + + // Is there enough space for a MPQ bitmap? + EndOfMpq = ha->MpqPos + ha->pHeader->ArchiveSize64; + FileSize = FileSize - sizeof(TMPQBitmap); + if(FileSize > EndOfMpq) + { + // Try to load the data bitmap from the end of the file + if(FileStream_Read(ha->pStream, &FileSize, &DataBitmap, sizeof(TMPQBitmap))) + { + // Is it a valid data bitmap? + BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&DataBitmap), sizeof(TMPQBitmap)); + if(DataBitmap.dwSignature == MPQ_DATA_BITMAP_SIGNATURE) + { + // We assume that MPQs with data bitmap begin at position 0 + assert(ha->MpqPos == 0); - // Only perform this check on MPQs version 1.0 - if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + // Calculate the number of extra bytes for data bitmap + DataBlockCount = (DWORD)(((ha->pHeader->ArchiveSize64 - 1) / DataBitmap.dwBlockSize) + 1); + BitmapByteSize = ((DataBlockCount - 1) / 8) + 1; + + // Verify the data block size + BitmapOffset = ((ULONGLONG)DataBitmap.dwMapOffsetHi << 32) | DataBitmap.dwMapOffsetLo; + assert((DWORD)(FileSize - BitmapOffset) == BitmapByteSize); + + // Allocate space for the data bitmap + pBitmap = (TMPQBitmap *)STORM_ALLOC(BYTE, sizeof(TMPQBitmap) + BitmapByteSize); + if(pBitmap != NULL) + { + // Copy the bitmap header + memcpy(pBitmap, &DataBitmap, sizeof(TMPQBitmap)); + + // Read the remaining part + if(!FileStream_Read(ha->pStream, &BitmapOffset, (pBitmap + 1), BitmapByteSize)) + { + STORM_FREE(pBitmap); + pBitmap = NULL; + } + } + } + } + } + + // If the caller asks for file completeness, check it + if(pBitmap != NULL && pbFileIsComplete != NULL) { - // Calculate claimed block table begin and end - BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + LPBYTE pbBitmap = (LPBYTE)(pBitmap + 1); + DWORD i; + bool bFileIsComplete = true; - for(DWORD i = 0; i < dwClaimedSize; i++) + // Calculate the number of whole bytes and extra bits of the bitmap + WholeByteCount = (DataBlockCount / 8); + ExtraBitsCount = (DataBlockCount & 7); + + // Verify the whole bytes - their value must be 0xFF + for(i = 0; i < WholeByteCount; i++) { - // If the block table end goes into that file, fix the block table end - FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; - if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) - { - dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); - BlockTableEnd = FileDataStart; - } + if(pbBitmap[i] != 0xFF) + bFileIsComplete = false; + } + + // If there are extra bits, calculate the mask + if(ExtraBitsCount != 0) + { + BYTE ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); + + if(pbBitmap[i] != ExpectedValue) + bFileIsComplete = false; } + + // Give the result to the caller + *pbFileIsComplete = bFileIsComplete; } - // Fix the block table size - pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); - pHeader->dwBlockTableSize = dwClaimedSize; + ha->pBitmap = pBitmap; + return ERROR_SUCCESS; } +//----------------------------------------------------------------------------- +// Support for file tables - hash table, block table, hi-block table + int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) { TMPQHash * pHashTable; @@ -1851,7 +1865,7 @@ int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize) return ERROR_SUCCESS; } -int LoadHashTable(TMPQArchive * ha) +TMPQHash * LoadHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; ULONGLONG ByteOffset; @@ -1862,17 +1876,17 @@ int LoadHashTable(TMPQArchive * ha) // If the MPQ has no hash table, do nothing if(pHeader->dwHashTablePos == 0 && pHeader->wHashTablePosHi == 0) - return ERROR_SUCCESS; + return NULL; // If the hash table size is zero, do nothing if(pHeader->dwHashTableSize == 0) - return ERROR_SUCCESS; + return NULL; // Allocate buffer for the hash table dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash); pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize); if(pHashTable == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + return NULL; // Compressed size of the hash table dwCmpSize = (DWORD)pHeader->HashTableSize64; @@ -1891,17 +1905,108 @@ int LoadHashTable(TMPQArchive * ha) { STORM_FREE(pHashTable); pHashTable = NULL; - return nError; } - // Set the maximum file count to the size of the hash table - // In case there is HET table, we have to keep the file limit - if(ha->pHetTable == NULL) - ha->dwMaxFileCount = pHeader->dwHashTableSize; + // Return the hash table + return pHashTable; +} - // Store the hash table to the MPQ - ha->pHashTable = pHashTable; - return ERROR_SUCCESS; +static void FixBlockTableSize( + TMPQArchive * ha, + TMPQBlock * pBlockTable, + DWORD dwClaimedSize) +{ + TMPQHeader * pHeader = ha->pHeader; + ULONGLONG BlockTableStart; + ULONGLONG BlockTableEnd; + ULONGLONG FileDataStart; + + // Only perform this check on MPQs version 1.0 + if(pHeader->dwHeaderSize == MPQ_HEADER_SIZE_V1) + { + // Calculate claimed block table begin and end + BlockTableStart = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + BlockTableEnd = BlockTableStart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)); + + for(DWORD i = 0; i < dwClaimedSize; i++) + { + // If the block table end goes into that file, fix the block table end + FileDataStart = ha->MpqPos + pBlockTable[i].dwFilePos; + if(BlockTableStart < FileDataStart && BlockTableEnd > FileDataStart) + { + dwClaimedSize = (DWORD)((FileDataStart - BlockTableStart) / sizeof(TMPQBlock)); + BlockTableEnd = FileDataStart; + } + } + } + + // Fix the block table size + pHeader->BlockTableSize64 = dwClaimedSize * sizeof(TMPQBlock); + pHeader->dwBlockTableSize = dwClaimedSize; +} + +TMPQBlock * LoadBlockTable(TMPQArchive * ha, ULONGLONG FileSize) +{ + TMPQHeader * pHeader = ha->pHeader; + TMPQBlock * pBlockTable; + ULONGLONG ByteOffset; + DWORD dwTableSize; + DWORD dwCmpSize; + int nError; + + // Do nothing if the block table position is zero + if(pHeader->dwBlockTablePos == 0 && pHeader->wBlockTablePosHi == 0) + return NULL; + + // Do nothing if the block table size is zero + if(pHeader->dwBlockTableSize == 0) + return NULL; + + // Sanity check, enforced by LoadAnyHashTable + assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); + + // Calculate sizes of both tables + ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); + dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + dwCmpSize = (DWORD)pHeader->BlockTableSize64; + + // Allocate space for the block table + // Note: pHeader->dwBlockTableSize can be zero !!! + pBlockTable = STORM_ALLOC(TMPQBlock, ha->dwMaxFileCount); + if(pBlockTable == NULL) + return NULL; + + // Fill the block table with zeros + memset(pBlockTable, 0, dwTableSize); + + // I found a MPQ which claimed 0x200 entries in the block table, + // but the file was cut and there was only 0x1A0 entries. + // We will handle this case properly. + if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize) + { + pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock)); + pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); + } + + // + // One of the first cracked versions of Diablo I had block table unencrypted + // StormLib does NOT support such MPQs anymore, as they are incompatible + // with compressed block table feature + // + + // Load the block table + nError = LoadMpqTable(ha, ByteOffset, pBlockTable, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); + if(nError != ERROR_SUCCESS) + { + // Failed, sorry + STORM_FREE(pBlockTable); + return NULL; + } + + // Defense against MPQs that claim block table to be bigger than it really is + FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); + return pBlockTable; } int LoadHetTable(TMPQArchive * ha) @@ -1963,19 +2068,25 @@ TMPQBetTable * LoadBetTable(TMPQArchive * ha) int LoadAnyHashTable(TMPQArchive * ha) { TMPQHeader * pHeader = ha->pHeader; - bool bHashTableLoaded = false; // If the MPQ archive is empty, don't bother trying to load anything if(pHeader->dwHashTableSize == 0 && pHeader->HetTableSize64 == 0) return CreateHashTable(ha, HASH_TABLE_SIZE_DEFAULT); - // Try to load HET table - if(LoadHetTable(ha) == ERROR_SUCCESS) - bHashTableLoaded = true; + // Try to load HET and/or classic hash table + LoadHetTable(ha); - // Try to load the classic hash table - if(LoadHashTable(ha) == ERROR_SUCCESS) - bHashTableLoaded = true; + // Load the HASH table + ha->pHashTable = LoadHashTable(ha); + + // Set the maximum file count to the size of the hash table + // In case there is HET table, we have to keep the file limit + if(ha->pHetTable == NULL) + ha->dwMaxFileCount = pHeader->dwHashTableSize; + + // Did at least one succeed? + if(ha->pHetTable == NULL && ha->pHashTable == NULL) + return ERROR_FILE_CORRUPT; // In theory, a MPQ could have bigger block table than hash table if(ha->pHeader->dwBlockTableSize > ha->dwMaxFileCount) @@ -1984,7 +2095,7 @@ int LoadAnyHashTable(TMPQArchive * ha) ha->dwFlags |= MPQ_FLAG_READ_ONLY; } - return bHashTableLoaded ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; + return ERROR_SUCCESS; } int BuildFileTable_Classic( @@ -2001,118 +2112,80 @@ int BuildFileTable_Classic( // Sanity checks assert(ha->pHashTable != NULL); - // Do nothing if the size of the block table is zero - if(pHeader->dwBlockTablePos != 0 && pHeader->dwBlockTableSize != 0) + // Load the block table + pBlockTable = LoadBlockTable(ha, FileSize); + if(pBlockTable != NULL) { - // Sanity check, enforced by LoadAnyHashTable - assert(ha->dwMaxFileCount >= pHeader->dwBlockTableSize); + TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; + TMPQHash * pHash; - // Allocate space for the block table - // Note: pHeader->dwBlockTableSize can be zero !!! - pBlockTable = STORM_ALLOC(TMPQBlock, ha->dwMaxFileCount); - if(pBlockTable != NULL) + // If we don't have HET table, we build the file entries from the hash&block tables + if(ha->pHetTable == NULL) { - ULONGLONG ByteOffset = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos); - TMPQHash * pHashEnd = ha->pHashTable + pHeader->dwHashTableSize; - TMPQHash * pHash; - DWORD dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - DWORD dwCmpSize = (DWORD)pHeader->BlockTableSize64; - - // Fill the block table with zeros - memset(pBlockTable, 0, dwTableSize); - - // I have found a MPQ which claimed 0x200 entries in the block table, - // but the file was cut and there was only 0x1A0 entries. - // We will handle this case properly. - if(dwTableSize == dwCmpSize && (ByteOffset + dwTableSize) > FileSize) + for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) { - pHeader->dwBlockTableSize = (DWORD)((FileSize - ByteOffset) / sizeof(TMPQBlock)); - pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - dwTableSize = dwCmpSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock); - } - - // - // One of the first cracked versions of Diablo I had block table unencrypted - // StormLib does NOT support such MPQs anymore, as they are incompatible - // with compressed block table feature - // - - // Load the block table - nError = LoadMpqTable(ha, ByteOffset, pBlockTable, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE); - if(nError == ERROR_SUCCESS) - { - // Defense against MPQs that claim block table to be bigger than it really is - FixBlockTableSize(ha, pBlockTable, pHeader->dwBlockTableSize); - - // If we don't have HET table, we build the file entries from the hash&block tables - if(ha->pHetTable == NULL) + if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) { - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + pFileEntry = pFileTable + pHash->dwBlockIndex; + pBlock = pBlockTable + pHash->dwBlockIndex; + + // + // Yet another silly map protector: For each valid file, + // there are 4 items in the hash table, that appears to be valid: + // + // a6d79af0 e61a0932 001e0000 0000770b <== Fake valid + // a6d79af0 e61a0932 0000d761 0000dacb <== Fake valid + // a6d79af0 e61a0932 00000000 0000002f <== Real file entry + // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid + // + + if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) { - if(pHash->dwBlockIndex < pHeader->dwBlockTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - pBlock = pBlockTable + pHash->dwBlockIndex; - - // - // Yet another silly map protector: For each valid file, - // there are 4 items in the hash table, that appears to be valid: - // - // a6d79af0 e61a0932 001e0000 0000770b <== Fake valid - // a6d79af0 e61a0932 0000d761 0000dacb <== Fake valid - // a6d79af0 e61a0932 00000000 0000002f <== Real file entry - // a6d79af0 e61a0932 00005a4f 000093bc <== Fake valid - // - - if(!(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) && (pBlock->dwFlags & MPQ_FILE_EXISTS)) - { - // Fill the entry - pFileEntry->ByteOffset = pBlock->dwFilePos; - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->dwFileSize = pBlock->dwFSize; - pFileEntry->dwCmpSize = pBlock->dwCSize; - pFileEntry->dwFlags = pBlock->dwFlags; - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - else - { - // If the hash table entry doesn't point to the valid file item, - // we invalidate the entire hash table entry - pHash->dwName1 = 0xFFFFFFFF; - pHash->dwName2 = 0xFFFFFFFF; - pHash->lcLocale = 0xFFFF; - pHash->wPlatform = 0xFFFF; - pHash->dwBlockIndex = HASH_ENTRY_DELETED; - } - } + // Fill the entry + pFileEntry->ByteOffset = pBlock->dwFilePos; + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); + pFileEntry->dwFileSize = pBlock->dwFSize; + pFileEntry->dwCmpSize = pBlock->dwCSize; + pFileEntry->dwFlags = pBlock->dwFlags; + pFileEntry->lcLocale = pHash->lcLocale; + pFileEntry->wPlatform = pHash->wPlatform; } - } - else - { - for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + else { - if(pHash->dwBlockIndex < ha->dwFileTableSize) - { - pFileEntry = pFileTable + pHash->dwBlockIndex; - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); - pFileEntry->lcLocale = pHash->lcLocale; - pFileEntry->wPlatform = pHash->wPlatform; - } - } + // If the hash table entry doesn't point to the valid file item, + // we invalidate the entire hash table entry + pHash->dwName1 = 0xFFFFFFFF; + pHash->dwName2 = 0xFFFFFFFF; + pHash->lcLocale = 0xFFFF; + pHash->wPlatform = 0xFFFF; + pHash->dwBlockIndex = HASH_ENTRY_DELETED; } } } - - // Free the block table - STORM_FREE(pBlockTable); } else { - nError = ERROR_NOT_ENOUGH_MEMORY; + for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) + { + if(pHash->dwBlockIndex < ha->dwFileTableSize) + { + pFileEntry = pFileTable + pHash->dwBlockIndex; + if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) + { + pFileEntry->dwHashIndex = (DWORD)(pHash - ha->pHashTable); + pFileEntry->lcLocale = pHash->lcLocale; + pFileEntry->wPlatform = pHash->wPlatform; + } + } + } } + + // Free the block table + STORM_FREE(pBlockTable); + } + else + { + nError = ERROR_NOT_ENOUGH_MEMORY; } // Load the hi-block table diff --git a/dep/StormLib/src/SCompression.cpp b/dep/StormLib/src/SCompression.cpp index 88351499ef7..5c7432248dc 100644 --- a/dep/StormLib/src/SCompression.cpp +++ b/dep/StormLib/src/SCompression.cpp @@ -175,7 +175,7 @@ void Compress_ZLIB( // Storm.dll uses zlib version 1.1.3 // Wow.exe uses zlib version 1.2.3 nResult = deflateInit2(&z, - Z_DEFAULT_COMPRESSION, + 6, // Compression level used by WoW MPQs Z_DEFLATED, windowBits, 8, @@ -292,7 +292,13 @@ static void Compress_PKLIB( Info.pbOutBuff = pbOutBuffer; Info.pbOutBuffEnd = pbOutBuffer + *pcbOutBuffer; - // Set the compression type and dictionary size + // + // Set the dictionary size + // + // Diablo I ues fixed dictionary size of CMP_IMPLODE_DICT_SIZE3 + // Starcraft uses the variable dictionary size based on algorithm below + // + if (cbInBuffer < 0x600) dict_size = CMP_IMPLODE_DICT_SIZE1; else if(0x600 <= cbInBuffer && cbInBuffer < 0xC00) @@ -455,7 +461,7 @@ static void LZMA_Callback_Free(void *p, void *address) // the data compressed by StormLib. // -static void Compress_LZMA( +/*static */ void Compress_LZMA( char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, @@ -752,13 +758,13 @@ int WINAPI SCompExplode(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffe static TCompressTable cmp_table[] = { - {MPQ_COMPRESSION_SPARSE, Compress_SPARSE}, // Sparse compression - {MPQ_COMPRESSION_WAVE_MONO, Compress_ADPCM_mono}, // IMA ADPCM mono compression - {MPQ_COMPRESSION_WAVE_STEREO, Compress_ADPCM_stereo}, // IMA ADPCM stereo compression - {MPQ_COMPRESSION_HUFFMANN, Compress_huff}, // Huffmann compression - {MPQ_COMPRESSION_ZLIB, Compress_ZLIB}, // Compression with the "zlib" library - {MPQ_COMPRESSION_PKWARE, Compress_PKLIB}, // Compression with Pkware DCL - {MPQ_COMPRESSION_BZIP2, Compress_BZIP2} // Compression Bzip2 library + {MPQ_COMPRESSION_SPARSE, Compress_SPARSE}, // Sparse compression + {MPQ_COMPRESSION_ADPCM_MONO, Compress_ADPCM_mono}, // IMA ADPCM mono compression + {MPQ_COMPRESSION_ADPCM_STEREO, Compress_ADPCM_stereo}, // IMA ADPCM stereo compression + {MPQ_COMPRESSION_HUFFMANN, Compress_huff}, // Huffmann compression + {MPQ_COMPRESSION_ZLIB, Compress_ZLIB}, // Compression with the "zlib" library + {MPQ_COMPRESSION_PKWARE, Compress_PKLIB}, // Compression with Pkware DCL + {MPQ_COMPRESSION_BZIP2, Compress_BZIP2} // Compression Bzip2 library }; int WINAPI SCompCompress( @@ -904,13 +910,13 @@ int WINAPI SCompCompress( // of compressed data static TDecompressTable dcmp_table[] = { - {MPQ_COMPRESSION_BZIP2, Decompress_BZIP2}, // Decompression with Bzip2 library - {MPQ_COMPRESSION_PKWARE, Decompress_PKLIB}, // Decompression with Pkware Data Compression Library - {MPQ_COMPRESSION_ZLIB, Decompress_ZLIB}, // Decompression with the "zlib" library - {MPQ_COMPRESSION_HUFFMANN, Decompress_huff}, // Huffmann decompression - {MPQ_COMPRESSION_WAVE_STEREO, Decompress_ADPCM_stereo}, // IMA ADPCM stereo decompression - {MPQ_COMPRESSION_WAVE_MONO, Decompress_ADPCM_mono}, // IMA ADPCM mono decompression - {MPQ_COMPRESSION_SPARSE, Decompress_SPARSE} // Sparse decompression + {MPQ_COMPRESSION_BZIP2, Decompress_BZIP2}, // Decompression with Bzip2 library + {MPQ_COMPRESSION_PKWARE, Decompress_PKLIB}, // Decompression with Pkware Data Compression Library + {MPQ_COMPRESSION_ZLIB, Decompress_ZLIB}, // Decompression with the "zlib" library + {MPQ_COMPRESSION_HUFFMANN, Decompress_huff}, // Huffmann decompression + {MPQ_COMPRESSION_ADPCM_STEREO, Decompress_ADPCM_stereo}, // IMA ADPCM stereo decompression + {MPQ_COMPRESSION_ADPCM_MONO, Decompress_ADPCM_mono}, // IMA ADPCM mono decompression + {MPQ_COMPRESSION_SPARSE, Decompress_SPARSE} // Sparse decompression }; int WINAPI SCompDecompress( @@ -919,109 +925,86 @@ int WINAPI SCompDecompress( char * pbInBuffer, int cbInBuffer) { - DECOMPRESS DecompressFuncArray[0x10]; // Array of compression functions, applied sequentially char * pbWorkBuffer = NULL; // Temporary storage for decompressed data char * pbOutput = pbOutBuffer; // Where to store decompressed data char * pbInput; // Where to store decompressed data unsigned uCompressionMask; // Decompressions applied to the data + unsigned uCompressionCopy; // Decompressions applied to the data int cbOutBuffer = *pcbOutBuffer; // Current size of the output buffer int cbInLength; // Current size of the input buffer int nCompressCount = 0; // Number of compressions to be applied int nCompressIndex = 0; int nResult = 1; - // Zero input data bring zero output data - if(cbInBuffer == 0) - { - *pcbOutBuffer = 0; - return 1; - } + // Verify buffer sizes + if(cbOutBuffer < cbInBuffer || cbInBuffer < 1) + return 0; -/* // If the input length is the same as output length, do nothing. - // Unfortunately, some data in WoW-Cataclysm BETA MPQs are like that .... - if(cbInBuffer == cbOutBuffer) + if(cbOutBuffer == cbInBuffer) { // If the buffers are equal, don't copy anything. - if(pbInBuffer == pbOutBuffer) - return 1; - - memcpy(pbOutBuffer, pbInBuffer, cbInBuffer); + if(pbInBuffer != pbOutBuffer) + memcpy(pbOutBuffer, pbInBuffer, cbInBuffer); return 1; } -*/ // Get applied compression types and decrement data length - uCompressionMask = (unsigned char)*pbInBuffer++; + uCompressionMask = uCompressionCopy = (unsigned char)*pbInBuffer++; cbInBuffer--; // Get current compressed data and length of it pbInput = pbInBuffer; cbInLength = cbInBuffer; - // - // Beginning with Starcraft II, the decompression byte can no longer contain - // any arbitrary combination types. Instead, it can be one of the few - // pre-set values (0x02, 0x08, 0x10, 0x12, 0x20, 0x22, 0x30) - // Note that Starcraft II no longer uses WAVE files, so compressions 0x41, 0x48, 0x81, 0x88 - // are no longer used. - // + // This compression function doesn't support LZMA + assert(uCompressionMask != MPQ_COMPRESSION_LZMA); - // Special case: LZMA decompression (added in Starcraft II) - if(uCompressionMask == MPQ_COMPRESSION_LZMA) + // Parse the compression mask + for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++) { - DecompressFuncArray[0] = Decompress_LZMA; - nCompressCount = 1; - } - else - { - // Fill the compressions array - for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++) + // If the mask agrees, insert the compression function to the array + if(uCompressionMask & dcmp_table[i].uMask) { - // If the mask agrees, insert the compression function to the array - if(uCompressionMask & dcmp_table[i].uMask) - { - DecompressFuncArray[nCompressCount] = dcmp_table[i].Decompress; - uCompressionMask &= ~dcmp_table[i].uMask; - nCompressCount++; - } + uCompressionCopy &= ~dcmp_table[i].uMask; + nCompressCount++; } + } - // If at least one of the compressions remaing unknown, return an error - if(uCompressionMask != 0) - { - SetLastError(ERROR_NOT_SUPPORTED); - return 0; - } + // If at least one of the compressions remaing unknown, return an error + if(nCompressCount == 0 || uCompressionCopy != 0) + { + SetLastError(ERROR_NOT_SUPPORTED); + return 0; } - // If there is at least one decompression, do it - if(nCompressCount > 0) + // If there is more than one compression, we have to allocate extra buffer + if(nCompressCount > 1) { - // If there is more than one compression, we have to allocate extra buffer - if(nCompressCount > 1) + pbWorkBuffer = STORM_ALLOC(char, cbOutBuffer); + if(pbWorkBuffer == NULL) { - pbWorkBuffer = STORM_ALLOC(char, *pcbOutBuffer); - if(pbWorkBuffer == NULL) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return 0; - } + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; } + } - // Get the current compression index - nCompressIndex = nCompressCount - 1; + // Get the current compression index + nCompressIndex = nCompressCount - 1; - // Apply all decompressions - for(int i = 0; i < nCompressCount; i++) + // Apply all decompressions + for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++) + { + // Perform the (next) decompression + if(uCompressionMask & dcmp_table[i].uMask) { // Get the correct output buffer pbOutput = (nCompressIndex & 1) ? pbWorkBuffer : pbOutBuffer; nCompressIndex--; - - // Perform the (next) decompression + + // Perform the decompression cbOutBuffer = *pcbOutBuffer; - nResult = DecompressFuncArray[i](pbOutput, &cbOutBuffer, pbInput, cbInLength); + nResult = dcmp_table[i].Decompress(pbOutput, &cbOutBuffer, pbInput, cbInLength); if(nResult == 0 || cbOutBuffer == 0) { SetLastError(ERROR_FILE_CORRUPT); @@ -1030,21 +1013,123 @@ int WINAPI SCompDecompress( } // Switch buffers - pbInput = pbOutput; cbInLength = cbOutBuffer; + pbInput = pbOutput; } + } + + // Put the length of the decompressed data to the output buffer + *pcbOutBuffer = cbOutBuffer; + + // Cleanup and return + if(pbWorkBuffer != NULL) + STORM_FREE(pbWorkBuffer); + return nResult; +} + +int WINAPI SCompDecompress2( + char * pbOutBuffer, + int * pcbOutBuffer, + char * pbInBuffer, + int cbInBuffer) +{ + DECOMPRESS pfnDecompress1 = NULL; + DECOMPRESS pfnDecompress2 = NULL; + char * pbWorkBuffer = pbOutBuffer; + int cbWorkBuffer = *pcbOutBuffer; + int nResult; + char CompressionMethod; + + // Verify buffer sizes + if(*pcbOutBuffer < cbInBuffer || cbInBuffer < 1) + return 0; - // Put the length of the decompressed data to the output buffer - *pcbOutBuffer = cbOutBuffer; + // If the outputbuffer is as big as input buffer, just copy the block + if(*pcbOutBuffer == cbInBuffer) + { + if(pbOutBuffer != pbInBuffer) + memcpy(pbOutBuffer, pbInBuffer, cbInBuffer); + return 1; } - else + + // Get the compression methods + CompressionMethod = *pbInBuffer++; + cbInBuffer--; + + // We only recognize a fixed set of compression methods + switch((unsigned char)CompressionMethod) { - memcpy(pbOutBuffer, pbInBuffer, cbInBuffer); - *pcbOutBuffer = cbInBuffer; + case MPQ_COMPRESSION_ZLIB: + pfnDecompress1 = Decompress_ZLIB; + break; + + case MPQ_COMPRESSION_PKWARE: + pfnDecompress1 = Decompress_PKLIB; + break; + + case MPQ_COMPRESSION_BZIP2: + pfnDecompress1 = Decompress_BZIP2; + break; + + case MPQ_COMPRESSION_LZMA: + pfnDecompress1 = Decompress_LZMA; + break; + + case MPQ_COMPRESSION_SPARSE: + pfnDecompress1 = Decompress_SPARSE; + break; + + case (MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_ZLIB): + pfnDecompress1 = Decompress_ZLIB; + pfnDecompress2 = Decompress_SPARSE; + break; + + case (MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_BZIP2): + pfnDecompress1 = Decompress_BZIP2; + pfnDecompress2 = Decompress_SPARSE; + break; + + // + // Note: Any combination including MPQ_COMPRESSION_ADPCM_MONO, + // MPQ_COMPRESSION_ADPCM_STEREO or MPQ_COMPRESSION_HUFFMANN + // is not supported by newer MPQs. + // + + default: + SetLastError(ERROR_FILE_CORRUPT); + return 0; } - // Cleanup and return - if(pbWorkBuffer != NULL) + // If we have to use two decompressions, allocate temporary buffer + if(pfnDecompress2 != NULL) + { + pbWorkBuffer = STORM_ALLOC(char, *pcbOutBuffer); + if(pbWorkBuffer == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; + } + } + + // Apply the first decompression method + nResult = pfnDecompress1(pbWorkBuffer, &cbWorkBuffer, pbInBuffer, cbInBuffer); + + // Apply the second decompression method, if any + if(pfnDecompress2 != NULL && nResult != 0) + { + cbInBuffer = cbWorkBuffer; + cbWorkBuffer = *pcbOutBuffer; + nResult = pfnDecompress2(pbOutBuffer, &cbWorkBuffer, pbWorkBuffer, cbInBuffer); + } + + // Supply the output buffer size + *pcbOutBuffer = cbWorkBuffer; + + // Free temporary buffer + if(pbWorkBuffer != pbOutBuffer) STORM_FREE(pbWorkBuffer); + + if(nResult == 0) + SetLastError(ERROR_FILE_CORRUPT); return nResult; } diff --git a/dep/StormLib/src/SFileAddFile.cpp b/dep/StormLib/src/SFileAddFile.cpp index e908d644990..dda47370bd1 100644 --- a/dep/StormLib/src/SFileAddFile.cpp +++ b/dep/StormLib/src/SFileAddFile.cpp @@ -13,6 +13,33 @@ #include "StormCommon.h" //----------------------------------------------------------------------------- +// Local structures + +#define FILE_SIGNATURE_RIFF 0x46464952 +#define FILE_SIGNATURE_WAVE 0x45564157 +#define FILE_SIGNATURE_FMT 0x20746D66 +#define AUDIO_FORMAT_PCM 1 + +typedef struct _WAVE_FILE_HEADER +{ + DWORD dwChunkId; // 0x52494646 ("RIFF") + DWORD dwChunkSize; // Size of that chunk, in bytes + DWORD dwFormat; // Must be 0x57415645 ("WAVE") + + // Format sub-chunk + DWORD dwSubChunk1Id; // 0x666d7420 ("fmt ") + DWORD dwSubChunk1Size; // 0x16 for PCM + USHORT wAudioFormat; // 1 = PCM. Other value means some sort of compression + USHORT wChannels; // Number of channels + DWORD dwSampleRate; // 8000, 44100, etc. + DWORD dwBytesRate; // SampleRate * NumChannels * BitsPerSample/8 + USHORT wBlockAlign; // NumChannels * BitsPerSample/8 + USHORT wBitsPerSample; // 8 bits = 8, 16 bits = 16, etc. + + // Followed by "data" sub-chunk (we don't care) +} WAVE_FILE_HEADER, *PWAVE_FILE_HEADER; + +//----------------------------------------------------------------------------- // Local variables // Data compression for SFileAddFile @@ -27,6 +54,29 @@ static void * pvUserData = NULL; #define LOSSY_COMPRESSION_MASK (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN) +static int IsWaveFile( + LPBYTE pbFileData, + DWORD cbFileData, + LPDWORD pdwChannels) +{ + PWAVE_FILE_HEADER pWaveHdr = (PWAVE_FILE_HEADER)pbFileData; + + if(cbFileData > sizeof(WAVE_FILE_HEADER)) + { + if(pWaveHdr->dwChunkId == FILE_SIGNATURE_RIFF && pWaveHdr->dwFormat == FILE_SIGNATURE_WAVE) + { + if(pWaveHdr->dwSubChunk1Id == FILE_SIGNATURE_FMT && pWaveHdr->wAudioFormat == AUDIO_FORMAT_PCM) + { + *pdwChannels = pWaveHdr->wChannels; + return true; + } + } + } + + return false; +} + + static int WriteDataToMpqFile( TMPQArchive * ha, TMPQFile * hf, @@ -86,16 +136,6 @@ static int WriteDataToMpqFile( // Set the position in the file ByteOffset = hf->RawFilePos + pFileEntry->dwCmpSize; - // If the file is compressed, allocate buffer for the compressed data. - // Note that we allocate buffer that is a bit longer than sector size, - // for case if the compression method performs a buffer overrun - if((pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) && pbCompressed == NULL) - { - pbToWrite = pbCompressed = STORM_ALLOC(BYTE, hf->dwSectorSize + 0x100); - if(pbCompressed == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } - // Update CRC32 and MD5 of the file md5_process((hash_state *)hf->hctx, hf->pbFileSector, dwBytesInSector); hf->dwCrc32 = crc32(hf->dwCrc32, hf->pbFileSector, dwBytesInSector); @@ -106,7 +146,18 @@ static int WriteDataToMpqFile( int nOutBuffer = (int)dwBytesInSector; int nInBuffer = (int)dwBytesInSector; - assert(pbCompressed != NULL); + // If the file is compressed, allocate buffer for the compressed data. + // Note that we allocate buffer that is a bit longer than sector size, + // for case if the compression method performs a buffer overrun + if(pbCompressed == NULL) + { + pbToWrite = pbCompressed = STORM_ALLOC(BYTE, hf->dwSectorSize + 0x100); + if(pbCompressed == NULL) + { + nError = ERROR_NOT_ENOUGH_MEMORY; + break; + } + } // // Note that both SCompImplode and SCompCompress give original buffer, @@ -319,11 +370,17 @@ int SFileAddFile_Init( // flags get to this point // - // Adjust file flags for too-small files - if(dwFileSize < 0x04) - dwFlags &= ~(MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY); - if(dwFileSize < 0x20) - dwFlags &= ~(MPQ_FILE_COMPRESSED | MPQ_FILE_SECTOR_CRC); + // Sestor CRC is not allowed with single unit files + if(dwFlags & MPQ_FILE_SINGLE_UNIT) + dwFlags &= ~MPQ_FILE_SECTOR_CRC; + + // Sector CRC is not allowed if the file is not compressed + if(!(dwFlags & MPQ_FILE_COMPRESSED)) + dwFlags &= ~MPQ_FILE_SECTOR_CRC; + + // Fix Key is not allowed if the file is not enrypted + if(!(dwFlags & MPQ_FILE_ENCRYPTED)) + dwFlags &= ~MPQ_FILE_FIX_KEY; // If the MPQ is of version 3.0 or higher, we ignore file locale. // This is because HET and BET tables have no known support for it @@ -371,12 +428,9 @@ int SFileAddFile_Init( } else { - // If the file is marked as deleted, it's OK - if(!(pFileEntry->dwFlags & MPQ_FILE_DELETE_MARKER)) - { - if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0) - nError = ERROR_ALREADY_EXISTS; - } + // If the file exists and "replace existing" is not set, fail it + if((dwFlags & MPQ_FILE_REPLACEEXISTING) == 0) + nError = ERROR_ALREADY_EXISTS; // If the file entry already contains a file // and it is a pseudo-name, replace it @@ -459,7 +513,7 @@ int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD d } // Allocate patch info, if the data is patch - if(hf->pPatchInfo == NULL && IsPatchData(pvData, dwSize, &hf->dwPatchedFileSize)) + if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize)) { // Set the MPQ_FILE_PATCH_FILE flag hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE; @@ -782,6 +836,9 @@ bool WINAPI SFileAddFileEx( DWORD dwBytesRemaining = 0; DWORD dwBytesToRead; DWORD dwSectorSize = 0x1000; + DWORD dwChannels = 0; + bool bIsAdpcmCompression = false; + bool bIsFirstSector = true; int nError = ERROR_SUCCESS; // Check parameters @@ -791,7 +848,7 @@ bool WINAPI SFileAddFileEx( // Open added file if(nError == ERROR_SUCCESS) { - pStream = FileStream_OpenFile(szFileName, false); + pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pStream == NULL) nError = GetLastError(); } @@ -799,7 +856,7 @@ bool WINAPI SFileAddFileEx( // Get the file size and file time if(nError == ERROR_SUCCESS) { - FileStream_GetLastWriteTime(pStream, &FileTime); + FileStream_GetTime(pStream, &FileTime); FileStream_GetSize(pStream, FileSize); // Files bigger than 4GB cannot be added to MPQ @@ -821,16 +878,19 @@ bool WINAPI SFileAddFileEx( { // When the compression for next blocks is set to default, // we will copy the compression for the first sector - if(dwCompressionNext == 0xFFFFFFFF) + if(dwCompressionNext == MPQ_COMPRESSION_NEXT_SAME) dwCompressionNext = dwCompression; - // If the caller wants ADPCM compression, we make sure that the first sector is not - // compressed with lossy compression - if(dwCompressionNext & (MPQ_COMPRESSION_WAVE_MONO | MPQ_COMPRESSION_WAVE_STEREO)) + // If the caller wants ADPCM compression, we make sure + // that the first sector is not compressed with lossy compression + if(dwCompressionNext & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) { // The first compression must not be WAVE - if(dwCompression & (MPQ_COMPRESSION_WAVE_MONO | MPQ_COMPRESSION_WAVE_STEREO)) + if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) dwCompression = MPQ_COMPRESSION_PKWARE; + + dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO); + bIsAdpcmCompression = true; } // Initiate adding file to the MPQ @@ -853,6 +913,21 @@ bool WINAPI SFileAddFileEx( break; } + // If the file being added is a WAVE file, we check number of channels + if(bIsFirstSector && bIsAdpcmCompression) + { + // The file must really be a wave file, otherwise it's data corruption + if(!IsWaveFile(pbFileData, dwBytesToRead, &dwChannels)) + { + nError = ERROR_BAD_FORMAT; + break; + } + + // Setup the compression according to number of channels + dwCompressionNext |= (dwChannels == 1) ? MPQ_COMPRESSION_ADPCM_MONO : MPQ_COMPRESSION_ADPCM_STEREO; + bIsFirstSector = false; + } + // Add the file sectors to the MPQ if(!SFileWriteFile(hMpqFile, pbFileData, dwBytesToRead, dwCompression)) { @@ -920,12 +995,12 @@ bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szA case MPQ_WAVE_QUALITY_MEDIUM: // WaveCompressionLevel = 4; - dwCompression = MPQ_COMPRESSION_WAVE_STEREO | MPQ_COMPRESSION_HUFFMANN; + dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN; break; case MPQ_WAVE_QUALITY_LOW: // WaveCompressionLevel = 2; - dwCompression = MPQ_COMPRESSION_WAVE_STEREO | MPQ_COMPRESSION_HUFFMANN; + dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN; break; } diff --git a/dep/StormLib/src/SFileAttributes.cpp b/dep/StormLib/src/SFileAttributes.cpp index 091950885c8..0f056088fa3 100644 --- a/dep/StormLib/src/SFileAttributes.cpp +++ b/dep/StormLib/src/SFileAttributes.cpp @@ -23,6 +23,7 @@ typedef struct _MPQ_ATTRIBUTES_HEADER // Followed by an array of CRC32 // Followed by an array of file times // Followed by an array of MD5 + // Followed by an array of patch bits } MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER; //----------------------------------------------------------------------------- @@ -147,6 +148,40 @@ int SAttrLoadAttributes(TMPQArchive * ha) nError = ERROR_NOT_ENOUGH_MEMORY; } + // Read the patch bit for each file + if(nError == ERROR_SUCCESS && (AttrHeader.dwFlags & MPQ_ATTRIBUTE_PATCH_BIT)) + { + LPBYTE pbBitArray; + DWORD dwByteSize = ((dwBlockTableSize - 1) / 8) + 1; + + pbBitArray = STORM_ALLOC(BYTE, dwByteSize); + if(pbBitArray != NULL) + { + SFileReadFile(hFile, pbBitArray, dwByteSize, &dwBytesRead, NULL); + if(dwBytesRead == dwByteSize) + { + for(i = 0; i < dwBlockTableSize; i++) + { + DWORD dwByteIndex = i / 8; + DWORD dwBitMask = 0x80 >> (i & 7); + + // Is the appropriate bit set? + if(pbBitArray[dwByteIndex] & dwBitMask) + { + // At the moment, we assume that the patch bit is present + // in both file table and (attributes) + assert((ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) != 0); + ha->pFileTable[i].dwFlags |= MPQ_FILE_PATCH_FILE; + } + } + } + else + nError = ERROR_FILE_CORRUPT; + + STORM_FREE(pbBitArray); + } + } + // // Note: Version 7.00 of StormLib saved the (attributes) incorrectly. // Sometimes, number of entries in the (attributes) was 1 item less @@ -174,6 +209,19 @@ int SAttrFileSaveToMpq(TMPQArchive * ha) DWORD i; int nError = ERROR_SUCCESS; + // Now we have to check if we need patch bits in the (attributes) + if(nError == ERROR_SUCCESS) + { + for(i = 0; i < ha->dwFileTableSize; i++) + { + if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) + { + ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; + break; + } + } + } + // If the (attributes) is not in the file table yet, // we have to increase the final block table size pFileEntry = GetFileEntryExact(ha, ATTRIBUTES_NAME, LANG_NEUTRAL); @@ -210,15 +258,20 @@ int SAttrFileSaveToMpq(TMPQArchive * ha) dwFileSize += dwFinalBlockTableSize * sizeof(ULONGLONG); if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) dwFileSize += dwFinalBlockTableSize * MD5_DIGEST_SIZE; + if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) + dwFileSize += ((dwFinalBlockTableSize - 1)) / 8 + 1; } + // Determine the flags for (attributes) + if(ha->dwFileFlags2 == 0) + ha->dwFileFlags2 = GetDefaultSpecialFileFlags(ha, dwFileSize); + // Create the attributes file in the MPQ - assert(ha->dwFileFlags2 != 0); nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME, 0, dwFileSize, LANG_NEUTRAL, - ha->dwFileFlags2, + ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING, &hf); // Write all parts of the (attributes) file @@ -226,8 +279,9 @@ int SAttrFileSaveToMpq(TMPQArchive * ha) { assert(ha->dwFileTableSize == dwFinalBlockTableSize); + // Note that we don't know what the new bit (0x08) means. AttrHeader.dwVersion = BSWAP_INT32_UNSIGNED(100); - AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED(ha->dwAttrFlags); + AttrHeader.dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL)); dwToWrite = sizeof(MPQ_ATTRIBUTES_HEADER); nError = SFileAddFile_Write(hf, &AttrHeader, dwToWrite, MPQ_COMPRESSION_ZLIB); } @@ -283,6 +337,30 @@ int SAttrFileSaveToMpq(TMPQArchive * ha) } } + // Write the array of patch bits + if(nError == ERROR_SUCCESS && (ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)) + { + LPBYTE pbBitArray; + DWORD dwByteSize = ((ha->dwFileTableSize - 1) / 8) + 1; + + pbBitArray = STORM_ALLOC(BYTE, dwByteSize); + if(pbBitArray != NULL) + { + memset(pbBitArray, 0, dwByteSize); + for(i = 0; i < ha->dwFileTableSize; i++) + { + DWORD dwByteIndex = i / 8; + DWORD dwBitMask = 0x80 >> (i & 7); + + if(ha->pFileTable[i].dwFlags & MPQ_FILE_PATCH_FILE) + pbBitArray[dwByteIndex] |= dwBitMask; + } + + nError = SFileAddFile_Write(hf, pbBitArray, dwByteSize, MPQ_COMPRESSION_ZLIB); + STORM_FREE(pbBitArray); + } + } + // Finalize the file in the archive if(hf != NULL) { diff --git a/dep/StormLib/src/SFileCompactArchive.cpp b/dep/StormLib/src/SFileCompactArchive.cpp index 09cb179bd74..319108b08f4 100644 --- a/dep/StormLib/src/SFileCompactArchive.cpp +++ b/dep/StormLib/src/SFileCompactArchive.cpp @@ -194,6 +194,7 @@ static int CopyMpqFileSectors( if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorOffsLen)) nError = GetLastError(); + dwBytesToCopy -= dwSectorOffsLen; dwCmpSize += dwSectorOffsLen; } @@ -216,10 +217,6 @@ static int CopyMpqFileSectors( DWORD dwRawDataInSector = hf->dwSectorSize; DWORD dwRawByteOffset = dwSector * hf->dwSectorSize; - // Last sector: If there is not enough bytes remaining in the file, cut the raw size - if(dwRawDataInSector > dwBytesToCopy) - dwRawDataInSector = dwBytesToCopy; - // Fix the raw data length if the file is compressed if(hf->SectorOffsets != NULL) { @@ -227,6 +224,10 @@ static int CopyMpqFileSectors( dwRawByteOffset = hf->SectorOffsets[dwSector]; } + // Last sector: If there is not enough bytes remaining in the file, cut the raw size + if(dwRawDataInSector > dwBytesToCopy) + dwRawDataInSector = dwBytesToCopy; + // Calculate the raw file offset of the file sector CalculateRawSectorOffset(RawFilePos, hf, dwRawByteOffset); @@ -263,7 +264,7 @@ static int CopyMpqFileSectors( } // Adjust byte counts - dwBytesToCopy -= hf->dwSectorSize; + dwBytesToCopy -= dwRawDataInSector; dwCmpSize += dwRawDataInSector; } } @@ -291,10 +292,39 @@ static int CopyMpqFileSectors( } // Size of the CRC block is also included in the compressed file size + dwBytesToCopy -= dwCrcLength; dwCmpSize += dwCrcLength; } } + // There might be extra data beyond sector checksum table + // Sometimes, these data are even part of sector offset table + // Examples: + // 2012 - WoW\15354\locale-enGB.MPQ:DBFilesClient\SpellLevels.dbc + // 2012 - WoW\15354\locale-enGB.MPQ:Interface\AddOns\Blizzard_AuctionUI\Blizzard_AuctionUI.xml + if(nError == ERROR_SUCCESS && dwBytesToCopy != 0) + { + LPBYTE pbExtraData; + + // Allocate space for the extra data + pbExtraData = STORM_ALLOC(BYTE, dwBytesToCopy); + if(pbExtraData != NULL) + { + if(!FileStream_Read(ha->pStream, NULL, pbExtraData, dwBytesToCopy)) + nError = GetLastError(); + + if(!FileStream_Write(pNewStream, NULL, pbExtraData, dwBytesToCopy)) + nError = GetLastError(); + + // Include these extra data in the compressed size + dwCmpSize += dwBytesToCopy; + dwBytesToCopy = 0; + STORM_FREE(pbExtraData); + } + else + nError = ERROR_NOT_ENOUGH_MEMORY; + } + // Write the MD5's of the raw file data, if needed if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0) { @@ -469,13 +499,13 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // Get the temporary file name and create it if(nError == ERROR_SUCCESS) { - _tcscpy(szTempFile, ha->pStream->szFileName); + _tcscpy(szTempFile, FileStream_GetFileName(ha->pStream)); if((szTemp = _tcsrchr(szTempFile, '.')) != NULL) _tcscpy(szTemp + 1, _T("mp_")); else _tcscat(szTempFile, _T("_")); - pTempStream = FileStream_CreateFile(szTempFile); + pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pTempStream == NULL) nError = GetLastError(); } @@ -531,7 +561,7 @@ bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bR // If succeeded, switch the streams if(nError == ERROR_SUCCESS) { - if(FileStream_MoveFile(ha->pStream, pTempStream)) + if(FileStream_Switch(ha->pStream, pTempStream)) pTempStream = NULL; else nError = ERROR_CAN_NOT_COMPLETE; diff --git a/dep/StormLib/src/SFileCreateArchive.cpp b/dep/StormLib/src/SFileCreateArchive.cpp index 6e8be7c46ec..59f3d3fa9e8 100644 --- a/dep/StormLib/src/SFileCreateArchive.cpp +++ b/dep/StormLib/src/SFileCreateArchive.cpp @@ -68,17 +68,46 @@ static int WriteNakedMPQHeader(TMPQArchive * ha) bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq) { + SFILE_CREATE_MPQ CreateInfo; + + // Fill the create structure + memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ)); + CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ); + CreateInfo.dwMpqVersion = (dwFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT; + CreateInfo.dwStreamFlags = STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE; + CreateInfo.dwAttrFlags = (dwFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_ATTRIBUTE_ALL : 0; + CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000; + CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0; + CreateInfo.dwMaxFileCount = dwMaxFileCount; + return SFileCreateArchive2(szMpqName, &CreateInfo, phMpq); +} + +bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq) +{ TFileStream * pStream = NULL; // File stream TMPQArchive * ha = NULL; // MPQ archive handle ULONGLONG MpqPos = 0; // Position of MPQ header in the file HANDLE hMpq = NULL; - USHORT wFormatVersion = MPQ_FORMAT_VERSION_1; DWORD dwBlockTableSize = 0; // Initial block table size DWORD dwHashTableSize = 0; + DWORD dwMaxFileCount; int nError = ERROR_SUCCESS; // Check the parameters, if they are valid - if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL) + if(szMpqName == NULL || *szMpqName == 0 || pCreateInfo == NULL || phMpq == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + // Verify if all variables in SFILE_CREATE_MPQ are correct + if((pCreateInfo->cbSize == 0 || pCreateInfo->cbSize > sizeof(SFILE_CREATE_MPQ)) || + (pCreateInfo->dwMpqVersion > MPQ_FORMAT_VERSION_4) || + (pCreateInfo->pvUserData != NULL || pCreateInfo->cbUserData != 0) || + (pCreateInfo->dwAttrFlags & ~MPQ_ATTRIBUTE_ALL) || + (pCreateInfo->dwSectorSize & (pCreateInfo->dwSectorSize - 1)) || + (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)) || + (pCreateInfo->dwMaxFileCount < 4)) { SetLastError(ERROR_INVALID_PARAMETER); return false; @@ -89,7 +118,7 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwM // We verify if the file already exists and if it's a MPQ archive. // If yes, we won't allow to overwrite it. - if(SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq)) + if(SFileOpenArchive(szMpqName, 0, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE | MPQ_OPEN_NO_ATTRIBUTES | MPQ_OPEN_NO_LISTFILE, &hMpq)) { SFileCloseArchive(hMpq); SetLastError(ERROR_ALREADY_EXISTS); @@ -102,25 +131,18 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwM // - If the file doesn't exist, create new empty file // - pStream = FileStream_OpenFile(szMpqName, true); + pStream = FileStream_OpenFile(szMpqName, pCreateInfo->dwStreamFlags); if(pStream == NULL) { - pStream = FileStream_CreateFile(szMpqName); + pStream = FileStream_CreateFile(szMpqName, pCreateInfo->dwStreamFlags); if(pStream == NULL) return false; } - // Decide what format to use - wFormatVersion = (USHORT)((dwFlags & MPQ_CREATE_ARCHIVE_VMASK) >> 16); - if(wFormatVersion > MPQ_FORMAT_VERSION_4) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - // Increment the maximum amount of files to have space // for listfile and attributes file - if(dwFlags & MPQ_CREATE_ATTRIBUTES) + dwMaxFileCount = pCreateInfo->dwMaxFileCount; + if(pCreateInfo->dwAttrFlags != 0) dwMaxFileCount++; dwMaxFileCount++; @@ -150,19 +172,18 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwM { memset(ha, 0, sizeof(TMPQArchive)); ha->pStream = pStream; - ha->dwSectorSize = (wFormatVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000; + ha->dwSectorSize = pCreateInfo->dwSectorSize; ha->UserDataPos = MpqPos; ha->MpqPos = MpqPos; ha->pHeader = (TMPQHeader *)ha->HeaderData; ha->dwMaxFileCount = dwMaxFileCount; ha->dwFileTableSize = 0; - ha->dwFileFlags1 = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING; - ha->dwFileFlags2 = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING; + ha->dwFileFlags1 = pCreateInfo->dwFileFlags1; + ha->dwFileFlags2 = pCreateInfo->dwFileFlags2; ha->dwFlags = 0; // Setup the attributes - if(dwFlags & MPQ_CREATE_ATTRIBUTES) - ha->dwAttrFlags = MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5; + ha->dwAttrFlags = pCreateInfo->dwAttrFlags; pStream = NULL; } @@ -174,9 +195,9 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwM // Fill the MPQ header memset(pHeader, 0, sizeof(ha->HeaderData)); pHeader->dwID = ID_MPQ; - pHeader->dwHeaderSize = MpqHeaderSizes[wFormatVersion]; + pHeader->dwHeaderSize = MpqHeaderSizes[pCreateInfo->dwMpqVersion]; pHeader->dwArchiveSize = pHeader->dwHeaderSize + dwHashTableSize * sizeof(TMPQHash); - pHeader->wFormatVersion = wFormatVersion; + pHeader->wFormatVersion = (USHORT)pCreateInfo->dwMpqVersion; pHeader->wSectorSize = GetSectorSizeShift(ha->dwSectorSize); pHeader->dwHashTablePos = pHeader->dwHeaderSize; pHeader->dwHashTableSize = dwHashTableSize; @@ -185,8 +206,8 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwM // For MPQs version 4 and higher, we set the size of raw data block // for calculating MD5 - if(wFormatVersion >= MPQ_FORMAT_VERSION_4) - pHeader->dwRawChunkSize = 0x4000; + if(pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_4) + pHeader->dwRawChunkSize = pCreateInfo->dwRawChunkSize; // Write the naked MPQ header nError = WriteNakedMPQHeader(ha); @@ -196,7 +217,7 @@ bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwM } // Create initial HET table, if the caller required an MPQ format 3.0 or newer - if(nError == ERROR_SUCCESS && wFormatVersion >= MPQ_FORMAT_VERSION_3) + if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3) { ha->pHetTable = CreateHetTable(ha->dwMaxFileCount, 0x40, true); if(ha->pHetTable == NULL) diff --git a/dep/StormLib/src/SFileExtractFile.cpp b/dep/StormLib/src/SFileExtractFile.cpp index f46ef178fbc..c8053ed4e62 100644 --- a/dep/StormLib/src/SFileExtractFile.cpp +++ b/dep/StormLib/src/SFileExtractFile.cpp @@ -28,7 +28,7 @@ bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR // Create the local file if(nError == ERROR_SUCCESS) { - pLocalFile = FileStream_CreateFile(szExtracted); + pLocalFile = FileStream_CreateFile(szExtracted, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pLocalFile == NULL) nError = GetLastError(); } diff --git a/dep/StormLib/src/SFileFindFile.cpp b/dep/StormLib/src/SFileFindFile.cpp index 542637b3668..337be7d2c54 100644 --- a/dep/StormLib/src/SFileFindFile.cpp +++ b/dep/StormLib/src/SFileFindFile.cpp @@ -31,7 +31,7 @@ struct TMPQSearch DWORD dwSearchTableItems; // Number of items in the search table DWORD dwNextIndex; // Next file index to be checked DWORD dwFlagMask; // For checking flag mask - char * szSearchMask; // Search mask (variable length) + char szSearchMask[1]; // Search mask (variable length) }; //----------------------------------------------------------------------------- @@ -69,7 +69,7 @@ bool CheckWildCard(const char * szString, const char * szWildCard) szString++; } - // If there is '*', means zero or more chars. We have to + // If there is '*', means zero or more chars. We have to // find the sequence after '*' if(*szWildCard == '*') { @@ -337,8 +337,6 @@ static void FreeMPQSearch(TMPQSearch *& hs) { if(hs->pSearchTable != NULL) STORM_FREE(hs->pSearchTable); - if(hs->szSearchMask != NULL) - free(hs->szSearchMask); // allocated with strdup STORM_FREE(hs); hs = NULL; } @@ -378,7 +376,7 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA if(nError == ERROR_SUCCESS) { memset(hs, 0, sizeof(TMPQSearch)); - hs->szSearchMask = strdup(szMask); + strcpy(&hs->szSearchMask[0], szMask); hs->dwFlagMask = MPQ_FILE_EXISTS; hs->ha = ha; @@ -408,7 +406,7 @@ HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DA FreeMPQSearch(hs); SetLastError(nError); } - + // Return the result value return (HANDLE)hs; } diff --git a/dep/StormLib/src/SFileListFile.cpp b/dep/StormLib/src/SFileListFile.cpp index 9f476d1e4a4..4604206ed0a 100644 --- a/dep/StormLib/src/SFileListFile.cpp +++ b/dep/StormLib/src/SFileListFile.cpp @@ -343,13 +343,16 @@ int SListFileSaveToMpq(TMPQArchive * ha) } } + // Determine the flags for (listfile) + if(ha->dwFileFlags1 == 0) + ha->dwFileFlags1 = GetDefaultSpecialFileFlags(ha, dwFileSize); + // Create the listfile in the MPQ - assert(ha->dwFileFlags1 != 0); nError = SFileAddFile_Init(ha, LISTFILE_NAME, 0, dwFileSize, LANG_NEUTRAL, - ha->dwFileFlags1, + ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING, &hf); // Add all file names if(nError == ERROR_SUCCESS) diff --git a/dep/StormLib/src/SFileOpenArchive.cpp b/dep/StormLib/src/SFileOpenArchive.cpp index d7b04aba70e..b36b7d35ea7 100644 --- a/dep/StormLib/src/SFileOpenArchive.cpp +++ b/dep/StormLib/src/SFileOpenArchive.cpp @@ -32,6 +32,34 @@ static bool IsAviFile(void * pvFileBegin) return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C); } +static TFileBitmap * CreateFileBitmap(TMPQArchive * ha, TMPQBitmap * pMpqBitmap, bool bFileIsComplete) +{ + TFileBitmap * pBitmap; + size_t nLength; + + // Calculate the length of the bitmap in blocks and in bytes + nLength = (size_t)(((ha->pHeader->ArchiveSize64 - 1) / pMpqBitmap->dwBlockSize) + 1); + nLength = (size_t)(((nLength - 1) / 8) + 1); + + // Allocate the file bitmap + pBitmap = (TFileBitmap *)STORM_ALLOC(BYTE, sizeof(TFileBitmap) + nLength); + if(pBitmap != NULL) + { + // Fill the structure + pBitmap->StartOffset = ha->MpqPos; + pBitmap->EndOffset = ha->MpqPos + ha->pHeader->ArchiveSize64; + pBitmap->IsComplete = bFileIsComplete ? 1 : 0; + pBitmap->BitmapSize = (DWORD)nLength; + pBitmap->BlockSize = pMpqBitmap->dwBlockSize; + pBitmap->Reserved = 0; + + // Copy the file bitmap + memcpy((pBitmap + 1), (pMpqBitmap + 1), nLength); + } + + return pBitmap; +} + // This function gets the right positions of the hash table and the block table. static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize) { @@ -91,19 +119,6 @@ static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize) // SFileGetLocale and SFileSetLocale // Set the locale for all newly opened files -DWORD WINAPI SFileGetGlobalFlags() -{ - return dwGlobalFlags; -} - -DWORD WINAPI SFileSetGlobalFlags(DWORD dwNewFlags) -{ - DWORD dwOldFlags = dwGlobalFlags; - - dwGlobalFlags = dwNewFlags; - return dwOldFlags; -} - LCID WINAPI SFileGetLocale() { return lcFileLocale; @@ -145,18 +160,10 @@ bool WINAPI SFileOpenArchive( // Open the MPQ archive file if(nError == ERROR_SUCCESS) { - if(!(dwFlags & MPQ_OPEN_ENCRYPTED)) - { - pStream = FileStream_OpenFile(szMpqName, (dwFlags & MPQ_OPEN_READ_ONLY) ? false : true); - if(pStream == NULL) - nError = GetLastError(); - } - else - { - pStream = FileStream_OpenEncrypted(szMpqName); - if(pStream == NULL) - nError = GetLastError(); - } + // Initialize the stream + pStream = FileStream_OpenFile(szMpqName, (dwFlags & STREAM_OPTIONS_MASK)); + if(pStream == NULL) + nError = GetLastError(); } // Allocate the MPQhandle @@ -175,7 +182,7 @@ bool WINAPI SFileOpenArchive( pStream = NULL; // Remember if the archive is open for write - if(ha->pStream->StreamFlags & (STREAM_FLAG_READ_ONLY | STREAM_FLAG_ENCRYPTED_FILE)) + if(FileStream_IsReadOnly(ha->pStream)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; // Also remember if we shall check sector CRCs when reading file @@ -276,10 +283,6 @@ bool WINAPI SFileOpenArchive( if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES)) ha->dwFlags |= MPQ_FLAG_READ_ONLY; - // Set the default file flags for (listfile) and (attributes) - ha->dwFileFlags1 = - ha->dwFileFlags2 = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING; - // Set the size of file sector ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize); @@ -287,6 +290,24 @@ bool WINAPI SFileOpenArchive( nError = VerifyMpqTablePositions(ha, FileSize); } + // Check if the MPQ has data bitmap. If yes, we can verify if the MPQ is complete + if(nError == ERROR_SUCCESS && ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4) + { + TFileBitmap * pBitmap; + bool bFileIsComplete = true; + + LoadMpqDataBitmap(ha, FileSize, &bFileIsComplete); + if(ha->pBitmap != NULL && bFileIsComplete == false) + { + // Convert the MPQ bitmap to the file bitmap + pBitmap = CreateFileBitmap(ha, ha->pBitmap, bFileIsComplete); + + // Set the data bitmap into the file stream for additional checks + FileStream_SetBitmap(ha->pStream, pBitmap); + ha->dwFlags |= MPQ_FLAG_READ_ONLY; + } + } + // Read the hash table. Ignore the result, as hash table is no longer required // Read HET table. Ignore the result, as HET table is no longer required if(nError == ERROR_SUCCESS) @@ -369,6 +390,16 @@ bool WINAPI SFileOpenArchive( } //----------------------------------------------------------------------------- +// SFileGetArchiveBitmap + +bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded) +{ + TMPQArchive * ha = (TMPQArchive *)hMpq; + + return FileStream_GetBitmap(ha->pStream, pBitmap, Length, LengthNeeded); +} + +//----------------------------------------------------------------------------- // bool SFileFlushArchive(HANDLE hMpq) // // Saves all dirty data into MPQ archive. diff --git a/dep/StormLib/src/SFileOpenFileEx.cpp b/dep/StormLib/src/SFileOpenFileEx.cpp index c35ac9f969c..e03a8b17998 100644 --- a/dep/StormLib/src/SFileOpenFileEx.cpp +++ b/dep/StormLib/src/SFileOpenFileEx.cpp @@ -29,10 +29,10 @@ static bool OpenLocalFile(const char * szFileName, HANDLE * phFile) for(i = 0; szFileName[i] != 0; i++) szFileNameT[i] = szFileName[i]; szFileNameT[i] = 0; - pStream = FileStream_OpenFile(szFileNameT, false); + pStream = FileStream_OpenFile(szFileNameT, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); #else - pStream = FileStream_OpenFile(szFileName, false); + pStream = FileStream_OpenFile(szFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); #endif if(pStream != NULL) @@ -73,7 +73,7 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN { // Construct the name of the patch file strcpy(szPatchFileName, ha->szPatchPrefix); - strcat(szPatchFileName, szFileName); + strcpy(&szPatchFileName[ha->cchPatchPrefix], szFileName); if(SFileOpenFileEx((HANDLE)ha, szPatchFileName, SFILE_OPEN_FROM_MPQ, (HANDLE *)&hfBase)) { // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE @@ -102,7 +102,7 @@ bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HAN { // Construct patch file name strcpy(szPatchFileName, ha->szPatchPrefix); - strcat(szPatchFileName, szFileName); + strcpy(&szPatchFileName[ha->cchPatchPrefix], szFileName); if(SFileOpenFileEx((HANDLE)ha, szPatchFileName, SFILE_OPEN_FROM_MPQ, &hPatchFile)) { // Remember the new version @@ -229,7 +229,11 @@ int WINAPI SFileEnumLocales( bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) { TMPQArchive * ha = (TMPQArchive *)hMpq; + TFileEntry * pFileEntry; + DWORD dwFlagsToCheck = MPQ_FILE_EXISTS; DWORD dwFileIndex = 0; + char szPatchFileName[MAX_PATH]; + bool bIsPseudoName; int nError = ERROR_SUCCESS; if(!IsValidMpqHandle(ha)) @@ -240,26 +244,39 @@ bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) // Prepare the file opening if(nError == ERROR_SUCCESS) { - if(!IsPseudoFileName(szFileName, &dwFileIndex)) - { - if(GetFileEntryLocale(ha, szFileName, lcFileLocale) == NULL) - { - nError = ERROR_FILE_NOT_FOUND; - } - } - else + // Different processing for pseudo-names + bIsPseudoName = IsPseudoFileName(szFileName, &dwFileIndex); + + // Walk through the MPQ and all patches + while(ha != NULL) { - if(GetFileEntryByIndex(ha, dwFileIndex) == NULL) + // Verify presence of the file + pFileEntry = (bIsPseudoName == false) ? GetFileEntryLocale(ha, szFileName, lcFileLocale) + : GetFileEntryByIndex(ha, dwFileIndex); + // Verify the file flags + if(pFileEntry != NULL && (pFileEntry->dwFlags & dwFlagsToCheck) == MPQ_FILE_EXISTS) + return true; + + // If this is patched archive, go to the patch + dwFlagsToCheck = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE; + ha = ha->haPatch; + + // Prepare the patched file name + if(ha != NULL) { - nError = ERROR_FILE_NOT_FOUND; + strcpy(szPatchFileName, ha->szPatchPrefix); + strcat(szPatchFileName, szFileName); + szFileName = szPatchFileName; } } + + // Not found, sorry + nError = ERROR_FILE_NOT_FOUND; } // Cleanup - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); + SetLastError(nError); + return false; } diff --git a/dep/StormLib/src/SFilePatchArchives.cpp b/dep/StormLib/src/SFilePatchArchives.cpp index 02f149be968..24ae2c52c37 100644 --- a/dep/StormLib/src/SFilePatchArchives.cpp +++ b/dep/StormLib/src/SFilePatchArchives.cpp @@ -400,7 +400,7 @@ static int ApplyMpqPatch( //----------------------------------------------------------------------------- // Public functions (StormLib internals) -bool IsPatchData(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize) +bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize) { TPatchHeader * pPatchHeader = (TPatchHeader *)pvData; BLIZZARD_BSDIFF40_FILE DiffFile; @@ -520,7 +520,7 @@ bool WINAPI SFileOpenPatchArchive( if(nError == ERROR_SUCCESS) { - if((ha->pStream->StreamFlags & STREAM_FLAG_READ_ONLY) == 0) + if(!FileStream_IsReadOnly(ha->pStream)) nError = ERROR_ACCESS_DENIED; } @@ -539,7 +539,7 @@ bool WINAPI SFileOpenPatchArchive( { if(!SFileHasFile(hPatchMpq, PATCH_METADATA_NAME)) { - GetDefaultPatchPrefix(ha->pStream->szFileName, szPatchPrefixBuff); + GetDefaultPatchPrefix(FileStream_GetFileName(ha->pStream), szPatchPrefixBuff); szPatchPathPrefix = szPatchPrefixBuff; } } diff --git a/dep/StormLib/src/SFileReadFile.cpp b/dep/StormLib/src/SFileReadFile.cpp index 8c6482c13d2..5570fd466c5 100644 --- a/dep/StormLib/src/SFileReadFile.cpp +++ b/dep/StormLib/src/SFileReadFile.cpp @@ -75,13 +75,14 @@ static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo if(hf->pStream != NULL) { // Calculate the length needed - cchCharsNeeded += _tcslen(hf->pStream->szFileName) + 1; + 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 = hf->pStream->szFileName) + 1; + nLength = _tcslen(szFileName) + 1; memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR)); szPatchChain += nLength; @@ -93,7 +94,7 @@ static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo { // Calculate number of characters needed for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) - cchCharsNeeded += _tcslen(hfTemp->ha->pStream->szFileName) + 1; + cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; cbLengthNeeded = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); // If we have enough space, the copy the patch chain @@ -101,7 +102,8 @@ static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo { for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatchFile) { - nLength = _tcslen(szFileName = hfTemp->ha->pStream->szFileName) + 1; + szFileName = FileStream_GetFileName(hfTemp->ha->pStream); + nLength = _tcslen(szFileName) + 1; memcpy(szPatchChain, szFileName, nLength * sizeof(TCHAR)); szPatchChain += nLength; } @@ -265,15 +267,19 @@ static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DW 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) { - hf->PreviousCompression = pbInSector[0]; - nResult = SCompDecompress((char *)pbOutSector, &cbOutSector, (char *)pbInSector, cbInSector); + 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 ? @@ -314,7 +320,6 @@ static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos 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. @@ -334,13 +339,12 @@ static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos if(hf->dwSectorOffs != 0) { // Is the file compressed? - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) + 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; - bIsReallyCompressed = true; pbRawData = pbCompressed; } @@ -359,47 +363,43 @@ static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos 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) + if(pFileEntry->dwFlags & MPQ_FILE_COMPRESSED) { int cbOutBuffer = (int)hf->dwDataSize; + int cbInBuffer = (int)pFileEntry->dwCmpSize; + int nResult = 0; - // 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 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(!SCompDecompress((char *)hf->pbFileSector, &cbOutBuffer, (char *)pbRawData, (int)pFileEntry->dwCmpSize)) - nError = ERROR_FILE_CORRUPT; + 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 { @@ -938,7 +938,7 @@ bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) if(pFileEntry != NULL && pFileEntry->szFileName != NULL) strcpy(szFileName, pFileEntry->szFileName); else if(hf->pStream != NULL) - CopyFileName(szFileName, hf->pStream->szFileName); + CopyFileName(szFileName, FileStream_GetFileName(hf->pStream)); } return (nError == ERROR_SUCCESS); } @@ -988,8 +988,8 @@ bool WINAPI SFileGetFileInfo( VERIFY_MPQ_HANDLE(ha); // pvFileInfo receives the name of the archive, terminated by 0 - cbLengthNeeded = (DWORD)(_tcslen(ha->pStream->szFileName) + 1) * sizeof(TCHAR); - pvSrcFileInfo = ha->pStream->szFileName; + pvSrcFileInfo = FileStream_GetFileName(ha->pStream); + cbLengthNeeded = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); break; case SFILE_INFO_ARCHIVE_SIZE: // Size of the archive @@ -1056,15 +1056,13 @@ bool WINAPI SFileGetFileInfo( pvSrcFileInfo = &dwFileCount; break; - case SFILE_INFO_STREAM_FLAGS: // Stream flags for the MPQ. See STREAM_FLAG_XXX - VERIFY_MPQ_HANDLE(ha); - cbLengthNeeded = sizeof(DWORD); - pvSrcFileInfo = &ha->pStream->StreamFlags; + case SFILE_INFO_STREAM_FLAGS: // Deprecated + nError = ERROR_INVALID_PARAMETER; break; case SFILE_INFO_IS_READ_ONLY: VERIFY_MPQ_HANDLE(ha); - dwIsReadOnly = ((ha->pStream->StreamFlags & STREAM_FLAG_READ_ONLY) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); + dwIsReadOnly = (FileStream_IsReadOnly(ha->pStream) || (ha->dwFlags & MPQ_FLAG_READ_ONLY)); cbLengthNeeded = sizeof(DWORD); pvSrcFileInfo = &dwIsReadOnly; break; diff --git a/dep/StormLib/src/SFileVerify.cpp b/dep/StormLib/src/SFileVerify.cpp index 2190caa6994..7457171d88f 100644 --- a/dep/StormLib/src/SFileVerify.cpp +++ b/dep/StormLib/src/SFileVerify.cpp @@ -169,9 +169,7 @@ static void CalculateArchiveRange( TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSI) { - TMPQHeader * pHeader = ha->pHeader; ULONGLONG TempPos = 0; - ULONGLONG MaxPos; char szMapHeader[0x200]; // Get the MPQ begin @@ -188,31 +186,9 @@ static void CalculateArchiveRange( } } - // Get the MPQ data end. The end is calculated as the biggest - // value of (end of the last file), (end of block table), - // (end of ext block table), (end of hash table) - FindFreeMpqSpace(ha, &MaxPos); - - // Check if hash table is beyond - TempPos = ha->MpqPos + MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos) + pHeader->HashTableSize64; - if(TempPos > MaxPos) - MaxPos = TempPos; - - // Check if block table is beyond - TempPos = ha->MpqPos + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos) + pHeader->BlockTableSize64; - if(TempPos > MaxPos) - MaxPos = TempPos; - - // Check if ext block table is beyond - if(pHeader->HiBlockTablePos64 != 0) - { - TempPos = ha->MpqPos + pHeader->HiBlockTablePos64 + pHeader->HiBlockTableSize64; - if(TempPos > MaxPos) - MaxPos = TempPos; - } - - // Give the end - pSI->EndMpqData = MaxPos; + // Get the MPQ data end. This is stored in our MPQ header, + // and it's been already prepared by SFileOpenArchive, + pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64; // Get the size of the entire file FileStream_GetSize(ha->pStream, pSI->EndOfFile); @@ -421,7 +397,7 @@ static bool CalculateMpqHashSha1( sha1_done(&sha1_state_temp, sha1_tail0); memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); - GetPlainAnsiFileName(ha->pStream->szFileName, szPlainName); + GetPlainAnsiFileName(FileStream_GetFileName(ha->pStream), szPlainName); AddTailToSha1(&sha1_state_temp, szPlainName); sha1_done(&sha1_state_temp, sha1_tail1); @@ -449,11 +425,13 @@ static int VerifyRawMpqData( DWORD dwMD5Size; int nError = ERROR_SUCCESS; + // Don't verify zero-sized blocks + if(dwDataSize == 0) + return ERROR_SUCCESS; + // Get the number of data chunks to calculate MD5 assert(dwChunkSize != 0); - dwChunkCount = dwDataSize / dwChunkSize; - if(dwDataSize % dwChunkSize) - dwChunkCount++; + dwChunkCount = ((dwDataSize - 1) / dwChunkSize) + 1; dwMD5Size = dwChunkCount * MD5_DIGEST_SIZE; // Allocate space for data chunk and for the MD5 array @@ -659,6 +637,35 @@ static DWORD VerifyFile( if(SFileIsPatchedArchive(hMpq)) dwSearchScope = SFILE_OPEN_PATCHED_FILE; + // If we have to verify raw data MD5, do it before file open + if(dwFlags & SFILE_VERIFY_RAW_MD5) + { + TMPQArchive * ha = (TMPQArchive *)hMpq; + + // Parse the base MPQ and all patches + while(ha != NULL) + { + // Does the archive have support for raw MD5? + if(ha->pHeader->dwRawChunkSize != 0) + { + // The file has raw MD5 if the archive supports it + dwVerifyResult |= VERIFY_FILE_HAS_RAW_MD5; + + // Find file entry for the file + pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); + if(pFileEntry != NULL) + { + // If the file's raw MD5 doesn't match, don't bother with more checks + if(VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize) != ERROR_SUCCESS) + return dwVerifyResult | VERIFY_FILE_RAW_MD5_ERROR; + } + } + + // Move to the next patch + ha = ha->haPatch; + } + } + // Attempt to open the file if(SFileOpenFileEx(hMpq, szFileName, dwSearchScope, &hFile)) { @@ -671,18 +678,6 @@ static DWORD VerifyFile( md5_init(&md5_state); dwCrc32 = crc32(0, Z_NULL, 0); - // If we have to verify raw data MD5, do it - if(dwFlags & SFILE_VERIFY_RAW_MD5) - { - if(hf->ha->pHeader->dwRawChunkSize != 0) - { - // Note: we have to open the file from the MPQ where it was open from - if(VerifyRawMpqData(hf->ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize) != ERROR_SUCCESS) - dwVerifyResult |= VERIFY_FILE_RAW_MD5_ERROR; - dwVerifyResult |= VERIFY_FILE_HAS_RAW_MD5; - } - } - // Also turn on sector checksum verification if(dwFlags & SFILE_VERIFY_SECTOR_CRC) hf->bCheckSectorCRCs = true; diff --git a/dep/StormLib/src/StormCommon.h b/dep/StormLib/src/StormCommon.h index 278c44ed810..9a85c4bd5ac 100644 --- a/dep/StormLib/src/StormCommon.h +++ b/dep/StormLib/src/StormCommon.h @@ -109,7 +109,6 @@ __inline void DebugFree(void * ptr) //----------------------------------------------------------------------------- // StormLib internal global variables -extern DWORD dwGlobalFlags; // Global StormLib flags extern LCID lcFileLocale; // Preferred file locale //----------------------------------------------------------------------------- @@ -122,7 +121,7 @@ extern LCID lcFileLocale; // Preferred file locale DWORD HashString(const char * szFileName, DWORD dwHashType); -void InitializeMpqCryptography(); +void InitializeMpqCryptography(); DWORD GetHashTableSizeForFileCount(DWORD dwFileCount); @@ -131,6 +130,8 @@ ULONGLONG HashStringJenkins(const char * szFileName); int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); +DWORD GetDefaultSpecialFileFlags(TMPQArchive * ha, DWORD dwFileSize); + void EncryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey); void DecryptMpqBlock(void * pvFileBlock, DWORD dwLength, DWORD dwKey); @@ -158,7 +159,10 @@ DWORD AllocateHetEntry(TMPQArchive * ha, TFileEntry * pFileEntry); void FindFreeMpqSpace(TMPQArchive * ha, ULONGLONG * pFreeSpacePos); -// Functions that load the HET abd BET tables +// Functions that loads and verifies MPQ data bitmap +int LoadMpqDataBitmap(TMPQArchive * ha, ULONGLONG FileSize, bool * pbFileIsComplete); + +// Functions that load the HET and BET tables int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize); int LoadAnyHashTable(TMPQArchive * ha); int BuildFileTable(TMPQArchive * ha, ULONGLONG FileSize); @@ -206,7 +210,7 @@ int WriteMemDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, void * pvRawD int WriteMpqDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, DWORD dwRawDataSize, DWORD dwChunkSize); void FreeMPQFile(TMPQFile *& hf); -bool IsPatchData(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize); +bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize); int PatchFileData(TMPQFile * hf); void FreeMPQArchive(TMPQArchive *& ha); diff --git a/dep/StormLib/src/StormLib.h b/dep/StormLib/src/StormLib.h index 1f75ffd26c1..a07ae46ca48 100644 --- a/dep/StormLib/src/StormLib.h +++ b/dep/StormLib/src/StormLib.h @@ -64,15 +64,16 @@ /* 20.09.10 8.00 Lad MPQs v 4, HET and BET tables */ /* 07.01.11 8.01 Lad Write support for MPQs v 3 and 4 */ /* 15.09.11 8.04 Lad Bug fixes, testing for Diablo III MPQs */ +/* 26.04.12 8.10 Lad Support for data map, added SFileGetArchiveBitmap */ /*****************************************************************************/ #ifndef __STORMLIB_H__ #define __STORMLIB_H__ #ifdef _MSC_VER -#pragma warning(disable:4668) // 'XXX' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +#pragma warning(disable:4668) // 'XXX' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' #pragma warning(disable:4820) // 'XXX' : '2' bytes padding added after data member 'XXX::yyy' -#endif +#endif #include "StormPort.h" @@ -85,118 +86,111 @@ extern "C" { // // The library type is encoded in the library name as the following // StormLibXYZ.lib -// +// // X - D for Debug version, R for Release version // Y - A for ANSI version, U for Unicode version // Z - S for static-linked CRT library, D for multithreaded DLL CRT library // -#if 0 -#if defined(_MSC_VER) && !defined(__STORMLIB_SELF__) - - #ifdef _DEBUG // DEBUG VERSIONS - #ifndef _UNICODE - #ifdef _DLL - #pragma comment(lib, "StormLibDAD.lib") // Debug Ansi CRT-DLL version - #else - #pragma comment(lib, "StormLibDAS.lib") // Debug Ansi CRT-LIB version +#if 0 && defined(_MSC_VER) && !defined(__STORMLIB_SELF__) + + #ifdef _DEBUG // DEBUG VERSIONS + #ifndef _UNICODE + #ifdef _DLL + #pragma comment(lib, "StormLibDAD.lib") // Debug Ansi CRT-DLL version + #else + #pragma comment(lib, "StormLibDAS.lib") // Debug Ansi CRT-LIB version #endif #else - #ifdef _DLL - #pragma comment(lib, "StormLibDUD.lib") // Debug Unicode CRT-DLL version - #else - #pragma comment(lib, "StormLibDUS.lib") // Debug Unicode CRT-LIB version + #ifdef _DLL + #pragma comment(lib, "StormLibDUD.lib") // Debug Unicode CRT-DLL version + #else + #pragma comment(lib, "StormLibDUS.lib") // Debug Unicode CRT-LIB version #endif #endif - #else // RELEASE VERSIONS - #ifndef _UNICODE + #else // RELEASE VERSIONS + #ifndef _UNICODE #ifdef _DLL - #pragma comment(lib, "StormLibRAD.lib") // Release Ansi CRT-DLL version - #else - #pragma comment(lib, "StormLibRAS.lib") // Release Ansi CRT-LIB version + #pragma comment(lib, "StormLibRAD.lib") // Release Ansi CRT-DLL version + #else + #pragma comment(lib, "StormLibRAS.lib") // Release Ansi CRT-LIB version #endif #else #ifdef _DLL - #pragma comment(lib, "StormLibRUD.lib") // Release Unicode CRT-DLL version - #else - #pragma comment(lib, "StormLibRUS.lib") // Release Unicode CRT-LIB version + #pragma comment(lib, "StormLibRUD.lib") // Release Unicode CRT-DLL version + #else + #pragma comment(lib, "StormLibRUS.lib") // Release Unicode CRT-LIB version #endif #endif #endif #endif -#endif //----------------------------------------------------------------------------- // Defines -#define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A') -#define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B') +#define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A') +#define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B') -#define ERROR_AVI_FILE 10000 // No MPQ file, but AVI file. -#define ERROR_UNKNOWN_FILE_KEY 10001 // Returned by SFileReadFile when can't find file key -#define ERROR_CHECKSUM_ERROR 10002 // Returned by SFileReadFile when sector CRC doesn't match -#define ERROR_INTERNAL_FILE 10003 // The given operation is not allowed on internal file -#define ERROR_BASE_FILE_MISSING 10004 // The file is present as incremental patch file, but base file is missing -#define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ +#define ERROR_AVI_FILE 10000 // No MPQ file, but AVI file. +#define ERROR_UNKNOWN_FILE_KEY 10001 // Returned by SFileReadFile when can't find file key +#define ERROR_CHECKSUM_ERROR 10002 // Returned by SFileReadFile when sector CRC doesn't match +#define ERROR_INTERNAL_FILE 10003 // The given operation is not allowed on internal file +#define ERROR_BASE_FILE_MISSING 10004 // The file is present as incremental patch file, but base file is missing +#define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ // Values for SFileCreateArchive -#define HASH_TABLE_SIZE_MIN 0x00000004 // Minimum acceptable hash table size -#define HASH_TABLE_SIZE_DEFAULT 0x00001000 // Default hash table size for empty MPQs -#define HASH_TABLE_SIZE_MAX 0x00080000 // Maximum acceptable hash table size +#define HASH_TABLE_SIZE_MIN 0x00000004 // Minimum acceptable hash table size +#define HASH_TABLE_SIZE_DEFAULT 0x00001000 // Default hash table size for empty MPQs +#define HASH_TABLE_SIZE_MAX 0x00080000 // Maximum acceptable hash table size -#define HASH_ENTRY_DELETED 0xFFFFFFFE // Block index for deleted entry in the hash table -#define HASH_ENTRY_FREE 0xFFFFFFFF // Block index for free entry in the hash table +#define HASH_ENTRY_DELETED 0xFFFFFFFE // Block index for deleted entry in the hash table +#define HASH_ENTRY_FREE 0xFFFFFFFF // Block index for free entry in the hash table -#define HET_ENTRY_DELETED 0x80 // HET hash value for a deleted entry -#define HET_ENTRY_FREE 0x00 // HET hash value for free entry +#define HET_ENTRY_DELETED 0x80 // HET hash value for a deleted entry +#define HET_ENTRY_FREE 0x00 // HET hash value for free entry -#define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure +#define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure -#define MPQ_PATCH_PREFIX_LEN 0x20 // Maximum length of the patch prefix - -// Values for TFileStream::Flags -#define STREAM_FLAG_READ_ONLY 0x01 // The stream is read only -#define STREAM_FLAG_PART_FILE 0x02 // The stream is a PART file. -#define STREAM_FLAG_ENCRYPTED_FILE 0x04 // The stream is an encrypted MPQ (MPQE). +#define MPQ_PATCH_PREFIX_LEN 0x20 // Maximum length of the patch prefix // Values for SFileOpenArchive -#define SFILE_OPEN_HARD_DISK_FILE 2 // Open the archive on HDD -#define SFILE_OPEN_CDROM_FILE 3 // Open the archive only if it is on CDROM +#define SFILE_OPEN_HARD_DISK_FILE 2 // Open the archive on HDD +#define SFILE_OPEN_CDROM_FILE 3 // Open the archive only if it is on CDROM // Values for SFileOpenFile -#define SFILE_OPEN_FROM_MPQ 0x00000000 // Open the file from the MPQ archive -#define SFILE_OPEN_PATCHED_FILE 0x00000001 // Open the file from the MPQ archive -#define SFILE_OPEN_ANY_LOCALE 0xFFFFFFFE // Reserved for StormLib internal use -#define SFILE_OPEN_LOCAL_FILE 0xFFFFFFFF // Open a local file +#define SFILE_OPEN_FROM_MPQ 0x00000000 // Open the file from the MPQ archive +#define SFILE_OPEN_PATCHED_FILE 0x00000001 // Open the file from the MPQ archive +#define SFILE_OPEN_ANY_LOCALE 0xFFFFFFFE // Reserved for StormLib internal use +#define SFILE_OPEN_LOCAL_FILE 0xFFFFFFFF // Open a local file // Flags for TMPQArchive::dwFlags -#define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access -#define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed -#define MPQ_FLAG_PROTECTED 0x00000004 // Set on protected MPQs (like W3M maps) -#define MPQ_FLAG_CHECK_SECTOR_CRC 0x0000008 // Checking sector CRC when reading files -#define MPQ_FLAG_NEED_FIX_SIZE 0x00000010 // Used during opening the archive -#define MPQ_FLAG_INV_LISTFILE 0x00000020 // If set, it means that the (listfile) has been invalidated -#define MPQ_FLAG_INV_ATTRIBUTES 0x00000040 // If set, it means that the (attributes) has been invalidated - -// Return value for SFilGetFileSize and SFileSetFilePointer -#define SFILE_INVALID_SIZE 0xFFFFFFFF -#define SFILE_INVALID_POS 0xFFFFFFFF -#define SFILE_INVALID_ATTRIBUTES 0xFFFFFFFF +#define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access +#define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed +#define MPQ_FLAG_PROTECTED 0x00000004 // Set on protected MPQs (like W3M maps) +#define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000008 // Checking sector CRC when reading files +#define MPQ_FLAG_NEED_FIX_SIZE 0x00000010 // Used during opening the archive +#define MPQ_FLAG_INV_LISTFILE 0x00000020 // If set, it means that the (listfile) has been invalidated +#define MPQ_FLAG_INV_ATTRIBUTES 0x00000040 // If set, it means that the (attributes) has been invalidated + +// Return value for SFileGetFileSize and SFileSetFilePointer +#define SFILE_INVALID_SIZE 0xFFFFFFFF +#define SFILE_INVALID_POS 0xFFFFFFFF +#define SFILE_INVALID_ATTRIBUTES 0xFFFFFFFF // Flags for SFileAddFile -#define MPQ_FILE_IMPLODE 0x00000100 // Implode method (By PKWARE Data Compression Library) -#define MPQ_FILE_COMPRESS 0x00000200 // Compress methods (By multiple methods) -#define MPQ_FILE_COMPRESSED 0x0000FF00 // File is compressed -#define MPQ_FILE_ENCRYPTED 0x00010000 // Indicates whether file is encrypted -#define MPQ_FILE_FIX_KEY 0x00020000 // File decryption key has to be fixed -#define MPQ_FILE_PATCH_FILE 0x00100000 // The file is a patch file. Raw file data begin with TPatchInfo structure -#define MPQ_FILE_SINGLE_UNIT 0x01000000 // File is stored as a single unit, rather than split into sectors (Thx, Quantam) -#define MPQ_FILE_DELETE_MARKER 0x02000000 // File is a deletion marker, indicating that the file no longer exists. -#define MPQ_FILE_SECTOR_CRC 0x04000000 // File has checksums for each sector. - // Ignored if file is not compressed or imploded. -#define MPQ_FILE_EXISTS 0x80000000 // Set if file exists, reset when the file was deleted -#define MPQ_FILE_REPLACEEXISTING 0x80000000 // Replace when the file exist (SFileAddFile) +#define MPQ_FILE_IMPLODE 0x00000100 // Implode method (By PKWARE Data Compression Library) +#define MPQ_FILE_COMPRESS 0x00000200 // Compress methods (By multiple methods) +#define MPQ_FILE_COMPRESSED 0x0000FF00 // File is compressed +#define MPQ_FILE_ENCRYPTED 0x00010000 // Indicates whether file is encrypted +#define MPQ_FILE_FIX_KEY 0x00020000 // File decryption key has to be fixed +#define MPQ_FILE_PATCH_FILE 0x00100000 // The file is a patch file. Raw file data begin with TPatchInfo structure +#define MPQ_FILE_SINGLE_UNIT 0x01000000 // File is stored as a single unit, rather than split into sectors (Thx, Quantam) +#define MPQ_FILE_DELETE_MARKER 0x02000000 // File is a deletion marker. Used in MPQ patches, indicating that the file no longer exists. +#define MPQ_FILE_SECTOR_CRC 0x04000000 // File has checksums for each sector. + // Ignored if file is not compressed or imploded. +#define MPQ_FILE_EXISTS 0x80000000 // Set if file exists, reset when the file was deleted +#define MPQ_FILE_REPLACEEXISTING 0x80000000 // Replace when the file exist (SFileAddFile) #define MPQ_FILE_VALID_FLAGS (MPQ_FILE_IMPLODE | \ MPQ_FILE_COMPRESS | \ @@ -209,211 +203,180 @@ extern "C" { MPQ_FILE_EXISTS) // Compression types for multiple compressions -#define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only) -#define MPQ_COMPRESSION_ZLIB 0x02 // ZLIB compression -#define MPQ_COMPRESSION_PKWARE 0x08 // PKWARE DCL compression -#define MPQ_COMPRESSION_BZIP2 0x10 // BZIP2 compression (added in Warcraft III) -#define MPQ_COMPRESSION_SPARSE 0x20 // Sparse compression (added in Starcraft 2) -#define MPQ_COMPRESSION_ADPCM_MONO 0x40 // IMA ADPCM compression (mono) -#define MPQ_COMPRESSION_ADPCM_STEREO 0x80 // IMA ADPCM compression (stereo) - -// For backward compatibility -#define MPQ_COMPRESSION_WAVE_MONO 0x40 // IMA ADPCM compression (mono) -#define MPQ_COMPRESSION_WAVE_STEREO 0x80 // IMA ADPCM compression (stereo) - -// LZMA compression. Added in Starcraft 2. This value is NOT a combination of flags. -#define MPQ_COMPRESSION_LZMA 0x12 +#define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only) +#define MPQ_COMPRESSION_ZLIB 0x02 // ZLIB compression +#define MPQ_COMPRESSION_PKWARE 0x08 // PKWARE DCL compression +#define MPQ_COMPRESSION_BZIP2 0x10 // BZIP2 compression (added in Warcraft III) +#define MPQ_COMPRESSION_SPARSE 0x20 // Sparse compression (added in Starcraft 2) +#define MPQ_COMPRESSION_ADPCM_MONO 0x40 // IMA ADPCM compression (mono) +#define MPQ_COMPRESSION_ADPCM_STEREO 0x80 // IMA ADPCM compression (stereo) +#define MPQ_COMPRESSION_LZMA 0x12 // LZMA compression. Added in Starcraft 2. This value is NOT a combination of flags. +#define MPQ_COMPRESSION_NEXT_SAME 0xFFFFFFFF // Same compression // Constants for SFileAddWave -#define MPQ_WAVE_QUALITY_HIGH 0 // Best quality, the worst compression -#define MPQ_WAVE_QUALITY_MEDIUM 1 // Medium quality, medium compression -#define MPQ_WAVE_QUALITY_LOW 2 // Low quality, the best compression +#define MPQ_WAVE_QUALITY_HIGH 0 // Best quality, the worst compression +#define MPQ_WAVE_QUALITY_MEDIUM 1 // Medium quality, medium compression +#define MPQ_WAVE_QUALITY_LOW 2 // Low quality, the best compression // Signatures for HET and BET table -#define HET_TABLE_SIGNATURE 0x1A544548 // 'HET\x1a' -#define BET_TABLE_SIGNATURE 0x1A544542 // 'BET\x1a' +#define HET_TABLE_SIGNATURE 0x1A544548 // 'HET\x1a' +#define BET_TABLE_SIGNATURE 0x1A544542 // 'BET\x1a' // Decryption keys for MPQ tables -#define MPQ_KEY_HASH_TABLE 0xC3AF3770 // Obtained by HashString("(hash table)", MPQ_HASH_FILE_KEY) -#define MPQ_KEY_BLOCK_TABLE 0xEC83B3A3 // Obtained by HashString("(block table)", MPQ_HASH_FILE_KEY) +#define MPQ_KEY_HASH_TABLE 0xC3AF3770 // Obtained by HashString("(hash table)", MPQ_HASH_FILE_KEY) +#define MPQ_KEY_BLOCK_TABLE 0xEC83B3A3 // Obtained by HashString("(block table)", MPQ_HASH_FILE_KEY) + +// Block map defines +#define MPQ_DATA_BITMAP_SIGNATURE 0x33767470 // Signature of the MPQ data bitmap ('ptv3') // Constants for SFileGetFileInfo -#define SFILE_INFO_ARCHIVE_NAME 1 // MPQ size (value from header) -#define SFILE_INFO_ARCHIVE_SIZE 2 // MPQ size (value from header) -#define SFILE_INFO_MAX_FILE_COUNT 3 // Max number of files in the MPQ -#define SFILE_INFO_HASH_TABLE_SIZE 4 // Size of hash table, in entries -#define SFILE_INFO_BLOCK_TABLE_SIZE 5 // Number of entries in the block table -#define SFILE_INFO_SECTOR_SIZE 6 // Size of file sector (in bytes) -#define SFILE_INFO_HASH_TABLE 7 // Pointer to Hash table (TMPQHash *) -#define SFILE_INFO_BLOCK_TABLE 8 // Pointer to Block Table (TMPQBlock *) -#define SFILE_INFO_NUM_FILES 9 // Real number of files within archive -#define SFILE_INFO_STREAM_FLAGS 10 // Stream flags for the MPQ. See STREAM_FLAG_XXX -#define SFILE_INFO_IS_READ_ONLY 11 // TRUE of the MPQ was open as read only -//------ -#define SFILE_INFO_HASH_INDEX 100 // Hash index of file in MPQ -#define SFILE_INFO_CODENAME1 101 // The first codename of the file -#define SFILE_INFO_CODENAME2 102 // The second codename of the file -#define SFILE_INFO_LOCALEID 103 // Locale ID of file in MPQ -#define SFILE_INFO_BLOCKINDEX 104 // Index to Block Table -#define SFILE_INFO_FILE_SIZE 105 // Original file size (from the block table) -#define SFILE_INFO_COMPRESSED_SIZE 106 // Compressed file size (from the block table) -#define SFILE_INFO_FLAGS 107 // File flags -#define SFILE_INFO_POSITION 108 // File position within archive -#define SFILE_INFO_KEY 109 // File decryption key -#define SFILE_INFO_KEY_UNFIXED 110 // Decryption key not fixed to file pos and size -#define SFILE_INFO_FILETIME 111 // TMPQFileTime -#define SFILE_INFO_PATCH_CHAIN 112 // Chain of patches - -#define LISTFILE_NAME "(listfile)" // Name of internal listfile -#define SIGNATURE_NAME "(signature)" // Name of internal signature -#define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file +#define SFILE_INFO_ARCHIVE_NAME 1 // MPQ size (value from header) +#define SFILE_INFO_ARCHIVE_SIZE 2 // MPQ size (value from header) +#define SFILE_INFO_MAX_FILE_COUNT 3 // Max number of files in the MPQ +#define SFILE_INFO_HASH_TABLE_SIZE 4 // Size of hash table, in entries +#define SFILE_INFO_BLOCK_TABLE_SIZE 5 // Number of entries in the block table +#define SFILE_INFO_SECTOR_SIZE 6 // Size of file sector (in bytes) +#define SFILE_INFO_HASH_TABLE 7 // Pointer to Hash table (TMPQHash *) +#define SFILE_INFO_BLOCK_TABLE 8 // Pointer to Block Table (TMPQBlock *) +#define SFILE_INFO_NUM_FILES 9 // Real number of files within archive +#define SFILE_INFO_STREAM_FLAGS 10 // Stream flags for the MPQ. See STREAM_FLAG_XXX +#define SFILE_INFO_IS_READ_ONLY 11 // TRUE of the MPQ was open as read only +//------ +#define SFILE_INFO_HASH_INDEX 100 // Hash index of file in MPQ +#define SFILE_INFO_CODENAME1 101 // The first codename of the file +#define SFILE_INFO_CODENAME2 102 // The second codename of the file +#define SFILE_INFO_LOCALEID 103 // Locale ID of file in MPQ +#define SFILE_INFO_BLOCKINDEX 104 // Index to Block Table +#define SFILE_INFO_FILE_SIZE 105 // Original file size (from the block table) +#define SFILE_INFO_COMPRESSED_SIZE 106 // Compressed file size (from the block table) +#define SFILE_INFO_FLAGS 107 // File flags +#define SFILE_INFO_POSITION 108 // File position within archive +#define SFILE_INFO_KEY 109 // File decryption key +#define SFILE_INFO_KEY_UNFIXED 110 // Decryption key not fixed to file pos and size +#define SFILE_INFO_FILETIME 111 // TMPQFileTime +#define SFILE_INFO_PATCH_CHAIN 112 // Chain of patches + +#define LISTFILE_NAME "(listfile)" // Name of internal listfile +#define SIGNATURE_NAME "(signature)" // Name of internal signature +#define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file #define PATCH_METADATA_NAME "(patch_metadata)" -#define STORMLIB_VERSION 0x0804 // Current version of StormLib (8.04) -#define STORMLIB_VERSION_STRING "8.04" +#define STORMLIB_VERSION 0x080A // Current version of StormLib (8.10) +#define STORMLIB_VERSION_STRING "8.10" -#define MPQ_FORMAT_VERSION_1 0 // Up to The Burning Crusade -#define MPQ_FORMAT_VERSION_2 1 // The Burning Crusade and newer -#define MPQ_FORMAT_VERSION_3 2 // WoW Cataclysm Beta -#define MPQ_FORMAT_VERSION_4 3 // WoW Cataclysm and newer +#define MPQ_FORMAT_VERSION_1 0 // Up to The Burning Crusade +#define MPQ_FORMAT_VERSION_2 1 // The Burning Crusade and newer +#define MPQ_FORMAT_VERSION_3 2 // WoW Cataclysm Beta +#define MPQ_FORMAT_VERSION_4 3 // WoW Cataclysm and newer // Flags for MPQ attributes -#define MPQ_ATTRIBUTE_CRC32 0x00000001 // The "(attributes)" contains CRC32 for each file -#define MPQ_ATTRIBUTE_FILETIME 0x00000002 // The "(attributes)" contains file time for each file -#define MPQ_ATTRIBUTE_MD5 0x00000004 // The "(attributes)" contains MD5 for each file -#define MPQ_ATTRIBUTE_ALL 0x00000007 // Summary mask +#define MPQ_ATTRIBUTE_CRC32 0x00000001 // The "(attributes)" contains CRC32 for each file +#define MPQ_ATTRIBUTE_FILETIME 0x00000002 // The "(attributes)" contains file time for each file +#define MPQ_ATTRIBUTE_MD5 0x00000004 // The "(attributes)" contains MD5 for each file +#define MPQ_ATTRIBUTE_PATCH_BIT 0x00000008 // The "(attributes)" contains a patch bit for each file +#define MPQ_ATTRIBUTE_ALL 0x0000000F // Summary mask -#define MPQ_ATTRIBUTES_V1 100 // (attributes) format version 1.00 +#define MPQ_ATTRIBUTES_V1 100 // (attributes) format version 1.00 // Flags for SFileOpenArchive -#define MPQ_OPEN_NO_LISTFILE 0x0010 // Don't load the internal listfile -#define MPQ_OPEN_NO_ATTRIBUTES 0x0020 // Don't open the attributes -#define MPQ_OPEN_FORCE_MPQ_V1 0x0040 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header -#define MPQ_OPEN_CHECK_SECTOR_CRC 0x0080 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file -#define MPQ_OPEN_READ_ONLY 0x0100 // Open the archive for read-only access -#define MPQ_OPEN_ENCRYPTED 0x0200 // Opens an encrypted MPQ archive (Example: Starcraft II installation) +#define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file +#define BASE_PROVIDER_MAP 0x00000001 // Base data source is memory-mapped file +#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server +#define BASE_PROVIDER_MASK 0x0000000F // Mask for base provider value + +#define STREAM_PROVIDER_LINEAR 0x00000000 // Stream is linear with no offset mapping +#define STREAM_PROVIDER_PARTIAL 0x00000010 // Stream is partial file (.part) +#define STREAM_PROVIDER_ENCRYPTED 0x00000020 // Stream is an encrypted MPQ +#define STREAM_PROVIDER_MASK 0x000000F0 // Mask for stream provider value + +#define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only +#define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write +#define STREAM_FLAG_MASK 0x0000FF00 // Mask for stream flags +#define STREAM_OPTIONS_MASK 0x0000FFFF // Mask for all stream options + +#define MPQ_OPEN_NO_LISTFILE 0x00010000 // Don't load the internal listfile +#define MPQ_OPEN_NO_ATTRIBUTES 0x00020000 // Don't open the attributes +#define MPQ_OPEN_FORCE_MPQ_V1 0x00040000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header +#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00080000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file + +// Deprecated +#define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY +#define MPQ_OPEN_ENCRYPTED STREAM_PROVIDER_ENCRYPTED // Flags for SFileCreateArchive -#define MPQ_CREATE_ATTRIBUTES 0x00000001 // Also add the (attributes) file -#define MPQ_CREATE_ARCHIVE_V1 0x00000000 // Creates archive of version 1 (size up to 4GB) -#define MPQ_CREATE_ARCHIVE_V2 0x00010000 // Creates archive of version 2 (larger than 4 GB) -#define MPQ_CREATE_ARCHIVE_V3 0x00020000 // Creates archive of version 3 -#define MPQ_CREATE_ARCHIVE_V4 0x00030000 // Creates archive of version 4 -#define MPQ_CREATE_ARCHIVE_VMASK 0x000F0000 // Mask for archive version +#define MPQ_CREATE_ATTRIBUTES 0x00100000 // Also add the (attributes) file +#define MPQ_CREATE_ARCHIVE_V1 0x00000000 // Creates archive of version 1 (size up to 4GB) +#define MPQ_CREATE_ARCHIVE_V2 0x01000000 // Creates archive of version 2 (larger than 4 GB) +#define MPQ_CREATE_ARCHIVE_V3 0x02000000 // Creates archive of version 3 +#define MPQ_CREATE_ARCHIVE_V4 0x03000000 // Creates archive of version 4 +#define MPQ_CREATE_ARCHIVE_VMASK 0x0F000000 // Mask for archive version + +#define FLAGS_TO_FORMAT_SHIFT 24 // (MPQ_CREATE_ARCHIVE_V4 >> FLAGS_TO_FORMAT_SHIFT) => MPQ_FORMAT_VERSION_4 // Flags for SFileVerifyFile -#define SFILE_VERIFY_SECTOR_CRC 0x0001 // Verify sector checksum for the file, if available -#define SFILE_VERIFY_FILE_CRC 0x0002 // Verify file CRC, if available -#define SFILE_VERIFY_FILE_MD5 0x0004 // Verify file MD5, if available -#define SFILE_VERIFY_RAW_MD5 0x0008 // Verify raw file MD5, if available -#define SFILE_VERIFY_ALL 0x000F // Verify every checksum possible +#define SFILE_VERIFY_SECTOR_CRC 0x00000001 // Verify sector checksum for the file, if available +#define SFILE_VERIFY_FILE_CRC 0x00000002 // Verify file CRC, if available +#define SFILE_VERIFY_FILE_MD5 0x00000004 // Verify file MD5, if available +#define SFILE_VERIFY_RAW_MD5 0x00000008 // Verify raw file MD5, if available +#define SFILE_VERIFY_ALL 0x0000000F // Verify every checksum possible // Return values for SFileVerifyFile -#define VERIFY_OPEN_ERROR 0x0001 // Failed to open the file -#define VERIFY_READ_ERROR 0x0002 // Failed to read all data from the file -#define VERIFY_FILE_HAS_SECTOR_CRC 0x0004 // File has sector CRC -#define VERIFY_FILE_SECTOR_CRC_ERROR 0x0008 // Sector CRC check failed -#define VERIFY_FILE_HAS_CHECKSUM 0x0010 // File has CRC32 -#define VERIFY_FILE_CHECKSUM_ERROR 0x0020 // CRC32 check failed -#define VERIFY_FILE_HAS_MD5 0x0040 // File has data MD5 -#define VERIFY_FILE_MD5_ERROR 0x0080 // MD5 check failed -#define VERIFY_FILE_HAS_RAW_MD5 0x0100 // File has raw data MD5 -#define VERIFY_FILE_RAW_MD5_ERROR 0x0200 // Raw MD5 check failed +#define VERIFY_OPEN_ERROR 0x0001 // Failed to open the file +#define VERIFY_READ_ERROR 0x0002 // Failed to read all data from the file +#define VERIFY_FILE_HAS_SECTOR_CRC 0x0004 // File has sector CRC +#define VERIFY_FILE_SECTOR_CRC_ERROR 0x0008 // Sector CRC check failed +#define VERIFY_FILE_HAS_CHECKSUM 0x0010 // File has CRC32 +#define VERIFY_FILE_CHECKSUM_ERROR 0x0020 // CRC32 check failed +#define VERIFY_FILE_HAS_MD5 0x0040 // File has data MD5 +#define VERIFY_FILE_MD5_ERROR 0x0080 // MD5 check failed +#define VERIFY_FILE_HAS_RAW_MD5 0x0100 // File has raw data MD5 +#define VERIFY_FILE_RAW_MD5_ERROR 0x0200 // Raw MD5 check failed #define VERIFY_FILE_ERROR_MASK (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR | VERIFY_FILE_RAW_MD5_ERROR) // Flags for SFileVerifyRawData (for MPQs version 4.0 or higher) -#define SFILE_VERIFY_MPQ_HEADER 0x0001 // Verify raw MPQ header -#define SFILE_VERIFY_HET_TABLE 0x0002 // Verify raw data of the HET table -#define SFILE_VERIFY_BET_TABLE 0x0003 // Verify raw data of the BET table -#define SFILE_VERIFY_HASH_TABLE 0x0004 // Verify raw data of the hash table -#define SFILE_VERIFY_BLOCK_TABLE 0x0005 // Verify raw data of the block table -#define SFILE_VERIFY_HIBLOCK_TABLE 0x0006 // Verify raw data of the hi-block table -#define SFILE_VERIFY_FILE 0x0007 // Verify raw data of a file +#define SFILE_VERIFY_MPQ_HEADER 0x0001 // Verify raw MPQ header +#define SFILE_VERIFY_HET_TABLE 0x0002 // Verify raw data of the HET table +#define SFILE_VERIFY_BET_TABLE 0x0003 // Verify raw data of the BET table +#define SFILE_VERIFY_HASH_TABLE 0x0004 // Verify raw data of the hash table +#define SFILE_VERIFY_BLOCK_TABLE 0x0005 // Verify raw data of the block table +#define SFILE_VERIFY_HIBLOCK_TABLE 0x0006 // Verify raw data of the hi-block table +#define SFILE_VERIFY_FILE 0x0007 // Verify raw data of a file // Return values for SFileVerifyArchive -#define ERROR_NO_SIGNATURE 0 // There is no signature in the MPQ -#define ERROR_VERIFY_FAILED 1 // There was an error during verifying signature (like no memory) -#define ERROR_WEAK_SIGNATURE_OK 2 // There is a weak signature and sign check passed -#define ERROR_WEAK_SIGNATURE_ERROR 3 // There is a weak signature but sign check failed -#define ERROR_STRONG_SIGNATURE_OK 4 // There is a strong signature and sign check passed -#define ERROR_STRONG_SIGNATURE_ERROR 5 // There is a strong signature but sign check failed - +#define ERROR_NO_SIGNATURE 0 // There is no signature in the MPQ +#define ERROR_VERIFY_FAILED 1 // There was an error during verifying signature (like no memory) +#define ERROR_WEAK_SIGNATURE_OK 2 // There is a weak signature and sign check passed +#define ERROR_WEAK_SIGNATURE_ERROR 3 // There is a weak signature but sign check failed +#define ERROR_STRONG_SIGNATURE_OK 4 // There is a strong signature and sign check passed +#define ERROR_STRONG_SIGNATURE_ERROR 5 // There is a strong signature but sign check failed + #ifndef MD5_DIGEST_SIZE -#define MD5_DIGEST_SIZE 0x10 +#define MD5_DIGEST_SIZE 0x10 #endif #ifndef SHA1_DIGEST_SIZE -#define SHA1_DIGEST_SIZE 0x14 // 160 bits +#define SHA1_DIGEST_SIZE 0x14 // 160 bits #endif #ifndef LANG_NEUTRAL -#define LANG_NEUTRAL 0x00 // Neutral locale +#define LANG_NEUTRAL 0x00 // Neutral locale #endif //----------------------------------------------------------------------------- // Callback functions // Values for compact callback -#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total) -#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total) -#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used -#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total) -#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used - +#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total) +#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total) +#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used +#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total) +#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used + typedef void (WINAPI * SFILE_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall); typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes); -//----------------------------------------------------------------------------- -// Stream support - structures - struct TFileStream; -typedef bool (*STREAM_GETPOS)( - struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG & ByteOffset // Pointer to store current file position - ); - -typedef bool (*STREAM_READ)( - struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position - void * pvBuffer, // Pointer to data to be read - DWORD dwBytesToRead // Number of bytes to read from the file - ); - -typedef bool (*STREAM_WRITE)( - struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it writes to the current position - const void * pvBuffer, // Pointer to data to be written - DWORD dwBytesToWrite // Number of bytes to read from the file - ); - -typedef bool (*STREAM_GETSIZE)( - struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG & FileSize // Receives the file size, in bytes - ); - -typedef bool (*STREAM_SETSIZE)( - struct TFileStream * pStream, // Pointer to an open stream - ULONGLONG FileSize // New size for the file, in bytes - ); - -// Common stream structure. Can be variable length -struct TFileStream -{ - ULONGLONG RawFilePos; // Current position in raw file - HANDLE hFile; // File handle. Do not use directly. - TCHAR szFileName[MAX_PATH];// Name of the file - BYTE StreamFlags; // See STREAM_FLAG_XXXX - - STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position - STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly. - STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly. - STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size - STREAM_SETSIZE StreamSetSize; // Pointer to function changing file size - - // Extra members may follow -}; - //----------------------------------------------------------------------------- // Structure for bit arrays used for HET and BET tables @@ -422,8 +385,21 @@ struct TBitArray void GetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize); void SetBits(unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize); - DWORD NumberOfBits; // Total number of bits that are available - BYTE Elements[1]; // Array of elements (variable length) + DWORD NumberOfBits; // Total number of bits that are available + BYTE Elements[1]; // Array of elements (variable length) +}; + +// Structure for file bitmap. Used by SFileGetArchiveBitmap +struct TFileBitmap +{ + ULONGLONG StartOffset; // Starting offset of the file, covered by bitmap + ULONGLONG EndOffset; // Ending offset of the file, covered by bitmap + DWORD IsComplete; // If nonzero, no blocks are missing + DWORD BitmapSize; // Size of the file bitmap (in bytes) + DWORD BlockSize; // Size of one block, in bytes + DWORD Reserved; // Alignment + + // Followed by file bitmap (variable length), array of BYTEs) }; //----------------------------------------------------------------------------- @@ -465,10 +441,10 @@ struct TMPQUserData struct TMPQHeader { // The ID_MPQ ('MPQ\x1A') signature - DWORD dwID; + DWORD dwID; // Size of the archive header - DWORD dwHeaderSize; + DWORD dwHeaderSize; // 32-bit size of MPQ archive // This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive @@ -488,14 +464,14 @@ struct TMPQHeader // Offset to the beginning of the hash table, relative to the beginning of the archive. DWORD dwHashTablePos; - + // Offset to the beginning of the block table, relative to the beginning of the archive. DWORD dwBlockTablePos; - + // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for // the original MoPaQ format, or less than 2^20 for the Burning Crusade format. DWORD dwHashTableSize; - + // Number of entries in the block table DWORD dwBlockTableSize; @@ -540,7 +516,7 @@ struct TMPQHeader // Size of raw data chunk to calculate MD5. // MD5 of each data chunk follows the raw file data. - DWORD dwRawChunkSize; + DWORD dwRawChunkSize; // MD5 of MPQ tables unsigned char MD5_BlockTable[MD5_DIGEST_SIZE]; // MD5 of the block table before decryption @@ -558,7 +534,7 @@ struct TMPQHash { // The hash of the file path, using method A. DWORD dwName1; - + // The hash of the file path, using method B. DWORD dwName2; @@ -595,30 +571,30 @@ struct TMPQBlock { // Offset of the beginning of the file, relative to the beginning of the archive. DWORD dwFilePos; - + // Compressed file size DWORD dwCSize; - + // Only valid if the block is a file; otherwise meaningless, and should be 0. // If the file is compressed, this is the size of the uncompressed file data. - DWORD dwFSize; - + DWORD dwFSize; + // Flags for the file. See MPQ_FILE_XXXX constants - DWORD dwFlags; + DWORD dwFlags; }; // Patch file information, preceding the sector offset table struct TPatchInfo { - DWORD dwLength; // Length of patch info header, in bytes - DWORD dwFlags; // Flags. 0x80000000 = MD5 (?) - DWORD dwDataSize; // Uncompressed size of the patch file - BYTE md5[0x10]; // MD5 of the entire patch file after decompression + DWORD dwLength; // Length of patch info header, in bytes + DWORD dwFlags; // Flags. 0x80000000 = MD5 (?) + DWORD dwDataSize; // Uncompressed size of the patch file + BYTE md5[0x10]; // MD5 of the entire patch file after decompression // Followed by the sector table (variable length) }; -// Header for PTCH files +// Header for PTCH files struct TPatchHeader { //-- PATCH header ----------------------------------- @@ -626,7 +602,7 @@ struct TPatchHeader DWORD dwSizeOfPatchData; // Size of the entire patch (decompressed) DWORD dwSizeBeforePatch; // Size of the file before patch DWORD dwSizeAfterPatch; // Size of file after patch - + //-- MD5 block -------------------------------------- DWORD dwMD5; // 'MD5_' DWORD dwMd5BlockSize; // Size of the MD5 block, including the signature and size itself @@ -666,54 +642,70 @@ struct TFileEntry // Common header for HET and BET tables struct TMPQExtTable { - DWORD dwSignature; // 'HET\x1A' or 'BET\x1A' - DWORD dwVersion; // Version. Seems to be always 1 - DWORD dwDataSize; // Size of the contained table + DWORD dwSignature; // 'HET\x1A' or 'BET\x1A' + DWORD dwVersion; // Version. Seems to be always 1 + DWORD dwDataSize; // Size of the contained table // Followed by the table header // Followed by the table data }; +// +// MPQ data bitmap, can be found at (FileSize - sizeof(TMPQBlockMap)) +// +// There is bit map of the entire MPQ before TMPQBitmap. Each 0x4000-byte +// block is represented by one bit (including the last, eventually incomplete block). +// +struct TMPQBitmap +{ + DWORD dwSignature; // 'ptv3' (MPQ_BLOCK_MAP_SIGNATURE) + DWORD dwAlways3; // Unknown, seems to always have value of 3 + DWORD dwBuildNumber; // Game build number for that MPQ + DWORD dwMapOffsetLo; // Low 32-bits of the offset of the bit map + DWORD dwMapOffsetHi; // High 32-bits of the offset of the bit map + DWORD dwBlockSize; // Size of one block (usually 0x4000 bytes) +}; + // Structure for parsed HET table struct TMPQHetTable { - TBitArray * pBetIndexes; // Bit array of indexes to BET tables - LPBYTE pHetHashes; // Array of HET hashes. Each entry has size of 1 byte - ULONGLONG AndMask64; // AND mask used for calculating file name hash - ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash - - DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits) - DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes - DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits) - DWORD dwMaxFileCount; // Maximum number of files in the MPQ - DWORD dwHashTableSize; // Number of entries in pBetHashes - DWORD dwHashBitSize; // Effective number of bits in the hash + TBitArray * pBetIndexes; // Bit array of indexes to BET tables + LPBYTE pHetHashes; // Array of HET hashes. Each entry has size of 1 byte + ULONGLONG AndMask64; // AND mask used for calculating file name hash + ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash + + DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits) + DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes + DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits) + DWORD dwMaxFileCount; // Maximum number of files in the MPQ + DWORD dwHashTableSize; // Number of entries in pBetHashes + DWORD dwHashBitSize; // Effective number of bits in the hash }; // Structure for parsed BET table struct TMPQBetTable { - TBitArray * pBetHashes; // Array of BET hashes - TBitArray * pFileTable; // Bit-based file table - LPDWORD pFileFlags; // Array of file flags - - DWORD dwTableEntrySize; // Size of one table entry, in bits - DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry - DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry - DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry - DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry - DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry - DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry - DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry - DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry - DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry - DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry - DWORD dwBetHashSizeTotal; // Total size of bet hash - DWORD dwBetHashSizeExtra; // Extra bits in the bet hash - DWORD dwBetHashSize; // Effective size of the bet hash - DWORD dwFileCount; // Number of files (usually equal to maximum number of files) - DWORD dwFlagCount; // Number of entries in pFileFlags + TBitArray * pBetHashes; // Array of BET hashes + TBitArray * pFileTable; // Bit-based file table + LPDWORD pFileFlags; // Array of file flags + + DWORD dwTableEntrySize; // Size of one table entry, in bits + DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry + DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry + DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry + DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry + DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry + DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry + DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry + DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry + DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry + DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry + DWORD dwBetHashSizeTotal; // Total size of bet hash + DWORD dwBetHashSizeExtra; // Extra bits in the bet hash + DWORD dwBetHashSize; // Effective size of the bet hash + DWORD dwFileCount; // Number of files (usually equal to maximum number of files) + DWORD dwFlagCount; // Number of entries in pFileFlags }; // Archive handle structure @@ -731,10 +723,11 @@ struct TMPQArchive TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file) TMPQHeader * pHeader; // MPQ file header + TMPQBitmap * pBitmap; // MPQ bitmap TMPQHash * pHashTable; // Hash table TMPQHetTable * pHetTable; // Het table TFileEntry * pFileTable; // File table - + TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found BYTE HeaderData[MPQ_HEADER_SIZE_V4]; // Storage for MPQ header @@ -747,7 +740,7 @@ struct TMPQArchive DWORD dwFileFlags2; // Flags for (attributes) DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX DWORD dwFlags; // See MPQ_FLAG_XXXXX -}; +}; // File handle structure struct TMPQFile @@ -767,7 +760,7 @@ struct TMPQFile DWORD cbFileData; // Size of loaded patched data TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table - DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files. + DWORD * SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files. DWORD * SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector DWORD dwSectorCount; // Number of sectors in the file DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ @@ -779,7 +772,6 @@ struct TMPQFile unsigned char hctx[HASH_STATE_SIZE];// Hash state for MD5. Used when saving file to MPQ DWORD dwCrc32; // CRC32 value, used when saving file to MPQ - BYTE PreviousCompression; // Compression of previous sector bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file @@ -803,19 +795,39 @@ typedef struct _SFILE_FIND_DATA } SFILE_FIND_DATA, *PSFILE_FIND_DATA; +typedef struct _SFILE_CREATE_MPQ +{ + DWORD cbSize; // Size of this structure, in bytes + DWORD dwMpqVersion; // Version of the MPQ to be created + void *pvUserData; // Reserved, must be NULL + DWORD cbUserData; // Reserved, must be 0 + DWORD dwStreamFlags; // Stream flags for creating the MPQ + DWORD dwFileFlags1; // File flags for (listfile). 0 = default + DWORD dwFileFlags2; // File flags for (attributes). 0 = default + DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created + DWORD dwSectorSize; // Sector size for compressed files + DWORD dwRawChunkSize; // Size of raw data chunk + DWORD dwMaxFileCount; // File limit for the MPQ + +} SFILE_CREATE_MPQ, *PSFILE_CREATE_MPQ; + //----------------------------------------------------------------------------- // Stream support - functions -TFileStream * FileStream_CreateFile(const TCHAR * szFileName); -TFileStream * FileStream_OpenFile(const TCHAR * szFileName, bool bWriteAccess); -TFileStream * FileStream_OpenEncrypted(const TCHAR * szFileName); -bool FileStream_GetPos(TFileStream * pStream, ULONGLONG & ByteOffset); +TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags); +TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags); +TCHAR * FileStream_GetFileName(TFileStream * pStream); +bool FileStream_IsReadOnly(TFileStream * pStream); bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead); bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite); -bool FileStream_GetLastWriteTime(TFileStream * pStream, ULONGLONG * pFT); +bool FileStream_GetPos(TFileStream * pStream, ULONGLONG & ByteOffset); +bool FileStream_SetPos(TFileStream * pStream, ULONGLONG ByteOffset); bool FileStream_GetSize(TFileStream * pStream, ULONGLONG & FileSize); bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize); -bool FileStream_MoveFile(TFileStream * pStream, TFileStream * pTempStream); +bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFT); +bool FileStream_Switch(TFileStream * pStream, TFileStream * pTempStream); +bool FileStream_SetBitmap(TFileStream * pStream, TFileBitmap * pBitmap); +bool FileStream_GetBitmap(TFileStream * pStream, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded); void FileStream_Close(TFileStream * pStream); //----------------------------------------------------------------------------- @@ -834,12 +846,6 @@ typedef bool (WINAPI * SFILEREADFILE)(HANDLE, void *, DWORD, LPDWORD, LPOVERLAP //----------------------------------------------------------------------------- // Functions for manipulation with StormLib global flags -#define SFILE_FLAG_ALLOW_WRITE_SHARE 0x00000001 // When a MPQ is open for write by StormLib, - // it is allowed to open it for write with another application. - -DWORD WINAPI SFileGetGlobalFlags(); -DWORD WINAPI SFileSetGlobalFlags(DWORD dwNewFlags); - LCID WINAPI SFileGetLocale(); LCID WINAPI SFileSetLocale(LCID lcNewLocale); @@ -848,7 +854,9 @@ LCID WINAPI SFileSetLocale(LCID lcNewLocale); bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq); bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwFlags, DWORD dwMaxFileCount, HANDLE * phMpq); +bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq); +bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded); bool WINAPI SFileFlushArchive(HANDLE hMpq); bool WINAPI SFileCloseArchive(HANDLE hMpq); @@ -931,9 +939,9 @@ bool WINAPI SFileCreateFile(HANDLE hMpq, const char * szArchivedName, ULONGLON bool WINAPI SFileWriteFile(HANDLE hFile, const void * pvData, DWORD dwSize, DWORD dwCompression); bool WINAPI SFileFinishFile(HANDLE hFile); -bool WINAPI SFileAddFileEx(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwCompression, DWORD dwCompressionNext = 0xFFFFFFFF); -bool WINAPI SFileAddFile(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags); -bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwQuality); +bool WINAPI SFileAddFileEx(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwCompression, DWORD dwCompressionNext = MPQ_COMPRESSION_NEXT_SAME); +bool WINAPI SFileAddFile(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags); +bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwQuality); bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope = SFILE_OPEN_FROM_MPQ); bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szOldFileName, const char * szNewFileName); bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale); @@ -948,6 +956,7 @@ int WINAPI SCompImplode (char * pbOutBuffer, int * pcbOutBuffer, char * pb int WINAPI SCompExplode (char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer); int WINAPI SCompCompress (char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer, unsigned uCompressionMask, int nCmpType, int nCmpLevel); int WINAPI SCompDecompress (char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer); +int WINAPI SCompDecompress2(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer); //----------------------------------------------------------------------------- // Non-Windows support for SetLastError/GetLastError diff --git a/dep/StormLib/src/StormPort.h b/dep/StormLib/src/StormPort.h index a8f5835bdfb..0914654b9af 100644 --- a/dep/StormLib/src/StormPort.h +++ b/dep/StormLib/src/StormPort.h @@ -46,6 +46,7 @@ #include <ctype.h> #include <stdio.h> #include <windows.h> + #include <wininet.h> #define PLATFORM_LITTLE_ENDIAN #ifdef WIN64 @@ -59,20 +60,26 @@ #endif -// Defines for Mac Carbon -#if !defined(PLATFORM_DEFINED) && defined(__APPLE__) // Mac Carbon API +// Defines for Mac +#if !defined(PLATFORM_DEFINED) && defined(__APPLE__) // Mac BSD API - // Macintosh using Carbon - #include <Carbon/Carbon.h> // Mac OS X + // Macintosh + #include <sys/types.h> + #include <sys/stat.h> + #include <sys/mman.h> + #include <unistd.h> + #include <fcntl.h> + #include <stdlib.h> + #include <errno.h> #define PKEXPORT #define __SYS_ZLIB #define __SYS_BZLIB #ifndef __BIG_ENDIAN__ - #define PLATFORM_LITTLE_ENDIAN // Apple is now making Macs with Intel CPUs + #define PLATFORM_LITTLE_ENDIAN #endif - + #define PLATFORM_MAC #define PLATFORM_DEFINED // The platform is known now @@ -83,6 +90,7 @@ #include <sys/types.h> #include <sys/stat.h> + #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> @@ -152,29 +160,21 @@ #define _stricmp strcasecmp #define _strnicmp strncasecmp + #define _tcsnicmp strncasecmp #endif // !WIN32 -// Platform-specific error codes -#ifdef PLATFORM_MAC - #define ERROR_SUCCESS noErr - #define ERROR_FILE_NOT_FOUND fnfErr - #define ERROR_ACCESS_DENIED permErr - #define ERROR_INVALID_HANDLE rfNumErr - #define ERROR_NOT_ENOUGH_MEMORY mFulErr - #define ERROR_BAD_FORMAT 200 // Returned when the opened file is in format that is not recognized by StormLib - #define ERROR_NO_MORE_FILES errFSNoMoreItems - #define ERROR_HANDLE_EOF eofErr - #define ERROR_NOT_SUPPORTED 201 - #define ERROR_INVALID_PARAMETER paramErr - #define ERROR_DISK_FULL dskFulErr - #define ERROR_ALREADY_EXISTS dupFNErr - #define ERROR_CAN_NOT_COMPLETE 202 // A generic error, when any operation fails from an unknown reason - #define ERROR_FILE_CORRUPT 203 // At any point when there is bad data format in the file - #define ERROR_INSUFFICIENT_BUFFER errFSBadBuffer +// 64-bit calls are supplied by "normal" calls on Mac +#if defined(PLATFORM_MAC) + #define stat64 stat + #define fstat64 fstat + #define lseek64 lseek + #define off64_t off_t + #define O_LARGEFILE 0 #endif -#ifdef PLATFORM_LINUX +// Platform-specific error codes for UNIX-based platforms +#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) #define ERROR_SUCCESS 0 #define ERROR_FILE_NOT_FOUND ENOENT #define ERROR_ACCESS_DENIED EPERM diff --git a/dep/StormLib/src/adpcm/adpcm.cpp b/dep/StormLib/src/adpcm/adpcm.cpp index c43d2234c18..916fa3811a8 100644 --- a/dep/StormLib/src/adpcm/adpcm.cpp +++ b/dep/StormLib/src/adpcm/adpcm.cpp @@ -17,17 +17,17 @@ //------------------------------------------------------------------------------ // Structures -union TByteAndWordPtr +typedef union _BYTE_AND_WORD_PTR { short * pw; unsigned char * pb; -}; +} BYTE_AND_WORD_PTR; -union TWordAndByteArray +typedef union _WORD_AND_BYTE_ARRAY { short w; unsigned char b[2]; -}; +} WORD_AND_BYTE_ARRAY; //----------------------------------------------------------------------------- // Tables necessary dor decompression @@ -63,8 +63,8 @@ static long step_table[] = int CompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuffer, int dwInLength, int nChannels, int nCmpLevel) // ECX EDX { - TWordAndByteArray Wcmp; - TByteAndWordPtr out; // Pointer to the output buffer + WORD_AND_BYTE_ARRAY Wcmp; + BYTE_AND_WORD_PTR out; // Pointer to the output buffer long SInt32Array1[2]; long SInt32Array2[2]; long SInt32Array3[2]; @@ -83,6 +83,7 @@ int CompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuff int nLength; int nIndex; int nValue; + int i, chnl; // If less than 2 bytes remain, don't decompress anything // pbSaveOutBuffer = pbOutBuffer; @@ -99,7 +100,7 @@ int CompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuff SInt32Array1[0] = SInt32Array1[1] = 0x2C; - for(int i = 0; i < nChannels; i++) + for(i = 0; i < nChannels; i++) { nOneWord = BSWAP_INT16_SIGNED(*pwInBuffer++); *out.pw++ = BSWAP_INT16_SIGNED((short)nOneWord); @@ -119,7 +120,7 @@ int CompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuff // ebx - nChannels // ecx - pwOutPos - for(int chnl = nChannels; chnl < nWordsRemains; chnl++) + for(chnl = nChannels; chnl < nWordsRemains; chnl++) { // 1500F030 if((out.pb - pbOutBuffer + 2) > nBytesRemains) @@ -223,14 +224,14 @@ int CompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuff // 1500F230 int DecompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, unsigned char * pbInBuffer, int dwInLength, int nChannels) { - TByteAndWordPtr out; // Output buffer - TByteAndWordPtr in; + BYTE_AND_WORD_PTR out; // Output buffer + BYTE_AND_WORD_PTR in; unsigned char * pbInBufferEnd = (pbInBuffer + dwInLength); long SInt32Array1[2]; long SInt32Array2[2]; long nOneWord; - int dwOutLengthCopy = dwOutLength; int nIndex; + int i; SInt32Array1[0] = SInt32Array1[1] = 0x2C; out.pb = pbOutBuffer; @@ -238,15 +239,15 @@ int DecompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, unsigned char in.pw++; // Fill the Uint32Array2 array by channel values. - for(int i = 0; i < nChannels; i++) + for(i = 0; i < nChannels; i++) { nOneWord = BSWAP_INT16_SIGNED(*in.pw++); SInt32Array2[i] = nOneWord; - if(dwOutLengthCopy < 2) + if(dwOutLength < 2) return (int)(out.pb - pbOutBuffer); *out.pw++ = BSWAP_INT16_SIGNED((short)nOneWord); - dwOutLengthCopy -= sizeof(short); + dwOutLength -= sizeof(short); } // Get the initial index @@ -270,7 +271,7 @@ int DecompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, unsigned char if(SInt32Array1[nIndex] != 0) SInt32Array1[nIndex]--; - if(dwOutLengthCopy < 2) + if(dwOutLength < 2) return (int)(out.pb - pbOutBuffer); *out.pw++ = BSWAP_INT16_SIGNED((unsigned short)SInt32Array2[nIndex]); diff --git a/dep/StormLib/src/huffman/huff.cpp b/dep/StormLib/src/huffman/huff.cpp index 34203a8108f..66a46b3fa55 100644 --- a/dep/StormLib/src/huffman/huff.cpp +++ b/dep/StormLib/src/huffman/huff.cpp @@ -263,10 +263,15 @@ static void InsertItem(THTreeItem ** itemPtr, THTreeItem * item, unsigned long n if(PTR_INVALID(prev2)) { - prev2 = PTR_NOT(prev); - - prev2->next = item; - item2->prev = item; // Next after last item + if(prev != NULL) + { + prev2 = PTR_NOT(prev); + if(prev2 != NULL) + { + prev2->next = item; + item2->prev = item; // Next after last item + } + } return; } @@ -1009,8 +1014,8 @@ unsigned int THuffmannTree::DoDecompression(unsigned char * pbOutBuffer, unsigne for(;;) { // Security check: If we are at the end of the input buffer, - // it means that the data are corrupt. - if(is->pbInBuffer > is->pbInBufferEnd) + // it means that the data is corrupt + if(is->BitCount == 0 && is->pbInBuffer >= is->pbInBufferEnd) return 0; // Get 7 bits from input stream @@ -1046,6 +1051,9 @@ _1500E549: do { + if(pItem1 == NULL) + return 0; + pItem1 = pItem1->child; // Move down by one level if(is->GetBit()) // If current bit is set, move to previous pItem1 = pItem1->prev; diff --git a/dep/StormLib/src/huffman/huff.h b/dep/StormLib/src/huffman/huff.h index 6af4ac638bb..83e9b2cdad0 100644 --- a/dep/StormLib/src/huffman/huff.h +++ b/dep/StormLib/src/huffman/huff.h @@ -65,8 +65,6 @@ class TOutputStream // Huffmann tree item (?) struct THTreeItem { - public: - THTreeItem * Call1501DB70(THTreeItem * pLast); THTreeItem * GetPrevItem(LONG_PTR value); void ClearItemLinks(); diff --git a/dep/StormLib/src/lzma/info.txt b/dep/StormLib/src/lzma/info.txt new file mode 100644 index 00000000000..4cee86e3542 --- /dev/null +++ b/dep/StormLib/src/lzma/info.txt @@ -0,0 +1 @@ +Taken from LZMA SDK v 9.11
\ No newline at end of file diff --git a/dep/StormLib/src/sparse/sparse.cpp b/dep/StormLib/src/sparse/sparse.cpp index 5b37f6d0f8e..6df7021fc8a 100644 --- a/dep/StormLib/src/sparse/sparse.cpp +++ b/dep/StormLib/src/sparse/sparse.cpp @@ -255,6 +255,10 @@ int DecompressSparse(unsigned char * pbOutBuffer, int * pcbOutBuffer, unsigned c OneByte = *pbInBuffer++; cbOutBuffer |= (OneByte << 0x00); + // Verify the size of the stream against the output buffer size + if(cbOutBuffer > *pcbOutBuffer) + return 0; + // Put the output size to the buffer *pcbOutBuffer = cbOutBuffer; |