diff options
author | unknown <E:\Ladik\Mail> | 2015-05-28 17:24:56 +0200 |
---|---|---|
committer | unknown <E:\Ladik\Mail> | 2015-05-28 17:24:56 +0200 |
commit | a8a92ccc92f786f56c0c306152de2b7bde53cc45 (patch) | |
tree | fe4852eef33c35fc7e783308d301eda429643f60 /src | |
parent | 61f552875dbcc0879ad69409fff25632460fe087 (diff) |
+ Fixed bug in SFileReadFile
Diffstat (limited to 'src')
-rw-r--r-- | src/FileStream.cpp | 5780 | ||||
-rw-r--r-- | src/SBaseCommon.cpp | 3548 | ||||
-rw-r--r-- | src/SFileAddFile.cpp | 2626 | ||||
-rw-r--r-- | src/SFileAttributes.cpp | 1140 | ||||
-rw-r--r-- | src/SFileCompactArchive.cpp | 1308 | ||||
-rw-r--r-- | src/SFileFindFile.cpp | 960 | ||||
-rw-r--r-- | src/SFileGetFileInfo.cpp | 1984 | ||||
-rw-r--r-- | src/SFileListFile.cpp | 1284 | ||||
-rw-r--r-- | src/SFilePatchArchives.cpp | 1874 | ||||
-rw-r--r-- | src/SFileReadFile.cpp | 4 | ||||
-rw-r--r-- | src/SFileVerify.cpp | 2108 | ||||
-rw-r--r-- | src/StormCommon.h | 744 | ||||
-rw-r--r-- | src/StormPort.h | 580 |
13 files changed, 11966 insertions, 11974 deletions
diff --git a/src/FileStream.cpp b/src/FileStream.cpp index a367db6..29a4d33 100644 --- a/src/FileStream.cpp +++ b/src/FileStream.cpp @@ -1,2890 +1,2890 @@ -/*****************************************************************************/ -/* FileStream.cpp Copyright (c) Ladislav Zezula 2010 */ -/*---------------------------------------------------------------------------*/ -/* File stream support for StormLib */ -/* */ -/* Windows support: Written by Ladislav Zezula */ -/* Mac support: Written by Sam Wilkins */ -/* Linux support: Written by Sam Wilkins and Ivan Komissarov */ -/* Big-endian: Written & debugged by Sam Wilkins */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 11.06.10 1.00 Lad Derived from StormPortMac.cpp and StormPortLinux.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" -#include "FileStream.h" - -#ifdef _MSC_VER -#pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream -#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) -#endif - -//----------------------------------------------------------------------------- -// Local defines - -#ifndef INVALID_HANDLE_VALUE -#define INVALID_HANDLE_VALUE ((HANDLE)-1) -#endif - -//----------------------------------------------------------------------------- -// Local functions - platform-specific functions - -#ifndef PLATFORM_WINDOWS -static int nLastError = ERROR_SUCCESS; - -int GetLastError() -{ - return nLastError; -} - -void SetLastError(int nError) -{ - nLastError = nError; -} -#endif - -static DWORD StringToInt(const char * szString) -{ - DWORD dwValue = 0; - - while('0' <= szString[0] && szString[0] <= '9') - { - dwValue = (dwValue * 10) + (szString[0] - '9'); - szString++; - } - - return dwValue; -} - -//----------------------------------------------------------------------------- -// Dummy init function - -static void BaseNone_Init(TFileStream *) -{ - // Nothing here -} - -//----------------------------------------------------------------------------- -// Local functions - base file support - -static bool BaseFile_Create(TFileStream * pStream) -{ -#ifdef PLATFORM_WINDOWS - { - DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; - - pStream->Base.File.hFile = CreateFile(pStream->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 defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - intptr_t handle; - - handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if(handle == -1) - { - nLastError = errno; - return false; - } - - pStream->Base.File.hFile = (HANDLE)handle; - } -#endif - - // Reset the file size and position - pStream->Base.File.FileSize = 0; - pStream->Base.File.FilePos = 0; - return true; -} - -static bool BaseFile_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags) -{ -#ifdef PLATFORM_WINDOWS - { - ULARGE_INTEGER FileSize; - DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES; - DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0; - - // Open the file - pStream->Base.File.hFile = CreateFile(szFileName, - FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess, - FILE_SHARE_READ | dwWriteShare, - NULL, - OPEN_EXISTING, - 0, - NULL); - if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE) - return false; - - // Query the file size - FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart); - pStream->Base.File.FileSize = FileSize.QuadPart; - - // Query last write time - GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime); - } -#endif - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - struct stat64 fileinfo; - int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR; - intptr_t handle; - - // Open the file - handle = open(szFileName, oflag | O_LARGEFILE); - if(handle == -1) - { - nLastError = errno; - return false; - } - - // Get the file size - if(fstat64(handle, &fileinfo) == -1) - { - 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 - - // Reset the file position - pStream->Base.File.FilePos = 0; - return true; -} - -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 -{ - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; - DWORD dwBytesRead = 0; // Must be set by platform-specific code - -#ifdef PLATFORM_WINDOWS - { - // 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 - - // Update the byte offset - pStream->Base.File.FilePos = ByteOffset; - - // Read the data - if(dwBytesToRead != 0) - { - 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; - } - } -#endif - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - ssize_t bytes_read; - - // If the byte offset is different from the current file position, - // we have to update the file position xxx - if(ByteOffset != pStream->Base.File.FilePos) - { - lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET); - pStream->Base.File.FilePos = ByteOffset; - } - - // 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 - - // 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); -} - -/** - * \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) -{ - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; - DWORD dwBytesWritten = 0; // Must be set by platform-specific code - -#ifdef PLATFORM_WINDOWS - { - // 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 - - // Update the byte offset - pStream->Base.File.FilePos = ByteOffset; - - // Read the data - if(dwBytesToWrite != 0) - { - OVERLAPPED Overlapped; - - Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32); - Overlapped.Offset = (DWORD)ByteOffset; - Overlapped.hEvent = NULL; - if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, &Overlapped)) - return false; - } - } -#endif - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - ssize_t bytes_written; - - // If the byte offset is different from the current file position, - // we have to update the file position - if(ByteOffset != pStream->Base.File.FilePos) - { - lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET); - pStream->Base.File.FilePos = ByteOffset; - } - - // 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 - - // 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); -} - -/** - * \a pStream Pointer to an open stream - * \a NewFileSize New size of the file - */ -static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize) -{ -#ifdef PLATFORM_WINDOWS - { - LONG FileSizeHi = (LONG)(NewFileSize >> 32); - LONG FileSizeLo; - DWORD dwNewPos; - bool bResult; - - // 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; - - // Set the current file pointer as the end of the file - bResult = (bool)SetEndOfFile(pStream->Base.File.hFile); - if(bResult) - pStream->Base.File.FileSize = NewFileSize; - - // 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 - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - { - if(ftruncate64((intptr_t)pStream->Base.File.hFile, (off64_t)NewFileSize) == -1) - { - nLastError = errno; - return false; - } - - pStream->Base.File.FileSize = NewFileSize; - return true; - } -#endif -} - -// Gives the current file size -static bool BaseFile_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) -{ - // Note: Used by all thre base providers. - // Requires the TBaseData union to have the same layout for all three base providers - *pFileSize = pStream->Base.File.FileSize; - return true; -} - -// Gives the current file position -static bool BaseFile_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) -{ - // Note: Used by all thre base providers. - // Requires the TBaseData union to have the same layout for all three base providers - *pByteOffset = pStream->Base.File.FilePos; - return true; -} - -// Renames the file pointed by pStream so that it contains data from pNewStream -static bool BaseFile_Replace(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 - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - // "rename" on Linux also works if the target file exists - if(rename(pNewStream->szFileName, pStream->szFileName) == -1) - { - nLastError = errno; - return false; - } - - return true; -#endif -} - -static void BaseFile_Close(TFileStream * pStream) -{ - 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; -} - -// Initializes base functions for the disk file -static void BaseFile_Init(TFileStream * pStream) -{ - pStream->BaseCreate = BaseFile_Create; - pStream->BaseOpen = BaseFile_Open; - pStream->BaseRead = BaseFile_Read; - pStream->BaseWrite = BaseFile_Write; - pStream->BaseResize = BaseFile_Resize; - pStream->BaseGetSize = BaseFile_GetSize; - pStream->BaseGetPos = BaseFile_GetPos; - pStream->BaseClose = BaseFile_Close; -} - -//----------------------------------------------------------------------------- -// Local functions - base memory-mapped file support - -static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags) -{ -#ifdef PLATFORM_WINDOWS - - ULARGE_INTEGER FileSize; - HANDLE hFile; - HANDLE hMap; - bool bResult = false; - - // Keep compiler happy - dwStreamFlags = dwStreamFlags; - - // Open the file for read access - hFile = CreateFile(szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if(hFile != INVALID_HANDLE_VALUE) - { - // Retrieve file size. Don't allow mapping file of a zero size. - FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart); - if(FileSize.QuadPart != 0) - { - // 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) - { - // Retrieve file time - GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime); - - // Retrieve file size and position - pStream->Base.Map.FileSize = FileSize.QuadPart; - pStream->Base.Map.FilePos = 0; - bResult = true; - } - - // Close the map handle - CloseHandle(hMap); - } - } - - // 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 - -#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) - struct stat64 fileinfo; - intptr_t handle; - bool bResult = false; - - // Open the file - handle = open(szFileName, O_RDONLY); - if(handle != -1) - { - // Get the file size - if(fstat64(handle, &fileinfo) != -1) - { - 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); - } - - // Did the mapping fail? - if(bResult == false) - { - nLastError = errno; - return false; - } -#endif - - return true; -} - -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; - - // 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); - } - - // Move the current file position - pStream->Base.Map.FilePos += dwBytesToRead; - 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; -} - -// Initializes base functions for the mapped file -static void BaseMap_Init(TFileStream * pStream) -{ - // Supply the file stream functions - pStream->BaseOpen = BaseMap_Open; - pStream->BaseRead = BaseMap_Read; - pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function - pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function - pStream->BaseClose = BaseMap_Close; - - // Mapped files are read-only - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; -} - -//----------------------------------------------------------------------------- -// 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) - { - while(szFileName[0] != 0 && szFileName[0] != _T('/')) - *szServerName++ = *szFileName++; - *szServerName = 0; - } - else - { - while(szFileName[0] != 0 && szFileName[0] != _T('/')) - szFileName++; - } - - // Return the remainder - return szFileName; -} - -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; - - // Keep compiler happy - dwStreamFlags = dwStreamFlags; - - // Don't connect to the internet - if(!InternetGetConnectedState(&dwTemp, 0)) - return false; - - // Initiate the connection to the internet - pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0); - if(pStream->Base.Http.hInternet == NULL) - return false; - - // 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) - { - InternetCloseHandle(pStream->Base.Http.hInternet); - return false; - } - } - - // 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 the file is not there and is not available for random access, - // report error - if(bFileAvailable == false) - { - pStream->BaseClose(pStream); - return false; - } - - return true; - -#else - - // Not supported - SetLastError(ERROR_NOT_SUPPORTED); - pStream = pStream; - return false; - -#endif -} - -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; - - // 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) - { - // Add range request to the HTTP headers - // http://www.clevercomponents.com/articles/article015/resuming.asp - _stprintf(szRangeRequest, _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset); - HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW); - - // 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 > 0x200) - dwBlockBytesToRead = 0x200; - InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead); - - // Check for end - if(dwBlockBytesRead == 0) - break; - - // Move buffers - dwTotalBytesRead += dwBlockBytesRead; - pbBuffer += dwBlockBytesRead; - } - } - InternetCloseHandle(hRequest); - } - } - - // Increment the current file position by number of bytes read - 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 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 -} - -// Initializes base functions for the mapped file -static void BaseHttp_Init(TFileStream * pStream) -{ - // Supply the stream functions - pStream->BaseOpen = BaseHttp_Open; - pStream->BaseRead = BaseHttp_Read; - pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function - pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function - pStream->BaseClose = BaseHttp_Close; - - // HTTP files are read-only - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; -} - -//----------------------------------------------------------------------------- -// Local functions - base block-based support - -// Generic function that loads blocks from the file -// The function groups the block with the same availability, -// so the called BlockRead can finish the request in a single system call -static bool BlockStream_Read( - TBlockStream * 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 BlockOffset0; - ULONGLONG BlockOffset; - ULONGLONG ByteOffset; - ULONGLONG EndOffset; - LPBYTE TransferBuffer; - LPBYTE BlockBuffer; - DWORD BlockBufferOffset; // Offset of the desired data in the block buffer - DWORD BytesNeeded; // Number of bytes that really need to be read - DWORD BlockSize = pStream->BlockSize; - DWORD BlockCount; - bool bPrevBlockAvailable; - bool bCallbackCalled = false; - bool bBlockAvailable; - bool bResult = true; - - // The base block read function must be present - assert(pStream->BlockRead != NULL); - - // NOP reading of zero bytes - if(dwBytesToRead == 0) - return true; - - // Get the current position in the stream - ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos; - EndOffset = ByteOffset + dwBytesToRead; - if(EndOffset > pStream->StreamSize) - { - SetLastError(ERROR_HANDLE_EOF); - return false; - } - - // Calculate the block parameters - BlockOffset0 = BlockOffset = ByteOffset & ~((ULONGLONG)BlockSize - 1); - BlockCount = (DWORD)(((EndOffset - BlockOffset) + (BlockSize - 1)) / BlockSize); - BytesNeeded = (DWORD)(EndOffset - BlockOffset); - - // Remember where we have our data - assert((BlockSize & (BlockSize - 1)) == 0); - BlockBufferOffset = (DWORD)(ByteOffset & (BlockSize - 1)); - - // Allocate buffer for reading blocks - TransferBuffer = BlockBuffer = STORM_ALLOC(BYTE, (BlockCount * BlockSize)); - if(TransferBuffer == NULL) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return false; - } - - // If all blocks are available, just read all blocks at once - if(pStream->IsComplete == 0) - { - // Now parse the blocks and send the block read request - // to all blocks with the same availability - assert(pStream->BlockCheck != NULL); - bPrevBlockAvailable = pStream->BlockCheck(pStream, BlockOffset); - - // Loop as long as we have something to read - while(BlockOffset < EndOffset) - { - // Determine availability of the next block - bBlockAvailable = pStream->BlockCheck(pStream, BlockOffset); - - // If the availability has changed, read all blocks up to this one - if(bBlockAvailable != bPrevBlockAvailable) - { - // Call the file stream callback, if the block is not available - if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false) - { - pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0)); - bCallbackCalled = true; - } - - // Load the continuous blocks with the same availability - assert(BlockOffset > BlockOffset0); - bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable); - if(!bResult) - break; - - // Move the block offset - BlockBuffer += (DWORD)(BlockOffset - BlockOffset0); - BytesNeeded -= (DWORD)(BlockOffset - BlockOffset0); - bPrevBlockAvailable = bBlockAvailable; - BlockOffset0 = BlockOffset; - } - - // Move to the block offset in the stream - BlockOffset += BlockSize; - } - - // If there is a block(s) remaining to be read, do it - if(BlockOffset > BlockOffset0) - { - // Call the file stream callback, if the block is not available - if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false) - { - pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0)); - bCallbackCalled = true; - } - - // Read the complete blocks from the file - if(BlockOffset > pStream->StreamSize) - BlockOffset = pStream->StreamSize; - bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable); - } - } - else - { - // Read the complete blocks from the file - if(EndOffset > pStream->StreamSize) - EndOffset = pStream->StreamSize; - bResult = pStream->BlockRead(pStream, BlockOffset, EndOffset, BlockBuffer, BytesNeeded, true); - } - - // Now copy the data to the user buffer - if(bResult) - { - memcpy(pvBuffer, TransferBuffer + BlockBufferOffset, dwBytesToRead); - pStream->StreamPos = ByteOffset + dwBytesToRead; - } - else - { - // If the block read failed, set the last error - SetLastError(ERROR_FILE_INCOMPLETE); - } - - // Call the callback to indicate we are done - if(bCallbackCalled) - pStream->pfnCallback(pStream->UserData, 0, 0); - - // Free the block buffer and return - STORM_FREE(TransferBuffer); - return bResult; -} - -static bool BlockStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) -{ - *pFileSize = pStream->StreamSize; - return true; -} - -static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) -{ - *pByteOffset = pStream->StreamPos; - return true; -} - -static void BlockStream_Close(TBlockStream * pStream) -{ - // Free the data map, if any - if(pStream->FileBitmap != NULL) - STORM_FREE(pStream->FileBitmap); - pStream->FileBitmap = NULL; - - // Call the base class for closing the stream - pStream->BaseClose(pStream); -} - -//----------------------------------------------------------------------------- -// File stream allocation function - -static STREAM_INIT StreamBaseInit[4] = -{ - BaseFile_Init, - BaseMap_Init, - BaseHttp_Init, - BaseNone_Init -}; - -// This function allocates an empty structure for the file stream -// The stream structure is created as flat block, variable length -// The file name is placed after the end of the stream structure data -static TFileStream * AllocateFileStream( - const TCHAR * szFileName, - size_t StreamSize, - DWORD dwStreamFlags) -{ - TFileStream * pMaster = NULL; - TFileStream * pStream; - const TCHAR * szNextFile = szFileName; - size_t FileNameSize; - - // Sanity check - assert(StreamSize != 0); - - // The caller can specify chain of files in the following form: - // C:\archive.MPQ*http://www.server.com/MPQs/archive-server.MPQ - // In that case, we use the part after "*" as master file name - while(szNextFile[0] != 0 && szNextFile[0] != _T('*')) - szNextFile++; - FileNameSize = (size_t)((szNextFile - szFileName) * sizeof(TCHAR)); - - // If we have a next file, we need to open it as master stream - // Note that we don't care if the master stream exists or not, - // If it doesn't, later attempts to read missing file block will fail - if(szNextFile[0] == _T('*')) - { - // Don't allow another master file in the string - if(_tcschr(szNextFile + 1, _T('*')) != NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return NULL; - } - - // Open the master file - pMaster = FileStream_OpenFile(szNextFile + 1, STREAM_FLAG_READ_ONLY); - } - - // Allocate the stream structure for the given stream type - pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize + sizeof(TCHAR)); - if(pStream != NULL) - { - // Zero the entire structure - memset(pStream, 0, StreamSize); - pStream->pMaster = pMaster; - pStream->dwFlags = dwStreamFlags; - - // Initialize the file name - pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize); - memcpy(pStream->szFileName, szFileName, FileNameSize); - pStream->szFileName[FileNameSize / sizeof(TCHAR)] = 0; - - // Initialize the stream functions - StreamBaseInit[dwStreamFlags & 0x03](pStream); - } - - return pStream; -} - -//----------------------------------------------------------------------------- -// Local functions - flat stream support - -static DWORD FlatStream_CheckFile(TBlockStream * pStream) -{ - LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap; - DWORD WholeByteCount = (pStream->BlockCount / 8); - DWORD ExtraBitsCount = (pStream->BlockCount & 7); - BYTE ExpectedValue; - - // Verify the whole bytes - their value must be 0xFF - for(DWORD i = 0; i < WholeByteCount; i++) - { - if(FileBitmap[i] != 0xFF) - return 0; - } - - // If there are extra bits, calculate the mask - if(ExtraBitsCount != 0) - { - ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1); - if(FileBitmap[WholeByteCount] != ExpectedValue) - return 0; - } - - // Yes, the file is complete - return 1; -} - -static bool FlatStream_LoadBitmap(TBlockStream * pStream) -{ - FILE_BITMAP_FOOTER Footer; - ULONGLONG ByteOffset; - LPBYTE FileBitmap; - DWORD BlockCount; - DWORD BitmapSize; - - // Do not load the bitmap if we should not have to - if(!(pStream->dwFlags & STREAM_FLAG_USE_BITMAP)) - return false; - - // Only if the size is greater than size of bitmap footer - if(pStream->Base.File.FileSize > sizeof(FILE_BITMAP_FOOTER)) - { - // Load the bitmap footer - ByteOffset = pStream->Base.File.FileSize - sizeof(FILE_BITMAP_FOOTER); - if(pStream->BaseRead(pStream, &ByteOffset, &Footer, sizeof(FILE_BITMAP_FOOTER))) - { - // Make sure that the array is properly BSWAP-ed - BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&Footer), sizeof(FILE_BITMAP_FOOTER)); - - // Verify if there is actually a footer - if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03) - { - // Get the offset of the bitmap, number of blocks and size of the bitmap - ByteOffset = MAKE_OFFSET64(Footer.MapOffsetHi, Footer.MapOffsetLo); - BlockCount = (DWORD)(((ByteOffset - 1) / Footer.BlockSize) + 1); - BitmapSize = ((BlockCount + 7) / 8); - - // Check if the sizes match - if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize) - { - // Allocate space for the bitmap - FileBitmap = STORM_ALLOC(BYTE, BitmapSize); - if(FileBitmap != NULL) - { - // Load the bitmap bits - if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize)) - { - STORM_FREE(FileBitmap); - return false; - } - - // Update the stream size - pStream->BuildNumber = Footer.BuildNumber; - pStream->StreamSize = ByteOffset; - - // Fill the bitmap information - pStream->FileBitmap = FileBitmap; - pStream->BitmapSize = BitmapSize; - pStream->BlockSize = Footer.BlockSize; - pStream->BlockCount = BlockCount; - pStream->IsComplete = FlatStream_CheckFile(pStream); - return true; - } - } - } - } - } - - return false; -} - -static void FlatStream_UpdateBitmap( - TBlockStream * pStream, // Pointer to an open stream - ULONGLONG StartOffset, - ULONGLONG EndOffset) -{ - LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap; - DWORD BlockIndex; - DWORD BlockSize = pStream->BlockSize; - DWORD ByteIndex; - BYTE BitMask; - - // Sanity checks - assert((StartOffset & (BlockSize - 1)) == 0); - assert(FileBitmap != NULL); - - // Calculate the index of the block - BlockIndex = (DWORD)(StartOffset / BlockSize); - ByteIndex = (BlockIndex / 0x08); - BitMask = (BYTE)(1 << (BlockIndex & 0x07)); - - // Set all bits for the specified range - while(StartOffset < EndOffset) - { - // Set the bit - FileBitmap[ByteIndex] |= BitMask; - - // Move all - StartOffset += BlockSize; - ByteIndex += (BitMask >> 0x07); - BitMask = (BitMask >> 0x07) | (BitMask << 0x01); - } - - // Increment the bitmap update count - pStream->IsModified = 1; -} - -static bool FlatStream_BlockCheck( - TBlockStream * pStream, // Pointer to an open stream - ULONGLONG BlockOffset) -{ - LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap; - DWORD BlockIndex; - BYTE BitMask; - - // Sanity checks - assert((BlockOffset & (pStream->BlockSize - 1)) == 0); - assert(FileBitmap != NULL); - - // Calculate the index of the block - BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize); - BitMask = (BYTE)(1 << (BlockIndex & 0x07)); - - // Check if the bit is present - return (FileBitmap[BlockIndex / 0x08] & BitMask) ? true : false; -} - -static bool FlatStream_BlockRead( - TBlockStream * pStream, // Pointer to an open stream - ULONGLONG StartOffset, - ULONGLONG EndOffset, - LPBYTE BlockBuffer, - DWORD BytesNeeded, - bool bAvailable) -{ - DWORD BytesToRead = (DWORD)(EndOffset - StartOffset); - - // The starting offset must be aligned to size of the block - assert(pStream->FileBitmap != NULL); - assert((StartOffset & (pStream->BlockSize - 1)) == 0); - assert(StartOffset < EndOffset); - - // If the blocks are not available, we need to load them from the master - // and then save to the mirror - if(bAvailable == false) - { - // If we have no master, we cannot satisfy read request - if(pStream->pMaster == NULL) - return false; - - // Load the blocks from the master stream - // Note that we always have to read complete blocks - // so they get properly stored to the mirror stream - if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead)) - return false; - - // Store the loaded blocks to the mirror file. - // Note that this operation is not required to succeed - if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead)) - FlatStream_UpdateBitmap(pStream, StartOffset, EndOffset); - - return true; - } - else - { - if(BytesToRead > BytesNeeded) - BytesToRead = BytesNeeded; - return pStream->BaseRead(pStream, &StartOffset, BlockBuffer, BytesToRead); - } -} - -static void FlatStream_Close(TBlockStream * pStream) -{ - FILE_BITMAP_FOOTER Footer; - - if(pStream->FileBitmap && pStream->IsModified) - { - // Write the file bitmap - pStream->BaseWrite(pStream, &pStream->StreamSize, pStream->FileBitmap, pStream->BitmapSize); - - // Prepare and write the file footer - Footer.Signature = ID_FILE_BITMAP_FOOTER; - Footer.Version = 3; - Footer.BuildNumber = pStream->BuildNumber; - Footer.MapOffsetLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF); - Footer.MapOffsetHi = (DWORD)(pStream->StreamSize >> 0x20); - Footer.BlockSize = pStream->BlockSize; - BSWAP_ARRAY32_UNSIGNED(&Footer, sizeof(FILE_BITMAP_FOOTER)); - pStream->BaseWrite(pStream, NULL, &Footer, sizeof(FILE_BITMAP_FOOTER)); - } - - // Close the base class - BlockStream_Close(pStream); -} - -static bool FlatStream_CreateMirror(TBlockStream * pStream) -{ - ULONGLONG MasterSize = 0; - ULONGLONG MirrorSize = 0; - LPBYTE FileBitmap = NULL; - DWORD dwBitmapSize; - DWORD dwBlockCount; - bool bNeedCreateMirrorStream = true; - bool bNeedResizeMirrorStream = true; - - // Do we have master function and base creation function? - if(pStream->pMaster == NULL || pStream->BaseCreate == NULL) - return false; - - // Retrieve the master file size, block count and bitmap size - FileStream_GetSize(pStream->pMaster, &MasterSize); - dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE); - dwBitmapSize = (DWORD)((dwBlockCount + 7) / 8); - - // Setup stream size and position - pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really??? - pStream->StreamSize = MasterSize; - pStream->StreamPos = 0; - - // Open the base stream for write access - if(pStream->BaseOpen(pStream, pStream->szFileName, 0)) - { - // If the file open succeeded, check if the file size matches required size - pStream->BaseGetSize(pStream, &MirrorSize); - if(MirrorSize == MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER)) - { - // Attempt to load an existing file bitmap - if(FlatStream_LoadBitmap(pStream)) - return true; - - // We need to create new file bitmap - bNeedResizeMirrorStream = false; - } - - // We need to create mirror stream - bNeedCreateMirrorStream = false; - } - - // Create a new stream, if needed - if(bNeedCreateMirrorStream) - { - if(!pStream->BaseCreate(pStream)) - return false; - } - - // If we need to, then resize the mirror stream - if(bNeedResizeMirrorStream) - { - if(!pStream->BaseResize(pStream, MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER))) - return false; - } - - // Allocate the bitmap array - FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize); - if(FileBitmap == NULL) - return false; - - // Initialize the bitmap - memset(FileBitmap, 0, dwBitmapSize); - pStream->FileBitmap = FileBitmap; - pStream->BitmapSize = dwBitmapSize; - pStream->BlockSize = DEFAULT_BLOCK_SIZE; - pStream->BlockCount = dwBlockCount; - pStream->IsComplete = 0; - pStream->IsModified = 1; - - // Note: Don't write the stream bitmap right away. - // Doing so would cause sparse file resize on NTFS, - // which would take long time on larger files. - return true; -} - -static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) -{ - TBlockStream * pStream; - ULONGLONG ByteOffset = 0; - - // Create new empty stream - pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); - if(pStream == NULL) - return NULL; - - // Do we have a master stream? - if(pStream->pMaster != NULL) - { - if(!FlatStream_CreateMirror(pStream)) - { - FileStream_Close(pStream); - SetLastError(ERROR_FILE_NOT_FOUND); - return NULL; - } - } - else - { - // Attempt to open the base stream - if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags)) - { - FileStream_Close(pStream); - return NULL; - } - - // Load the bitmap, if required to - if(dwStreamFlags & STREAM_FLAG_USE_BITMAP) - FlatStream_LoadBitmap(pStream); - } - - // If we have a stream bitmap, set the reading functions - // which check presence of each file block - if(pStream->FileBitmap != NULL) - { - // Set the stream position to zero. Stream size is already set - assert(pStream->StreamSize != 0); - pStream->StreamPos = 0; - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - - // Supply the stream functions - pStream->StreamRead = (STREAM_READ)BlockStream_Read; - pStream->StreamGetSize = BlockStream_GetSize; - pStream->StreamGetPos = BlockStream_GetPos; - pStream->StreamClose = (STREAM_CLOSE)FlatStream_Close; - - // Supply the block functions - pStream->BlockCheck = (BLOCK_CHECK)FlatStream_BlockCheck; - pStream->BlockRead = (BLOCK_READ)FlatStream_BlockRead; - } - else - { - // Reset the base position to zero - pStream->BaseRead(pStream, &ByteOffset, NULL, 0); - - // Setup stream size and position - pStream->StreamSize = pStream->Base.File.FileSize; - pStream->StreamPos = 0; - - // Set the base functions - pStream->StreamRead = pStream->BaseRead; - pStream->StreamWrite = pStream->BaseWrite; - pStream->StreamResize = pStream->BaseResize; - pStream->StreamGetSize = pStream->BaseGetSize; - pStream->StreamGetPos = pStream->BaseGetPos; - pStream->StreamClose = pStream->BaseClose; - } - - return pStream; -} - -//----------------------------------------------------------------------------- -// 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 DWORD PartStream_CheckFile(TBlockStream * pStream) -{ - PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap; - DWORD dwBlockCount; - - // Get the number of blocks - dwBlockCount = (DWORD)((pStream->StreamSize + pStream->BlockSize - 1) / pStream->BlockSize); - - // Check all blocks - for(DWORD i = 0; i < dwBlockCount; i++, FileBitmap++) - { - // Few sanity checks - assert(FileBitmap->LargeValueHi == 0); - assert(FileBitmap->LargeValueLo == 0); - assert(FileBitmap->Flags == 0 || FileBitmap->Flags == 3); - - // Check if this block is present - if(FileBitmap->Flags != 3) - return 0; - } - - // Yes, the file is complete - return 1; -} - -static bool PartStream_LoadBitmap(TBlockStream * pStream) -{ - PPART_FILE_MAP_ENTRY FileBitmap; - PART_FILE_HEADER PartHdr; - ULONGLONG ByteOffset = 0; - ULONGLONG StreamSize = 0; - DWORD BlockCount; - DWORD BitmapSize; - - // Only if the size is greater than size of the bitmap header - if(pStream->Base.File.FileSize > sizeof(PART_FILE_HEADER)) - { - // 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_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER)); - - // Verify the PART file header - if(IsPartHeader(&PartHdr)) - { - // Get the number of blocks and size of one block - StreamSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo); - ByteOffset = sizeof(PART_FILE_HEADER); - BlockCount = (DWORD)((StreamSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize); - BitmapSize = BlockCount * sizeof(PART_FILE_MAP_ENTRY); - - // Check if sizes match - if((ByteOffset + BitmapSize) < pStream->Base.File.FileSize) - { - // Allocate space for the array of PART_FILE_MAP_ENTRY - FileBitmap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount); - if(FileBitmap != NULL) - { - // Load the block map - if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize)) - { - STORM_FREE(FileBitmap); - return false; - } - - // Make sure that the byte order is correct - BSWAP_ARRAY32_UNSIGNED(FileBitmap, BitmapSize); - - // Update the stream size - pStream->BuildNumber = StringToInt(PartHdr.GameBuildNumber); - pStream->StreamSize = StreamSize; - - // Fill the bitmap information - pStream->FileBitmap = FileBitmap; - pStream->BitmapSize = BitmapSize; - pStream->BlockSize = PartHdr.BlockSize; - pStream->BlockCount = BlockCount; - pStream->IsComplete = PartStream_CheckFile(pStream); - return true; - } - } - } - } - } - - return false; -} - -static void PartStream_UpdateBitmap( - TBlockStream * pStream, // Pointer to an open stream - ULONGLONG StartOffset, - ULONGLONG EndOffset, - ULONGLONG RealOffset) -{ - PPART_FILE_MAP_ENTRY FileBitmap; - DWORD BlockSize = pStream->BlockSize; - - // Sanity checks - assert((StartOffset & (BlockSize - 1)) == 0); - assert(pStream->FileBitmap != NULL); - - // Calculate the first entry in the block map - FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (StartOffset / BlockSize); - - // Set all bits for the specified range - while(StartOffset < EndOffset) - { - // Set the bit - FileBitmap->BlockOffsHi = (DWORD)(RealOffset >> 0x20); - FileBitmap->BlockOffsLo = (DWORD)(RealOffset & 0xFFFFFFFF); - FileBitmap->Flags = 3; - - // Move all - StartOffset += BlockSize; - RealOffset += BlockSize; - FileBitmap++; - } - - // Increment the bitmap update count - pStream->IsModified = 1; -} - -static bool PartStream_BlockCheck( - TBlockStream * pStream, // Pointer to an open stream - ULONGLONG BlockOffset) -{ - PPART_FILE_MAP_ENTRY FileBitmap; - - // Sanity checks - assert((BlockOffset & (pStream->BlockSize - 1)) == 0); - assert(pStream->FileBitmap != NULL); - - // Calculate the block map entry - FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (BlockOffset / pStream->BlockSize); - - // Check if the flags are present - return (FileBitmap->Flags & 0x03) ? true : false; -} - -static bool PartStream_BlockRead( - TBlockStream * pStream, - ULONGLONG StartOffset, - ULONGLONG EndOffset, - LPBYTE BlockBuffer, - DWORD BytesNeeded, - bool bAvailable) -{ - PPART_FILE_MAP_ENTRY FileBitmap; - ULONGLONG ByteOffset; - DWORD BytesToRead; - DWORD BlockIndex = (DWORD)(StartOffset / pStream->BlockSize); - - // The starting offset must be aligned to size of the block - assert(pStream->FileBitmap != NULL); - assert((StartOffset & (pStream->BlockSize - 1)) == 0); - assert(StartOffset < EndOffset); - - // If the blocks are not available, we need to load them from the master - // and then save to the mirror - if(bAvailable == false) - { - // If we have no master, we cannot satisfy read request - if(pStream->pMaster == NULL) - return false; - - // Load the blocks from the master stream - // Note that we always have to read complete blocks - // so they get properly stored to the mirror stream - BytesToRead = (DWORD)(EndOffset - StartOffset); - if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead)) - return false; - - // The loaded blocks are going to be stored to the end of the file - // Note that this operation is not required to succeed - if(pStream->BaseGetSize(pStream, &ByteOffset)) - { - // Store the loaded blocks to the mirror file. - if(pStream->BaseWrite(pStream, &ByteOffset, BlockBuffer, BytesToRead)) - { - PartStream_UpdateBitmap(pStream, StartOffset, EndOffset, ByteOffset); - } - } - } - else - { - // Get the file map entry - FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex; - - // Read all blocks - while(StartOffset < EndOffset) - { - // Get the number of bytes to be read - BytesToRead = (DWORD)(EndOffset - StartOffset); - if(BytesToRead > pStream->BlockSize) - BytesToRead = pStream->BlockSize; - if(BytesToRead > BytesNeeded) - BytesToRead = BytesNeeded; - - // Read the block - ByteOffset = MAKE_OFFSET64(FileBitmap->BlockOffsHi, FileBitmap->BlockOffsLo); - if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead)) - return false; - - // Move the pointers - StartOffset += pStream->BlockSize; - BlockBuffer += pStream->BlockSize; - BytesNeeded -= pStream->BlockSize; - FileBitmap++; - } - } - - return true; -} - -static void PartStream_Close(TBlockStream * pStream) -{ - PART_FILE_HEADER PartHeader; - ULONGLONG ByteOffset = 0; - - if(pStream->FileBitmap && pStream->IsModified) - { - // Prepare the part file header - memset(&PartHeader, 0, sizeof(PART_FILE_HEADER)); - PartHeader.PartialVersion = 2; - PartHeader.FileSizeHi = (DWORD)(pStream->StreamSize >> 0x20); - PartHeader.FileSizeLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF); - PartHeader.BlockSize = pStream->BlockSize; - - // Make sure that the header is properly BSWAPed - BSWAP_ARRAY32_UNSIGNED(&PartHeader, sizeof(PART_FILE_HEADER)); - sprintf(PartHeader.GameBuildNumber, "%u", (unsigned int)pStream->BuildNumber); - - // Write the part header - pStream->BaseWrite(pStream, &ByteOffset, &PartHeader, sizeof(PART_FILE_HEADER)); - - // Write the block bitmap - BSWAP_ARRAY32_UNSIGNED(pStream->FileBitmap, pStream->BitmapSize); - pStream->BaseWrite(pStream, NULL, pStream->FileBitmap, pStream->BitmapSize); - } - - // Close the base class - BlockStream_Close(pStream); -} - -static bool PartStream_CreateMirror(TBlockStream * pStream) -{ - ULONGLONG RemainingSize; - ULONGLONG MasterSize = 0; - ULONGLONG MirrorSize = 0; - LPBYTE FileBitmap = NULL; - DWORD dwBitmapSize; - DWORD dwBlockCount; - bool bNeedCreateMirrorStream = true; - bool bNeedResizeMirrorStream = true; - - // Do we have master function and base creation function? - if(pStream->pMaster == NULL || pStream->BaseCreate == NULL) - return false; - - // Retrieve the master file size, block count and bitmap size - FileStream_GetSize(pStream->pMaster, &MasterSize); - dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE); - dwBitmapSize = (DWORD)(dwBlockCount * sizeof(PART_FILE_MAP_ENTRY)); - - // Setup stream size and position - pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really??? - pStream->StreamSize = MasterSize; - pStream->StreamPos = 0; - - // Open the base stream for write access - if(pStream->BaseOpen(pStream, pStream->szFileName, 0)) - { - // If the file open succeeded, check if the file size matches required size - pStream->BaseGetSize(pStream, &MirrorSize); - if(MirrorSize >= sizeof(PART_FILE_HEADER) + dwBitmapSize) - { - // Check if the remaining size is aligned to block - RemainingSize = MirrorSize - sizeof(PART_FILE_HEADER) - dwBitmapSize; - if((RemainingSize & (DEFAULT_BLOCK_SIZE - 1)) == 0 || RemainingSize == MasterSize) - { - // Attempt to load an existing file bitmap - if(PartStream_LoadBitmap(pStream)) - return true; - } - } - - // We need to create mirror stream - bNeedCreateMirrorStream = false; - } - - // Create a new stream, if needed - if(bNeedCreateMirrorStream) - { - if(!pStream->BaseCreate(pStream)) - return false; - } - - // If we need to, then resize the mirror stream - if(bNeedResizeMirrorStream) - { - if(!pStream->BaseResize(pStream, sizeof(PART_FILE_HEADER) + dwBitmapSize)) - return false; - } - - // Allocate the bitmap array - FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize); - if(FileBitmap == NULL) - return false; - - // Initialize the bitmap - memset(FileBitmap, 0, dwBitmapSize); - pStream->FileBitmap = FileBitmap; - pStream->BitmapSize = dwBitmapSize; - pStream->BlockSize = DEFAULT_BLOCK_SIZE; - pStream->BlockCount = dwBlockCount; - pStream->IsComplete = 0; - pStream->IsModified = 1; - - // Note: Don't write the stream bitmap right away. - // Doing so would cause sparse file resize on NTFS, - // which would take long time on larger files. - return true; -} - - -static TFileStream * PartStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) -{ - TBlockStream * pStream; - - // Create new empty stream - pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); - if(pStream == NULL) - return NULL; - - // Do we have a master stream? - if(pStream->pMaster != NULL) - { - if(!PartStream_CreateMirror(pStream)) - { - FileStream_Close(pStream); - SetLastError(ERROR_FILE_NOT_FOUND); - return NULL; - } - } - else - { - // Attempt to open the base stream - if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags)) - { - FileStream_Close(pStream); - return NULL; - } - - // Load the part stream block map - if(!PartStream_LoadBitmap(pStream)) - { - FileStream_Close(pStream); - SetLastError(ERROR_BAD_FORMAT); - return NULL; - } - } - - // Set the stream position to zero. Stream size is already set - assert(pStream->StreamSize != 0); - pStream->StreamPos = 0; - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - - // Set new function pointers - pStream->StreamRead = (STREAM_READ)BlockStream_Read; - pStream->StreamGetPos = BlockStream_GetPos; - pStream->StreamGetSize = BlockStream_GetSize; - pStream->StreamClose = (STREAM_CLOSE)PartStream_Close; - - // Supply the block functions - pStream->BlockCheck = (BLOCK_CHECK)PartStream_BlockCheck; - pStream->BlockRead = (BLOCK_READ)PartStream_BlockRead; - return pStream; -} - -//----------------------------------------------------------------------------- -// Local functions - MPQE stream support - -static const char * szKeyTemplate = "expand 32-byte k000000000000000000000000000000000000000000000000"; - -static const char * AuthCodeArray[] = -{ - // Starcraft II (Heart of the Swarm) - // Authentication code URL: http://dist.blizzard.com/mediakey/hots-authenticationcode-bgdl.txt - // -0C- -1C--08- -18--04- -14--00- -10- - "S48B6CDTN5XEQAKQDJNDLJBJ73FDFM3U", // SC2 Heart of the Swarm-all : "expand 32-byte kQAKQ0000FM3UN5XE000073FD6CDT0000LJBJS48B0000DJND" - - // Diablo III: Agent.exe (1.0.0.954) - // Address of decryption routine: 00502b00 - // Pointer to decryptor object: ECX - // Pointer to key: ECX+0x5C - // Authentication code URL: http://dist.blizzard.com/mediakey/d3-authenticationcode-enGB.txt - // -0C- -1C--08- -18--04- -14--00- -10- - "UCMXF6EJY352EFH4XFRXCFH2XC9MQRZK", // Diablo III Installer (deDE): "expand 32-byte kEFH40000QRZKY3520000XC9MF6EJ0000CFH2UCMX0000XFRX" - "MMKVHY48RP7WXP4GHYBQ7SL9J9UNPHBP", // Diablo III Installer (enGB): "expand 32-byte kXP4G0000PHBPRP7W0000J9UNHY4800007SL9MMKV0000HYBQ" - "8MXLWHQ7VGGLTZ9MQZQSFDCLJYET3CPP", // Diablo III Installer (enSG): "expand 32-byte kTZ9M00003CPPVGGL0000JYETWHQ70000FDCL8MXL0000QZQS" - "EJ2R5TM6XFE2GUNG5QDGHKQ9UAKPWZSZ", // Diablo III Installer (enUS): "expand 32-byte kGUNG0000WZSZXFE20000UAKP5TM60000HKQ9EJ2R00005QDG" - "PBGFBE42Z6LNK65UGJQ3WZVMCLP4HQQT", // Diablo III Installer (esES): "expand 32-byte kK65U0000HQQTZ6LN0000CLP4BE420000WZVMPBGF0000GJQ3" - "X7SEJJS9TSGCW5P28EBSC47AJPEY8VU2", // Diablo III Installer (esMX): "expand 32-byte kW5P200008VU2TSGC0000JPEYJJS90000C47AX7SE00008EBS" - "5KVBQA8VYE6XRY3DLGC5ZDE4XS4P7YA2", // Diablo III Installer (frFR): "expand 32-byte kRY3D00007YA2YE6X0000XS4PQA8V0000ZDE45KVB0000LGC5" - "478JD2K56EVNVVY4XX8TDWYT5B8KB254", // Diablo III Installer (itIT): "expand 32-byte kVVY40000B2546EVN00005B8KD2K50000DWYT478J0000XX8T" - "8TS4VNFQRZTN6YWHE9CHVDH9NVWD474A", // Diablo III Installer (koKR): "expand 32-byte k6YWH0000474ARZTN0000NVWDVNFQ0000VDH98TS40000E9CH" - "LJ52Z32DF4LZ4ZJJXVKK3AZQA6GABLJB", // Diablo III Installer (plPL): "expand 32-byte k4ZJJ0000BLJBF4LZ0000A6GAZ32D00003AZQLJ520000XVKK" - "K6BDHY2ECUE2545YKNLBJPVYWHE7XYAG", // Diablo III Installer (ptBR): "expand 32-byte k545Y0000XYAGCUE20000WHE7HY2E0000JPVYK6BD0000KNLB" - "NDVW8GWLAYCRPGRNY8RT7ZZUQU63VLPR", // Diablo III Installer (ruRU): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - "6VWCQTN8V3ZZMRUCZXV8A8CGUX2TAA8H", // Diablo III Installer (zhTW): "expand 32-byte kMRUC0000AA8HV3ZZ0000UX2TQTN80000A8CG6VWC0000ZXV8" -// "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Diablo III Installer (zhCN): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - - // Starcraft II (Wings of Liberty): Installer.exe (4.1.1.4219) - // Address of decryption routine: 0053A3D0 - // Pointer to decryptor object: ECX - // Pointer to key: ECX+0x5C - // Authentication code URL: http://dist.blizzard.com/mediakey/sc2-authenticationcode-enUS.txt - // -0C- -1C--08- -18--04- -14--00- -10- - "Y45MD3CAK4KXSSXHYD9VY64Z8EKJ4XFX", // SC2 Wings of Liberty (deDE): "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V" - "G8MN8UDG6NA2ANGY6A3DNY82HRGF29ZH", // SC2 Wings of Liberty (enGB): "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D" - "W9RRHLB2FDU9WW5B3ECEBLRSFWZSF7HW", // SC2 Wings of Liberty (enSG): "expand 32-byte kWW5B0000F7HWFDU90000FWZSHLB20000BLRSW9RR00003ECE" - "3DH5RE5NVM5GTFD85LXGWT6FK859ETR5", // SC2 Wings of Liberty (enUS): "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG" - "8WLKUAXE94PFQU4Y249PAZ24N4R4XKTQ", // SC2 Wings of Liberty (esES): "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P" - "A34DXX3VHGGXSQBRFE5UFFDXMF9G4G54", // SC2 Wings of Liberty (esMX): "expand 32-byte kSQBR00004G54HGGX0000MF9GXX3V0000FFDXA34D0000FE5U" - "ZG7J9K938HJEFWPQUA768MA2PFER6EAJ", // SC2 Wings of Liberty (frFR): "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76" - "NE7CUNNNTVAPXV7E3G2BSVBWGVMW8BL2", // SC2 Wings of Liberty (itIT): "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B" - "3V9E2FTMBM9QQWK7U6MAMWAZWQDB838F", // SC2 Wings of Liberty (koKR): "expand 32-byte kQWK70000838FBM9Q0000WQDB2FTM0000MWAZ3V9E0000U6MA" - "2NSFB8MELULJ83U6YHA3UP6K4MQD48L6", // SC2 Wings of Liberty (plPL): "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3" - "QA2TZ9EWZ4CUU8BMB5WXCTY65F9CSW4E", // SC2 Wings of Liberty (ptBR): "expand 32-byte kU8BM0000SW4EZ4CU00005F9CZ9EW0000CTY6QA2T0000B5WX" - "VHB378W64BAT9SH7D68VV9NLQDK9YEGT", // SC2 Wings of Liberty (ruRU): "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V" - "U3NFQJV4M6GC7KBN9XQJ3BRDN3PLD9NE", // SC2 Wings of Liberty (zhTW): "expand 32-byte k7KBN0000D9NEM6GC0000N3PLQJV400003BRDU3NF00009XQJ" - - NULL -}; - -static DWORD Rol32(DWORD dwValue, DWORD dwRolCount) -{ - DWORD dwShiftRight = 32 - dwRolCount; - - return (dwValue << dwRolCount) | (dwValue >> dwShiftRight); -} - -static void CreateKeyFromAuthCode( - LPBYTE pbKeyBuffer, - const char * szAuthCode) -{ - LPDWORD KeyPosition = (LPDWORD)(pbKeyBuffer + 0x10); - LPDWORD AuthCode32 = (LPDWORD)szAuthCode; - - memcpy(pbKeyBuffer, szKeyTemplate, MPQE_CHUNK_SIZE); - KeyPosition[0x00] = AuthCode32[0x03]; - KeyPosition[0x02] = AuthCode32[0x07]; - KeyPosition[0x03] = AuthCode32[0x02]; - KeyPosition[0x05] = AuthCode32[0x06]; - KeyPosition[0x06] = AuthCode32[0x01]; - KeyPosition[0x08] = AuthCode32[0x05]; - KeyPosition[0x09] = AuthCode32[0x00]; - KeyPosition[0x0B] = AuthCode32[0x04]; - BSWAP_ARRAY32_UNSIGNED(pbKeyBuffer, MPQE_CHUNK_SIZE); -} - -static void DecryptFileChunk( - DWORD * MpqData, - LPBYTE pbKey, - ULONGLONG ByteOffset, - DWORD dwLength) -{ - ULONGLONG ChunkOffset; - DWORD KeyShuffled[0x10]; - DWORD KeyMirror[0x10]; - DWORD RoundCount = 0x14; - - // Prepare the key - ChunkOffset = ByteOffset / MPQE_CHUNK_SIZE; - memcpy(KeyMirror, pbKey, MPQE_CHUNK_SIZE); - BSWAP_ARRAY32_UNSIGNED(KeyMirror, MPQE_CHUNK_SIZE); - KeyMirror[0x05] = (DWORD)(ChunkOffset >> 32); - KeyMirror[0x08] = (DWORD)(ChunkOffset); - - while(dwLength >= MPQE_CHUNK_SIZE) - { - // Shuffle the key - part 1 - KeyShuffled[0x0E] = KeyMirror[0x00]; - KeyShuffled[0x0C] = KeyMirror[0x01]; - KeyShuffled[0x05] = KeyMirror[0x02]; - KeyShuffled[0x0F] = KeyMirror[0x03]; - KeyShuffled[0x0A] = KeyMirror[0x04]; - KeyShuffled[0x07] = KeyMirror[0x05]; - KeyShuffled[0x0B] = KeyMirror[0x06]; - KeyShuffled[0x09] = KeyMirror[0x07]; - KeyShuffled[0x03] = KeyMirror[0x08]; - KeyShuffled[0x06] = KeyMirror[0x09]; - KeyShuffled[0x08] = KeyMirror[0x0A]; - KeyShuffled[0x0D] = KeyMirror[0x0B]; - KeyShuffled[0x02] = KeyMirror[0x0C]; - KeyShuffled[0x04] = KeyMirror[0x0D]; - KeyShuffled[0x01] = KeyMirror[0x0E]; - KeyShuffled[0x00] = KeyMirror[0x0F]; - - // Shuffle the key - part 2 - for(DWORD i = 0; i < RoundCount; i += 2) - { - KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x02]), 0x07); - KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0E]), 0x09); - KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x0A]), 0x0D); - KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x03]), 0x12); - - KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x04]), 0x07); - KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x0C]), 0x09); - KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x07]), 0x0D); - KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x06]), 0x12); - - KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x01]), 0x07); - KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x05]), 0x09); - KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x0B]), 0x0D); - KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x08]), 0x12); - - KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x00]), 0x07); - KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x0F]), 0x09); - KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x09]), 0x0D); - KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x0D]), 0x12); - - KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x09]), 0x07); - KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x0E]), 0x09); - KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x04]), 0x0D); - KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x08]), 0x12); - - KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x0A]), 0x07); - KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x0C]), 0x09); - KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x01]), 0x0D); - KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0D]), 0x12); - - KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x07]), 0x07); - KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x05]), 0x09); - KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x00]), 0x0D); - KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x03]), 0x12); - - KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x0B]), 0x07); - KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x0F]), 0x09); - KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x02]), 0x0D); - KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x06]), 0x12); - } - - // Decrypt one data chunk - BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE); - MpqData[0x00] = MpqData[0x00] ^ (KeyShuffled[0x0E] + KeyMirror[0x00]); - MpqData[0x01] = MpqData[0x01] ^ (KeyShuffled[0x04] + KeyMirror[0x0D]); - MpqData[0x02] = MpqData[0x02] ^ (KeyShuffled[0x08] + KeyMirror[0x0A]); - MpqData[0x03] = MpqData[0x03] ^ (KeyShuffled[0x09] + KeyMirror[0x07]); - MpqData[0x04] = MpqData[0x04] ^ (KeyShuffled[0x0A] + KeyMirror[0x04]); - MpqData[0x05] = MpqData[0x05] ^ (KeyShuffled[0x0C] + KeyMirror[0x01]); - MpqData[0x06] = MpqData[0x06] ^ (KeyShuffled[0x01] + KeyMirror[0x0E]); - MpqData[0x07] = MpqData[0x07] ^ (KeyShuffled[0x0D] + KeyMirror[0x0B]); - MpqData[0x08] = MpqData[0x08] ^ (KeyShuffled[0x03] + KeyMirror[0x08]); - MpqData[0x09] = MpqData[0x09] ^ (KeyShuffled[0x07] + KeyMirror[0x05]); - MpqData[0x0A] = MpqData[0x0A] ^ (KeyShuffled[0x05] + KeyMirror[0x02]); - MpqData[0x0B] = MpqData[0x0B] ^ (KeyShuffled[0x00] + KeyMirror[0x0F]); - MpqData[0x0C] = MpqData[0x0C] ^ (KeyShuffled[0x02] + KeyMirror[0x0C]); - MpqData[0x0D] = MpqData[0x0D] ^ (KeyShuffled[0x06] + KeyMirror[0x09]); - MpqData[0x0E] = MpqData[0x0E] ^ (KeyShuffled[0x0B] + KeyMirror[0x06]); - MpqData[0x0F] = MpqData[0x0F] ^ (KeyShuffled[0x0F] + KeyMirror[0x03]); - BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE); - - // Update byte offset in the key - KeyMirror[0x08]++; - if(KeyMirror[0x08] == 0) - KeyMirror[0x05]++; - - // Move pointers and decrease number of bytes to decrypt - MpqData += (MPQE_CHUNK_SIZE / sizeof(DWORD)); - dwLength -= MPQE_CHUNK_SIZE; - } -} - -static bool MpqeStream_DetectFileKey(TEncryptedStream * pStream) -{ - ULONGLONG ByteOffset = 0; - BYTE EncryptedHeader[MPQE_CHUNK_SIZE]; - BYTE FileHeader[MPQE_CHUNK_SIZE]; - - // Read the first file chunk - if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader))) - { - // We just try all known keys one by one - for(int i = 0; AuthCodeArray[i] != NULL; i++) - { - // Prepare they decryption key from game serial number - CreateKeyFromAuthCode(pStream->Key, AuthCodeArray[i]); - - // Try to decrypt with the given key - memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE); - DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE); - - // 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') - { - // Update the stream size - pStream->StreamSize = pStream->Base.File.FileSize; - - // Fill the block information - pStream->BlockSize = MPQE_CHUNK_SIZE; - pStream->BlockCount = (DWORD)(pStream->Base.File.FileSize + MPQE_CHUNK_SIZE - 1) / MPQE_CHUNK_SIZE; - pStream->IsComplete = 1; - return true; - } - } - } - - // Key not found, sorry - return false; -} - -static bool MpqeStream_BlockRead( - TEncryptedStream * pStream, - ULONGLONG StartOffset, - ULONGLONG EndOffset, - LPBYTE BlockBuffer, - DWORD BytesNeeded, - bool bAvailable) -{ - DWORD dwBytesToRead; - - assert((StartOffset & (pStream->BlockSize - 1)) == 0); - assert(StartOffset < EndOffset); - assert(bAvailable != false); - BytesNeeded = BytesNeeded; - bAvailable = bAvailable; - - // Read the file from the stream as-is - // Limit the reading to number of blocks really needed - dwBytesToRead = (DWORD)(EndOffset - StartOffset); - if(!pStream->BaseRead(pStream, &StartOffset, BlockBuffer, dwBytesToRead)) - return false; - - // Decrypt the data - dwBytesToRead = (dwBytesToRead + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1); - DecryptFileChunk((LPDWORD)BlockBuffer, pStream->Key, StartOffset, dwBytesToRead); - return true; -} - -static TFileStream * MpqeStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) -{ - TEncryptedStream * pStream; - - // Create new empty stream - pStream = (TEncryptedStream *)AllocateFileStream(szFileName, sizeof(TEncryptedStream), dwStreamFlags); - if(pStream == NULL) - return NULL; - - // Attempt to open the base stream - assert(pStream->BaseOpen != NULL); - if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags)) - return NULL; - - // Determine the encryption key for the MPQ - if(MpqeStream_DetectFileKey(pStream)) - { - // Set the stream position and size - assert(pStream->StreamSize != 0); - pStream->StreamPos = 0; - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - - // Set new function pointers - pStream->StreamRead = (STREAM_READ)BlockStream_Read; - pStream->StreamGetPos = BlockStream_GetPos; - pStream->StreamGetSize = BlockStream_GetSize; - pStream->StreamClose = pStream->BaseClose; - - // Supply the block functions - pStream->BlockRead = (BLOCK_READ)MpqeStream_BlockRead; - return pStream; - } - - // Cleanup the stream and return - FileStream_Close(pStream); - SetLastError(ERROR_UNKNOWN_FILE_KEY); - return NULL; -} - -//----------------------------------------------------------------------------- -// Local functions - Block4 stream support - -#define BLOCK4_BLOCK_SIZE 0x4000 // Size of one block -#define BLOCK4_HASH_SIZE 0x20 // Size of MD5 hash that is after each block -#define BLOCK4_MAX_BLOCKS 0x00002000 // Maximum amount of blocks per file -#define BLOCK4_MAX_FSIZE 0x08040000 // Max size of one file - -static bool Block4Stream_BlockRead( - TBlockStream * pStream, // Pointer to an open stream - ULONGLONG StartOffset, - ULONGLONG EndOffset, - LPBYTE BlockBuffer, - DWORD BytesNeeded, - bool bAvailable) -{ - TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap; - ULONGLONG ByteOffset; - DWORD BytesToRead; - DWORD StreamIndex; - DWORD BlockIndex; - bool bResult; - - // The starting offset must be aligned to size of the block - assert(pStream->FileBitmap != NULL); - assert((StartOffset & (pStream->BlockSize - 1)) == 0); - assert(StartOffset < EndOffset); - assert(bAvailable == true); - - // Keep compiler happy - bAvailable = bAvailable; - EndOffset = EndOffset; - - while(BytesNeeded != 0) - { - // Calculate the block index and the file index - StreamIndex = (DWORD)((StartOffset / pStream->BlockSize) / BLOCK4_MAX_BLOCKS); - BlockIndex = (DWORD)((StartOffset / pStream->BlockSize) % BLOCK4_MAX_BLOCKS); - if(StreamIndex > pStream->BitmapSize) - return false; - - // Calculate the block offset - ByteOffset = ((ULONGLONG)BlockIndex * (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE)); - BytesToRead = STORMLIB_MIN(BytesNeeded, BLOCK4_BLOCK_SIZE); - - // Read from the base stream - pStream->Base = BaseArray[StreamIndex]; - bResult = pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead); - BaseArray[StreamIndex] = pStream->Base; - - // Did the result succeed? - if(bResult == false) - return false; - - // Move pointers - StartOffset += BytesToRead; - BlockBuffer += BytesToRead; - BytesNeeded -= BytesToRead; - } - - return true; -} - - -static void Block4Stream_Close(TBlockStream * pStream) -{ - TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap; - - // If we have a non-zero count of base streams, - // we have to close them all - if(BaseArray != NULL) - { - // Close all base streams - for(DWORD i = 0; i < pStream->BitmapSize; i++) - { - memcpy(&pStream->Base, BaseArray + i, sizeof(TBaseProviderData)); - pStream->BaseClose(pStream); - } - } - - // Free the data map, if any - if(pStream->FileBitmap != NULL) - STORM_FREE(pStream->FileBitmap); - pStream->FileBitmap = NULL; - - // Do not call the BaseClose function, - // we closed all handles already - return; -} - -static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) -{ - TBaseProviderData * NewBaseArray = NULL; - ULONGLONG RemainderBlock; - ULONGLONG BlockCount; - ULONGLONG FileSize; - TBlockStream * pStream; - TCHAR * szNameBuff; - size_t nNameLength; - DWORD dwBaseFiles = 0; - DWORD dwBaseFlags; - - // Create new empty stream - pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); - if(pStream == NULL) - return NULL; - - // Sanity check - assert(pStream->BaseOpen != NULL); - - // Get the length of the file name without numeric suffix - nNameLength = _tcslen(pStream->szFileName); - if(pStream->szFileName[nNameLength - 2] == '.' && pStream->szFileName[nNameLength - 1] == '0') - nNameLength -= 2; - pStream->szFileName[nNameLength] = 0; - - // Supply the stream functions - pStream->StreamRead = (STREAM_READ)BlockStream_Read; - pStream->StreamGetSize = BlockStream_GetSize; - pStream->StreamGetPos = BlockStream_GetPos; - pStream->StreamClose = (STREAM_CLOSE)Block4Stream_Close; - pStream->BlockRead = (BLOCK_READ)Block4Stream_BlockRead; - - // Allocate work space for numeric names - szNameBuff = STORM_ALLOC(TCHAR, nNameLength + 4); - if(szNameBuff != NULL) - { - // Set the base flags - dwBaseFlags = (dwStreamFlags & STREAM_PROVIDERS_MASK) | STREAM_FLAG_READ_ONLY; - - // Go all suffixes from 0 to 30 - for(int nSuffix = 0; nSuffix < 30; nSuffix++) - { - // Open the n-th file - _stprintf(szNameBuff, _T("%s.%u"), pStream->szFileName, nSuffix); - if(!pStream->BaseOpen(pStream, szNameBuff, dwBaseFlags)) - break; - - // If the open succeeded, we re-allocate the base provider array - NewBaseArray = STORM_ALLOC(TBaseProviderData, dwBaseFiles + 1); - if(NewBaseArray == NULL) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return NULL; - } - - // Copy the old base data array to the new base data array - if(pStream->FileBitmap != NULL) - { - memcpy(NewBaseArray, pStream->FileBitmap, sizeof(TBaseProviderData) * dwBaseFiles); - STORM_FREE(pStream->FileBitmap); - } - - // Also copy the opened base array - memcpy(NewBaseArray + dwBaseFiles, &pStream->Base, sizeof(TBaseProviderData)); - pStream->FileBitmap = NewBaseArray; - dwBaseFiles++; - - // Get the size of the base stream - pStream->BaseGetSize(pStream, &FileSize); - assert(FileSize <= BLOCK4_MAX_FSIZE); - RemainderBlock = FileSize % (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE); - BlockCount = FileSize / (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE); - - // Increment the stream size and number of blocks - pStream->StreamSize += (BlockCount * BLOCK4_BLOCK_SIZE); - pStream->BlockCount += (DWORD)BlockCount; - - // Is this the last file? - if(FileSize < BLOCK4_MAX_FSIZE) - { - if(RemainderBlock) - { - pStream->StreamSize += (RemainderBlock - BLOCK4_HASH_SIZE); - pStream->BlockCount++; - } - break; - } - } - - // Fill the remainining block stream variables - pStream->BitmapSize = dwBaseFiles; - pStream->BlockSize = BLOCK4_BLOCK_SIZE; - pStream->IsComplete = 1; - pStream->IsModified = 0; - - // Fill the remaining stream variables - pStream->StreamPos = 0; - pStream->dwFlags |= STREAM_FLAG_READ_ONLY; - - STORM_FREE(szNameBuff); - } - - // If we opened something, return success - if(dwBaseFiles == 0) - { - FileStream_Close(pStream); - SetLastError(ERROR_FILE_NOT_FOUND); - pStream = NULL; - } - - return pStream; -} - -//----------------------------------------------------------------------------- -// Public functions - -/** - * 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 - * can open the file for read, but not for write) - * - If the file does not exist, the function must create new one - * - If the file exists, the function must rewrite it and set to zero size - * - The parameters of the function must be validate by the caller - * - 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 create - */ - -TFileStream * FileStream_CreateFile( - const TCHAR * szFileName, - DWORD dwStreamFlags) -{ - TFileStream * pStream; - - // We only support creation of flat, local file - if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE)) - { - SetLastError(ERROR_NOT_SUPPORTED); - return NULL; - } - - // Allocate file stream structure for flat stream - pStream = AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); - if(pStream != NULL) - { - // Attempt to create the disk file - if(BaseFile_Create(pStream)) - { - // Fill the stream provider functions - pStream->StreamRead = pStream->BaseRead; - pStream->StreamWrite = pStream->BaseWrite; - pStream->StreamResize = pStream->BaseResize; - pStream->StreamGetSize = pStream->BaseGetSize; - pStream->StreamGetPos = pStream->BaseGetPos; - pStream->StreamClose = pStream->BaseClose; - return pStream; - } - - // File create failed, delete the stream - STORM_FREE(pStream); - pStream = NULL; - } - - // Return the stream - return pStream; -} - -/** - * This function opens an existing file for read or read-write access - * - If the current platform supports file sharing, - * the file must be open for read sharing (i.e. another application - * can open the file for read, but not for write) - * - If the file does not exist, the function must return NULL - * - 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 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 dwStreamFlags specifies the provider and base storage type - */ - -TFileStream * FileStream_OpenFile( - const TCHAR * szFileName, - DWORD dwStreamFlags) -{ - DWORD dwProvider = dwStreamFlags & STREAM_PROVIDERS_MASK; - size_t nPrefixLength = FileStream_Prefix(szFileName, &dwProvider); - - // Re-assemble the stream flags - dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | dwProvider; - szFileName += nPrefixLength; - - // Perform provider-specific open - switch(dwStreamFlags & STREAM_PROVIDER_MASK) - { - case STREAM_PROVIDER_FLAT: - return FlatStream_Open(szFileName, dwStreamFlags); - - case STREAM_PROVIDER_PARTIAL: - return PartStream_Open(szFileName, dwStreamFlags); - - case STREAM_PROVIDER_MPQE: - return MpqeStream_Open(szFileName, dwStreamFlags); - - case STREAM_PROVIDER_BLOCK4: - return Block4Stream_Open(szFileName, dwStreamFlags); - - default: - SetLastError(ERROR_INVALID_PARAMETER); - return NULL; - } -} - -/** - * Returns the file name of the stream - * - * \a pStream Pointer to an open stream - */ -const TCHAR * FileStream_GetFileName(TFileStream * pStream) -{ - assert(pStream != NULL); - return pStream->szFileName; -} - -/** - * Returns the length of the provider prefix. Returns zero if no prefix - * - * \a szFileName Pointer to a stream name (file, mapped file, URL) - * \a pdwStreamProvider Pointer to a DWORD variable that receives stream provider (STREAM_PROVIDER_XXX) - */ - -size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider) -{ - size_t nPrefixLength1 = 0; - size_t nPrefixLength2 = 0; - DWORD dwProvider = 0; - - if(szFileName != NULL) - { - // - // Determine the stream provider - // - - if(!_tcsnicmp(szFileName, _T("flat-"), 5)) - { - dwProvider |= STREAM_PROVIDER_FLAT; - nPrefixLength1 = 5; - } - - else if(!_tcsnicmp(szFileName, _T("part-"), 5)) - { - dwProvider |= STREAM_PROVIDER_PARTIAL; - nPrefixLength1 = 5; - } - - else if(!_tcsnicmp(szFileName, _T("mpqe-"), 5)) - { - dwProvider |= STREAM_PROVIDER_MPQE; - nPrefixLength1 = 5; - } - - else if(!_tcsnicmp(szFileName, _T("blk4-"), 5)) - { - dwProvider |= STREAM_PROVIDER_BLOCK4; - nPrefixLength1 = 5; - } - - // - // Determine the base provider - // - - if(!_tcsnicmp(szFileName+nPrefixLength1, _T("file:"), 5)) - { - dwProvider |= BASE_PROVIDER_FILE; - nPrefixLength2 = 5; - } - - else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4)) - { - dwProvider |= BASE_PROVIDER_MAP; - nPrefixLength2 = 4; - } - - else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5)) - { - dwProvider |= BASE_PROVIDER_HTTP; - nPrefixLength2 = 5; - } - - // Only accept stream provider if we recognized the base provider - if(nPrefixLength2 != 0) - { - // It is also allowed to put "//" after the base provider, e.g. "file://", "http://" - if(szFileName[nPrefixLength1+nPrefixLength2] == '/' && szFileName[nPrefixLength1+nPrefixLength2+1] == '/') - nPrefixLength2 += 2; - - if(pdwProvider != NULL) - *pdwProvider = dwProvider; - return nPrefixLength1 + nPrefixLength2; - } - } - - return 0; -} - -/** - * Sets a download callback. Whenever the stream needs to download one or more blocks - * from the server, the callback is called - * - * \a pStream Pointer to an open stream - * \a pfnCallback Pointer to callback function - * \a pvUserData Arbitrary user pointer passed to the download callback - */ - -bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData) -{ - TBlockStream * pBlockStream = (TBlockStream *)pStream; - - if(pStream->BlockRead == NULL) - { - SetLastError(ERROR_NOT_SUPPORTED); - return false; - } - - pBlockStream->pfnCallback = pfnCallback; - pBlockStream->UserData = pvUserData; - return true; -} - -/** - * This function gives the block map. The 'pvBitmap' pointer must point to a buffer - * of at least sizeof(STREAM_BLOCK_MAP) size. It can also have size of the complete - * block map (i.e. sizeof(STREAM_BLOCK_MAP) + BitmapSize). In that case, the function - * also copies the bit-based block map. - * - * \a pStream Pointer to an open stream - * \a pvBitmap Pointer to buffer where the block map will be stored - * \a cbBitmap Length of the buffer, of the block map - * \a cbLengthNeeded Length of the bitmap, in bytes - */ - -bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, DWORD * pcbLengthNeeded) -{ - TStreamBitmap * pBitmap = (TStreamBitmap *)pvBitmap; - TBlockStream * pBlockStream = (TBlockStream *)pStream; - ULONGLONG BlockOffset; - LPBYTE Bitmap = (LPBYTE)(pBitmap + 1); - DWORD BitmapSize; - DWORD BlockCount; - DWORD BlockSize; - bool bResult = false; - - // Retrieve the size of one block - if(pStream->BlockCheck != NULL) - { - BlockCount = pBlockStream->BlockCount; - BlockSize = pBlockStream->BlockSize; - } - else - { - BlockCount = (DWORD)((pStream->StreamSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE); - BlockSize = DEFAULT_BLOCK_SIZE; - } - - // Fill-in the variables - BitmapSize = (BlockCount + 7) / 8; - - // Give the number of blocks - if(pcbLengthNeeded != NULL) - pcbLengthNeeded[0] = sizeof(TStreamBitmap) + BitmapSize; - - // If the length of the buffer is not enough - if(pvBitmap != NULL && cbBitmap != 0) - { - // Give the STREAM_BLOCK_MAP structure - if(cbBitmap >= sizeof(TStreamBitmap)) - { - pBitmap->StreamSize = pStream->StreamSize; - pBitmap->BitmapSize = BitmapSize; - pBitmap->BlockCount = BlockCount; - pBitmap->BlockSize = BlockSize; - pBitmap->IsComplete = (pStream->BlockCheck != NULL) ? pBlockStream->IsComplete : 1; - bResult = true; - } - - // Give the block bitmap, if enough space - if(cbBitmap >= sizeof(TStreamBitmap) + BitmapSize) - { - // Version with bitmap present - if(pStream->BlockCheck != NULL) - { - DWORD ByteIndex = 0; - BYTE BitMask = 0x01; - - // Initialize the map with zeros - memset(Bitmap, 0, BitmapSize); - - // Fill the map - for(BlockOffset = 0; BlockOffset < pStream->StreamSize; BlockOffset += BlockSize) - { - // Set the bit if the block is present - if(pBlockStream->BlockCheck(pStream, BlockOffset)) - Bitmap[ByteIndex] |= BitMask; - - // Move bit position - ByteIndex += (BitMask >> 0x07); - BitMask = (BitMask >> 0x07) | (BitMask << 0x01); - } - } - else - { - memset(Bitmap, 0xFF, BitmapSize); - } - } - } - - // Set last error value and return - if(bResult == false) - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return bResult; -} - -/** - * Reads data from the stream - * - * - Returns true if the read operation succeeded and all bytes have been read - * - Returns false if either read failed or not all bytes have been read - * - If the pByteOffset is NULL, the function must read the data from the current file position - * - The function can be called with dwBytesToRead = 0. In that case, pvBuffer is ignored - * and the function just adjusts file pointer. - * - * \a pStream Pointer to an open stream - * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position - * \a pvBuffer Pointer to data to be read - * \a dwBytesToRead Number of bytes to read from the file - * - * \returns - * - If the function reads the required amount of bytes, it returns true. - * - If the function reads less than required bytes, it returns false and GetLastError() returns ERROR_HANDLE_EOF - * - If the function fails, it reads false and GetLastError() returns an error code different from ERROR_HANDLE_EOF - */ -bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead) -{ - assert(pStream->StreamRead != NULL); - return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); -} - -/** - * This function writes data to the stream - * - * - Returns true if the write operation succeeded and all bytes have been written - * - Returns false if either write failed or not all bytes have been written - * - If the pByteOffset is NULL, the function must write the data to the current file position - * - * \a pStream Pointer to an open stream - * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position - * \a pvBuffer Pointer to data to be written - * \a dwBytesToWrite Number of bytes to write to the file - */ -bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite) -{ - if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - assert(pStream->StreamWrite != NULL); - return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite); -} - -/** - * Returns the size of a file - * - * \a pStream Pointer to an open stream - * \a FileSize Pointer where to store the file size - */ -bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) -{ - assert(pStream->StreamGetSize != NULL); - return pStream->StreamGetSize(pStream, pFileSize); -} - -/** - * Sets the size of a file - * - * \a pStream Pointer to an open stream - * \a NewFileSize File size to set - */ -bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize) -{ - if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - assert(pStream->StreamResize != NULL); - return pStream->StreamResize(pStream, NewFileSize); -} - -/** - * This function returns the current file position - * \a pStream - * \a pByteOffset - */ -bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) -{ - assert(pStream->StreamGetPos != NULL); - return pStream->StreamGetPos(pStream, pByteOffset); -} - -/** - * 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) -{ - // Just use the saved filetime value - *pFileTime = pStream->Base.File.FileTime; - return true; -} - -/** - * Returns the stream flags - * - * \a pStream Pointer to an open stream - * \a pdwStreamFlags Pointer where to store the stream flags - */ -bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags) -{ - *pdwStreamFlags = pStream->dwFlags; - return true; -} - -/** - * Switches a stream with another. Used for final phase of archive compacting. - * Performs these steps: - * - * 1) Closes the handle to the existing MPQ - * 2) Renames the temporary MPQ to the original MPQ, overwrites existing one - * 3) Opens the MPQ stores the handle and stream position to the new stream structure - * - * \a pStream Pointer to an open stream - * \a pNewStream Temporary ("working") stream (created during archive compacting) - */ -bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream) -{ - // Only supported on flat files - if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE)) - { - SetLastError(ERROR_NOT_SUPPORTED); - return false; - } - - // Not supported on read-only streams - if(pStream->dwFlags & STREAM_FLAG_READ_ONLY) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - // Close both stream's base providers - pNewStream->BaseClose(pNewStream); - pStream->BaseClose(pStream); - - // Now we have to delete the (now closed) old file and rename the new file - if(!BaseFile_Replace(pStream, pNewStream)) - return false; - - // Now open the base file again - if(!BaseFile_Open(pStream, pStream->szFileName, pStream->dwFlags)) - return false; - - // Cleanup the new stream - FileStream_Close(pNewStream); - return true; -} - -/** - * 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 - * can be NULL, if there was an allocation failure during the process - * - * \a pStream Pointer to an open stream - */ -void FileStream_Close(TFileStream * pStream) -{ - // Check if the stream structure is allocated at all - if(pStream != NULL) - { - // Free the master stream, if any - if(pStream->pMaster != NULL) - FileStream_Close(pStream->pMaster); - pStream->pMaster = NULL; - - // Close the stream provider. - if(pStream->StreamClose != NULL) - pStream->StreamClose(pStream); - - // Also close base stream, if any - else if(pStream->BaseClose != NULL) - pStream->BaseClose(pStream); - - // Free the stream itself - STORM_FREE(pStream); - } -} - -//----------------------------------------------------------------------------- -// Utility functions (ANSI) - -const char * GetPlainFileName(const char * szFileName) -{ - const char * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - -void CopyFileName(char * szTarget, const char * szSource, size_t cchLength) -{ - memcpy(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -//----------------------------------------------------------------------------- -// Utility functions (UNICODE) only exist in the ANSI version of the library -// In ANSI builds, TCHAR = char, so we don't need these functions implemented - -#ifdef _UNICODE -const TCHAR * GetPlainFileName(const TCHAR * szFileName) -{ - const TCHAR * szPlainName = szFileName; - - while(*szFileName != 0) - { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; - } - - return szPlainName; -} - -void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength) -{ - mbstowcs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} - -void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength) -{ - wcstombs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; -} -#endif +/*****************************************************************************/
+/* FileStream.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* File stream support for StormLib */
+/* */
+/* Windows support: Written by Ladislav Zezula */
+/* Mac support: Written by Sam Wilkins */
+/* Linux support: Written by Sam Wilkins and Ivan Komissarov */
+/* Big-endian: Written & debugged by Sam Wilkins */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 11.06.10 1.00 Lad Derived from StormPortMac.cpp and StormPortLinux.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+#include "FileStream.h"
+
+#ifdef _MSC_VER
+#pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream
+#pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)
+#endif
+
+//-----------------------------------------------------------------------------
+// Local defines
+
+#ifndef INVALID_HANDLE_VALUE
+#define INVALID_HANDLE_VALUE ((HANDLE)-1)
+#endif
+
+//-----------------------------------------------------------------------------
+// Local functions - platform-specific functions
+
+#ifndef PLATFORM_WINDOWS
+static int nLastError = ERROR_SUCCESS;
+
+int GetLastError()
+{
+ return nLastError;
+}
+
+void SetLastError(int nError)
+{
+ nLastError = nError;
+}
+#endif
+
+static DWORD StringToInt(const char * szString)
+{
+ DWORD dwValue = 0;
+
+ while('0' <= szString[0] && szString[0] <= '9')
+ {
+ dwValue = (dwValue * 10) + (szString[0] - '9');
+ szString++;
+ }
+
+ return dwValue;
+}
+
+//-----------------------------------------------------------------------------
+// Dummy init function
+
+static void BaseNone_Init(TFileStream *)
+{
+ // Nothing here
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - base file support
+
+static bool BaseFile_Create(TFileStream * pStream)
+{
+#ifdef PLATFORM_WINDOWS
+ {
+ DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
+
+ pStream->Base.File.hFile = CreateFile(pStream->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 defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ intptr_t handle;
+
+ handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if(handle == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ pStream->Base.File.hFile = (HANDLE)handle;
+ }
+#endif
+
+ // Reset the file size and position
+ pStream->Base.File.FileSize = 0;
+ pStream->Base.File.FilePos = 0;
+ return true;
+}
+
+static bool BaseFile_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+#ifdef PLATFORM_WINDOWS
+ {
+ ULARGE_INTEGER FileSize;
+ DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES;
+ DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
+
+ // Open the file
+ pStream->Base.File.hFile = CreateFile(szFileName,
+ FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess,
+ FILE_SHARE_READ | dwWriteShare,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
+ return false;
+
+ // Query the file size
+ FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart);
+ pStream->Base.File.FileSize = FileSize.QuadPart;
+
+ // Query last write time
+ GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime);
+ }
+#endif
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ struct stat64 fileinfo;
+ int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR;
+ intptr_t handle;
+
+ // Open the file
+ handle = open(szFileName, oflag | O_LARGEFILE);
+ if(handle == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ // Get the file size
+ if(fstat64(handle, &fileinfo) == -1)
+ {
+ 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
+
+ // Reset the file position
+ pStream->Base.File.FilePos = 0;
+ return true;
+}
+
+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
+{
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
+ DWORD dwBytesRead = 0; // Must be set by platform-specific code
+
+#ifdef PLATFORM_WINDOWS
+ {
+ // 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
+
+ // Update the byte offset
+ pStream->Base.File.FilePos = ByteOffset;
+
+ // Read the data
+ if(dwBytesToRead != 0)
+ {
+ 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;
+ }
+ }
+#endif
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ ssize_t bytes_read;
+
+ // If the byte offset is different from the current file position,
+ // we have to update the file position xxx
+ if(ByteOffset != pStream->Base.File.FilePos)
+ {
+ lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET);
+ pStream->Base.File.FilePos = ByteOffset;
+ }
+
+ // 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
+
+ // 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);
+}
+
+/**
+ * \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)
+{
+ ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
+ DWORD dwBytesWritten = 0; // Must be set by platform-specific code
+
+#ifdef PLATFORM_WINDOWS
+ {
+ // 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
+
+ // Update the byte offset
+ pStream->Base.File.FilePos = ByteOffset;
+
+ // Read the data
+ if(dwBytesToWrite != 0)
+ {
+ OVERLAPPED Overlapped;
+
+ Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32);
+ Overlapped.Offset = (DWORD)ByteOffset;
+ Overlapped.hEvent = NULL;
+ if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, &Overlapped))
+ return false;
+ }
+ }
+#endif
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ ssize_t bytes_written;
+
+ // If the byte offset is different from the current file position,
+ // we have to update the file position
+ if(ByteOffset != pStream->Base.File.FilePos)
+ {
+ lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET);
+ pStream->Base.File.FilePos = ByteOffset;
+ }
+
+ // 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
+
+ // 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);
+}
+
+/**
+ * \a pStream Pointer to an open stream
+ * \a NewFileSize New size of the file
+ */
+static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize)
+{
+#ifdef PLATFORM_WINDOWS
+ {
+ LONG FileSizeHi = (LONG)(NewFileSize >> 32);
+ LONG FileSizeLo;
+ DWORD dwNewPos;
+ bool bResult;
+
+ // 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;
+
+ // Set the current file pointer as the end of the file
+ bResult = (bool)SetEndOfFile(pStream->Base.File.hFile);
+ if(bResult)
+ pStream->Base.File.FileSize = NewFileSize;
+
+ // 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
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ {
+ if(ftruncate64((intptr_t)pStream->Base.File.hFile, (off64_t)NewFileSize) == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ pStream->Base.File.FileSize = NewFileSize;
+ return true;
+ }
+#endif
+}
+
+// Gives the current file size
+static bool BaseFile_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
+{
+ // Note: Used by all thre base providers.
+ // Requires the TBaseData union to have the same layout for all three base providers
+ *pFileSize = pStream->Base.File.FileSize;
+ return true;
+}
+
+// Gives the current file position
+static bool BaseFile_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ // Note: Used by all thre base providers.
+ // Requires the TBaseData union to have the same layout for all three base providers
+ *pByteOffset = pStream->Base.File.FilePos;
+ return true;
+}
+
+// Renames the file pointed by pStream so that it contains data from pNewStream
+static bool BaseFile_Replace(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
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ // "rename" on Linux also works if the target file exists
+ if(rename(pNewStream->szFileName, pStream->szFileName) == -1)
+ {
+ nLastError = errno;
+ return false;
+ }
+
+ return true;
+#endif
+}
+
+static void BaseFile_Close(TFileStream * pStream)
+{
+ 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;
+}
+
+// Initializes base functions for the disk file
+static void BaseFile_Init(TFileStream * pStream)
+{
+ pStream->BaseCreate = BaseFile_Create;
+ pStream->BaseOpen = BaseFile_Open;
+ pStream->BaseRead = BaseFile_Read;
+ pStream->BaseWrite = BaseFile_Write;
+ pStream->BaseResize = BaseFile_Resize;
+ pStream->BaseGetSize = BaseFile_GetSize;
+ pStream->BaseGetPos = BaseFile_GetPos;
+ pStream->BaseClose = BaseFile_Close;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - base memory-mapped file support
+
+static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+#ifdef PLATFORM_WINDOWS
+
+ ULARGE_INTEGER FileSize;
+ HANDLE hFile;
+ HANDLE hMap;
+ bool bResult = false;
+
+ // Keep compiler happy
+ dwStreamFlags = dwStreamFlags;
+
+ // Open the file for read access
+ hFile = CreateFile(szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if(hFile != INVALID_HANDLE_VALUE)
+ {
+ // Retrieve file size. Don't allow mapping file of a zero size.
+ FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart);
+ if(FileSize.QuadPart != 0)
+ {
+ // 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)
+ {
+ // Retrieve file time
+ GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime);
+
+ // Retrieve file size and position
+ pStream->Base.Map.FileSize = FileSize.QuadPart;
+ pStream->Base.Map.FilePos = 0;
+ bResult = true;
+ }
+
+ // Close the map handle
+ CloseHandle(hMap);
+ }
+ }
+
+ // 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
+
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX)
+ struct stat64 fileinfo;
+ intptr_t handle;
+ bool bResult = false;
+
+ // Open the file
+ handle = open(szFileName, O_RDONLY);
+ if(handle != -1)
+ {
+ // Get the file size
+ if(fstat64(handle, &fileinfo) != -1)
+ {
+ 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);
+ }
+
+ // Did the mapping fail?
+ if(bResult == false)
+ {
+ nLastError = errno;
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+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;
+
+ // 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);
+ }
+
+ // Move the current file position
+ pStream->Base.Map.FilePos += dwBytesToRead;
+ 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;
+}
+
+// Initializes base functions for the mapped file
+static void BaseMap_Init(TFileStream * pStream)
+{
+ // Supply the file stream functions
+ pStream->BaseOpen = BaseMap_Open;
+ pStream->BaseRead = BaseMap_Read;
+ pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
+ pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
+ pStream->BaseClose = BaseMap_Close;
+
+ // Mapped files are read-only
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+}
+
+//-----------------------------------------------------------------------------
+// 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)
+ {
+ while(szFileName[0] != 0 && szFileName[0] != _T('/'))
+ *szServerName++ = *szFileName++;
+ *szServerName = 0;
+ }
+ else
+ {
+ while(szFileName[0] != 0 && szFileName[0] != _T('/'))
+ szFileName++;
+ }
+
+ // Return the remainder
+ return szFileName;
+}
+
+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;
+
+ // Keep compiler happy
+ dwStreamFlags = dwStreamFlags;
+
+ // Don't connect to the internet
+ if(!InternetGetConnectedState(&dwTemp, 0))
+ return false;
+
+ // Initiate the connection to the internet
+ pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"),
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL,
+ NULL,
+ 0);
+ if(pStream->Base.Http.hInternet == NULL)
+ return false;
+
+ // 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)
+ {
+ InternetCloseHandle(pStream->Base.Http.hInternet);
+ return false;
+ }
+ }
+
+ // 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 the file is not there and is not available for random access,
+ // report error
+ if(bFileAvailable == false)
+ {
+ pStream->BaseClose(pStream);
+ return false;
+ }
+
+ return true;
+
+#else
+
+ // Not supported
+ SetLastError(ERROR_NOT_SUPPORTED);
+ pStream = pStream;
+ return false;
+
+#endif
+}
+
+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;
+
+ // 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)
+ {
+ // Add range request to the HTTP headers
+ // http://www.clevercomponents.com/articles/article015/resuming.asp
+ _stprintf(szRangeRequest, _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset);
+ HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW);
+
+ // 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 > 0x200)
+ dwBlockBytesToRead = 0x200;
+ InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead);
+
+ // Check for end
+ if(dwBlockBytesRead == 0)
+ break;
+
+ // Move buffers
+ dwTotalBytesRead += dwBlockBytesRead;
+ pbBuffer += dwBlockBytesRead;
+ }
+ }
+ InternetCloseHandle(hRequest);
+ }
+ }
+
+ // Increment the current file position by number of bytes read
+ 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 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
+}
+
+// Initializes base functions for the mapped file
+static void BaseHttp_Init(TFileStream * pStream)
+{
+ // Supply the stream functions
+ pStream->BaseOpen = BaseHttp_Open;
+ pStream->BaseRead = BaseHttp_Read;
+ pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
+ pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
+ pStream->BaseClose = BaseHttp_Close;
+
+ // HTTP files are read-only
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - base block-based support
+
+// Generic function that loads blocks from the file
+// The function groups the block with the same availability,
+// so the called BlockRead can finish the request in a single system call
+static bool BlockStream_Read(
+ TBlockStream * 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 BlockOffset0;
+ ULONGLONG BlockOffset;
+ ULONGLONG ByteOffset;
+ ULONGLONG EndOffset;
+ LPBYTE TransferBuffer;
+ LPBYTE BlockBuffer;
+ DWORD BlockBufferOffset; // Offset of the desired data in the block buffer
+ DWORD BytesNeeded; // Number of bytes that really need to be read
+ DWORD BlockSize = pStream->BlockSize;
+ DWORD BlockCount;
+ bool bPrevBlockAvailable;
+ bool bCallbackCalled = false;
+ bool bBlockAvailable;
+ bool bResult = true;
+
+ // The base block read function must be present
+ assert(pStream->BlockRead != NULL);
+
+ // NOP reading of zero bytes
+ if(dwBytesToRead == 0)
+ return true;
+
+ // Get the current position in the stream
+ ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos;
+ EndOffset = ByteOffset + dwBytesToRead;
+ if(EndOffset > pStream->StreamSize)
+ {
+ SetLastError(ERROR_HANDLE_EOF);
+ return false;
+ }
+
+ // Calculate the block parameters
+ BlockOffset0 = BlockOffset = ByteOffset & ~((ULONGLONG)BlockSize - 1);
+ BlockCount = (DWORD)(((EndOffset - BlockOffset) + (BlockSize - 1)) / BlockSize);
+ BytesNeeded = (DWORD)(EndOffset - BlockOffset);
+
+ // Remember where we have our data
+ assert((BlockSize & (BlockSize - 1)) == 0);
+ BlockBufferOffset = (DWORD)(ByteOffset & (BlockSize - 1));
+
+ // Allocate buffer for reading blocks
+ TransferBuffer = BlockBuffer = STORM_ALLOC(BYTE, (BlockCount * BlockSize));
+ if(TransferBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return false;
+ }
+
+ // If all blocks are available, just read all blocks at once
+ if(pStream->IsComplete == 0)
+ {
+ // Now parse the blocks and send the block read request
+ // to all blocks with the same availability
+ assert(pStream->BlockCheck != NULL);
+ bPrevBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
+
+ // Loop as long as we have something to read
+ while(BlockOffset < EndOffset)
+ {
+ // Determine availability of the next block
+ bBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
+
+ // If the availability has changed, read all blocks up to this one
+ if(bBlockAvailable != bPrevBlockAvailable)
+ {
+ // Call the file stream callback, if the block is not available
+ if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false)
+ {
+ pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0));
+ bCallbackCalled = true;
+ }
+
+ // Load the continuous blocks with the same availability
+ assert(BlockOffset > BlockOffset0);
+ bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
+ if(!bResult)
+ break;
+
+ // Move the block offset
+ BlockBuffer += (DWORD)(BlockOffset - BlockOffset0);
+ BytesNeeded -= (DWORD)(BlockOffset - BlockOffset0);
+ bPrevBlockAvailable = bBlockAvailable;
+ BlockOffset0 = BlockOffset;
+ }
+
+ // Move to the block offset in the stream
+ BlockOffset += BlockSize;
+ }
+
+ // If there is a block(s) remaining to be read, do it
+ if(BlockOffset > BlockOffset0)
+ {
+ // Call the file stream callback, if the block is not available
+ if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false)
+ {
+ pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0));
+ bCallbackCalled = true;
+ }
+
+ // Read the complete blocks from the file
+ if(BlockOffset > pStream->StreamSize)
+ BlockOffset = pStream->StreamSize;
+ bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
+ }
+ }
+ else
+ {
+ // Read the complete blocks from the file
+ if(EndOffset > pStream->StreamSize)
+ EndOffset = pStream->StreamSize;
+ bResult = pStream->BlockRead(pStream, BlockOffset, EndOffset, BlockBuffer, BytesNeeded, true);
+ }
+
+ // Now copy the data to the user buffer
+ if(bResult)
+ {
+ memcpy(pvBuffer, TransferBuffer + BlockBufferOffset, dwBytesToRead);
+ pStream->StreamPos = ByteOffset + dwBytesToRead;
+ }
+ else
+ {
+ // If the block read failed, set the last error
+ SetLastError(ERROR_FILE_INCOMPLETE);
+ }
+
+ // Call the callback to indicate we are done
+ if(bCallbackCalled)
+ pStream->pfnCallback(pStream->UserData, 0, 0);
+
+ // Free the block buffer and return
+ STORM_FREE(TransferBuffer);
+ return bResult;
+}
+
+static bool BlockStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
+{
+ *pFileSize = pStream->StreamSize;
+ return true;
+}
+
+static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ *pByteOffset = pStream->StreamPos;
+ return true;
+}
+
+static void BlockStream_Close(TBlockStream * pStream)
+{
+ // Free the data map, if any
+ if(pStream->FileBitmap != NULL)
+ STORM_FREE(pStream->FileBitmap);
+ pStream->FileBitmap = NULL;
+
+ // Call the base class for closing the stream
+ pStream->BaseClose(pStream);
+}
+
+//-----------------------------------------------------------------------------
+// File stream allocation function
+
+static STREAM_INIT StreamBaseInit[4] =
+{
+ BaseFile_Init,
+ BaseMap_Init,
+ BaseHttp_Init,
+ BaseNone_Init
+};
+
+// This function allocates an empty structure for the file stream
+// The stream structure is created as flat block, variable length
+// The file name is placed after the end of the stream structure data
+static TFileStream * AllocateFileStream(
+ const TCHAR * szFileName,
+ size_t StreamSize,
+ DWORD dwStreamFlags)
+{
+ TFileStream * pMaster = NULL;
+ TFileStream * pStream;
+ const TCHAR * szNextFile = szFileName;
+ size_t FileNameSize;
+
+ // Sanity check
+ assert(StreamSize != 0);
+
+ // The caller can specify chain of files in the following form:
+ // C:\archive.MPQ*http://www.server.com/MPQs/archive-server.MPQ
+ // In that case, we use the part after "*" as master file name
+ while(szNextFile[0] != 0 && szNextFile[0] != _T('*'))
+ szNextFile++;
+ FileNameSize = (size_t)((szNextFile - szFileName) * sizeof(TCHAR));
+
+ // If we have a next file, we need to open it as master stream
+ // Note that we don't care if the master stream exists or not,
+ // If it doesn't, later attempts to read missing file block will fail
+ if(szNextFile[0] == _T('*'))
+ {
+ // Don't allow another master file in the string
+ if(_tcschr(szNextFile + 1, _T('*')) != NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return NULL;
+ }
+
+ // Open the master file
+ pMaster = FileStream_OpenFile(szNextFile + 1, STREAM_FLAG_READ_ONLY);
+ }
+
+ // Allocate the stream structure for the given stream type
+ pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize + sizeof(TCHAR));
+ if(pStream != NULL)
+ {
+ // Zero the entire structure
+ memset(pStream, 0, StreamSize);
+ pStream->pMaster = pMaster;
+ pStream->dwFlags = dwStreamFlags;
+
+ // Initialize the file name
+ pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize);
+ memcpy(pStream->szFileName, szFileName, FileNameSize);
+ pStream->szFileName[FileNameSize / sizeof(TCHAR)] = 0;
+
+ // Initialize the stream functions
+ StreamBaseInit[dwStreamFlags & 0x03](pStream);
+ }
+
+ return pStream;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - flat stream support
+
+static DWORD FlatStream_CheckFile(TBlockStream * pStream)
+{
+ LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
+ DWORD WholeByteCount = (pStream->BlockCount / 8);
+ DWORD ExtraBitsCount = (pStream->BlockCount & 7);
+ BYTE ExpectedValue;
+
+ // Verify the whole bytes - their value must be 0xFF
+ for(DWORD i = 0; i < WholeByteCount; i++)
+ {
+ if(FileBitmap[i] != 0xFF)
+ return 0;
+ }
+
+ // If there are extra bits, calculate the mask
+ if(ExtraBitsCount != 0)
+ {
+ ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1);
+ if(FileBitmap[WholeByteCount] != ExpectedValue)
+ return 0;
+ }
+
+ // Yes, the file is complete
+ return 1;
+}
+
+static bool FlatStream_LoadBitmap(TBlockStream * pStream)
+{
+ FILE_BITMAP_FOOTER Footer;
+ ULONGLONG ByteOffset;
+ LPBYTE FileBitmap;
+ DWORD BlockCount;
+ DWORD BitmapSize;
+
+ // Do not load the bitmap if we should not have to
+ if(!(pStream->dwFlags & STREAM_FLAG_USE_BITMAP))
+ return false;
+
+ // Only if the size is greater than size of bitmap footer
+ if(pStream->Base.File.FileSize > sizeof(FILE_BITMAP_FOOTER))
+ {
+ // Load the bitmap footer
+ ByteOffset = pStream->Base.File.FileSize - sizeof(FILE_BITMAP_FOOTER);
+ if(pStream->BaseRead(pStream, &ByteOffset, &Footer, sizeof(FILE_BITMAP_FOOTER)))
+ {
+ // Make sure that the array is properly BSWAP-ed
+ BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&Footer), sizeof(FILE_BITMAP_FOOTER));
+
+ // Verify if there is actually a footer
+ if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03)
+ {
+ // Get the offset of the bitmap, number of blocks and size of the bitmap
+ ByteOffset = MAKE_OFFSET64(Footer.MapOffsetHi, Footer.MapOffsetLo);
+ BlockCount = (DWORD)(((ByteOffset - 1) / Footer.BlockSize) + 1);
+ BitmapSize = ((BlockCount + 7) / 8);
+
+ // Check if the sizes match
+ if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize)
+ {
+ // Allocate space for the bitmap
+ FileBitmap = STORM_ALLOC(BYTE, BitmapSize);
+ if(FileBitmap != NULL)
+ {
+ // Load the bitmap bits
+ if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize))
+ {
+ STORM_FREE(FileBitmap);
+ return false;
+ }
+
+ // Update the stream size
+ pStream->BuildNumber = Footer.BuildNumber;
+ pStream->StreamSize = ByteOffset;
+
+ // Fill the bitmap information
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = BitmapSize;
+ pStream->BlockSize = Footer.BlockSize;
+ pStream->BlockCount = BlockCount;
+ pStream->IsComplete = FlatStream_CheckFile(pStream);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static void FlatStream_UpdateBitmap(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset)
+{
+ LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
+ DWORD BlockIndex;
+ DWORD BlockSize = pStream->BlockSize;
+ DWORD ByteIndex;
+ BYTE BitMask;
+
+ // Sanity checks
+ assert((StartOffset & (BlockSize - 1)) == 0);
+ assert(FileBitmap != NULL);
+
+ // Calculate the index of the block
+ BlockIndex = (DWORD)(StartOffset / BlockSize);
+ ByteIndex = (BlockIndex / 0x08);
+ BitMask = (BYTE)(1 << (BlockIndex & 0x07));
+
+ // Set all bits for the specified range
+ while(StartOffset < EndOffset)
+ {
+ // Set the bit
+ FileBitmap[ByteIndex] |= BitMask;
+
+ // Move all
+ StartOffset += BlockSize;
+ ByteIndex += (BitMask >> 0x07);
+ BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
+ }
+
+ // Increment the bitmap update count
+ pStream->IsModified = 1;
+}
+
+static bool FlatStream_BlockCheck(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG BlockOffset)
+{
+ LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
+ DWORD BlockIndex;
+ BYTE BitMask;
+
+ // Sanity checks
+ assert((BlockOffset & (pStream->BlockSize - 1)) == 0);
+ assert(FileBitmap != NULL);
+
+ // Calculate the index of the block
+ BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize);
+ BitMask = (BYTE)(1 << (BlockIndex & 0x07));
+
+ // Check if the bit is present
+ return (FileBitmap[BlockIndex / 0x08] & BitMask) ? true : false;
+}
+
+static bool FlatStream_BlockRead(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
+{
+ DWORD BytesToRead = (DWORD)(EndOffset - StartOffset);
+
+ // The starting offset must be aligned to size of the block
+ assert(pStream->FileBitmap != NULL);
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+
+ // If the blocks are not available, we need to load them from the master
+ // and then save to the mirror
+ if(bAvailable == false)
+ {
+ // If we have no master, we cannot satisfy read request
+ if(pStream->pMaster == NULL)
+ return false;
+
+ // Load the blocks from the master stream
+ // Note that we always have to read complete blocks
+ // so they get properly stored to the mirror stream
+ if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead))
+ return false;
+
+ // Store the loaded blocks to the mirror file.
+ // Note that this operation is not required to succeed
+ if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead))
+ FlatStream_UpdateBitmap(pStream, StartOffset, EndOffset);
+
+ return true;
+ }
+ else
+ {
+ if(BytesToRead > BytesNeeded)
+ BytesToRead = BytesNeeded;
+ return pStream->BaseRead(pStream, &StartOffset, BlockBuffer, BytesToRead);
+ }
+}
+
+static void FlatStream_Close(TBlockStream * pStream)
+{
+ FILE_BITMAP_FOOTER Footer;
+
+ if(pStream->FileBitmap && pStream->IsModified)
+ {
+ // Write the file bitmap
+ pStream->BaseWrite(pStream, &pStream->StreamSize, pStream->FileBitmap, pStream->BitmapSize);
+
+ // Prepare and write the file footer
+ Footer.Signature = ID_FILE_BITMAP_FOOTER;
+ Footer.Version = 3;
+ Footer.BuildNumber = pStream->BuildNumber;
+ Footer.MapOffsetLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF);
+ Footer.MapOffsetHi = (DWORD)(pStream->StreamSize >> 0x20);
+ Footer.BlockSize = pStream->BlockSize;
+ BSWAP_ARRAY32_UNSIGNED(&Footer, sizeof(FILE_BITMAP_FOOTER));
+ pStream->BaseWrite(pStream, NULL, &Footer, sizeof(FILE_BITMAP_FOOTER));
+ }
+
+ // Close the base class
+ BlockStream_Close(pStream);
+}
+
+static bool FlatStream_CreateMirror(TBlockStream * pStream)
+{
+ ULONGLONG MasterSize = 0;
+ ULONGLONG MirrorSize = 0;
+ LPBYTE FileBitmap = NULL;
+ DWORD dwBitmapSize;
+ DWORD dwBlockCount;
+ bool bNeedCreateMirrorStream = true;
+ bool bNeedResizeMirrorStream = true;
+
+ // Do we have master function and base creation function?
+ if(pStream->pMaster == NULL || pStream->BaseCreate == NULL)
+ return false;
+
+ // Retrieve the master file size, block count and bitmap size
+ FileStream_GetSize(pStream->pMaster, &MasterSize);
+ dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
+ dwBitmapSize = (DWORD)((dwBlockCount + 7) / 8);
+
+ // Setup stream size and position
+ pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really???
+ pStream->StreamSize = MasterSize;
+ pStream->StreamPos = 0;
+
+ // Open the base stream for write access
+ if(pStream->BaseOpen(pStream, pStream->szFileName, 0))
+ {
+ // If the file open succeeded, check if the file size matches required size
+ pStream->BaseGetSize(pStream, &MirrorSize);
+ if(MirrorSize == MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER))
+ {
+ // Attempt to load an existing file bitmap
+ if(FlatStream_LoadBitmap(pStream))
+ return true;
+
+ // We need to create new file bitmap
+ bNeedResizeMirrorStream = false;
+ }
+
+ // We need to create mirror stream
+ bNeedCreateMirrorStream = false;
+ }
+
+ // Create a new stream, if needed
+ if(bNeedCreateMirrorStream)
+ {
+ if(!pStream->BaseCreate(pStream))
+ return false;
+ }
+
+ // If we need to, then resize the mirror stream
+ if(bNeedResizeMirrorStream)
+ {
+ if(!pStream->BaseResize(pStream, MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER)))
+ return false;
+ }
+
+ // Allocate the bitmap array
+ FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize);
+ if(FileBitmap == NULL)
+ return false;
+
+ // Initialize the bitmap
+ memset(FileBitmap, 0, dwBitmapSize);
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = dwBitmapSize;
+ pStream->BlockSize = DEFAULT_BLOCK_SIZE;
+ pStream->BlockCount = dwBlockCount;
+ pStream->IsComplete = 0;
+ pStream->IsModified = 1;
+
+ // Note: Don't write the stream bitmap right away.
+ // Doing so would cause sparse file resize on NTFS,
+ // which would take long time on larger files.
+ return true;
+}
+
+static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+ TBlockStream * pStream;
+ ULONGLONG ByteOffset = 0;
+
+ // Create new empty stream
+ pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
+
+ // Do we have a master stream?
+ if(pStream->pMaster != NULL)
+ {
+ if(!FlatStream_CreateMirror(pStream))
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return NULL;
+ }
+ }
+ else
+ {
+ // Attempt to open the base stream
+ if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
+ {
+ FileStream_Close(pStream);
+ return NULL;
+ }
+
+ // Load the bitmap, if required to
+ if(dwStreamFlags & STREAM_FLAG_USE_BITMAP)
+ FlatStream_LoadBitmap(pStream);
+ }
+
+ // If we have a stream bitmap, set the reading functions
+ // which check presence of each file block
+ if(pStream->FileBitmap != NULL)
+ {
+ // Set the stream position to zero. Stream size is already set
+ assert(pStream->StreamSize != 0);
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+
+ // Supply the stream functions
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamClose = (STREAM_CLOSE)FlatStream_Close;
+
+ // Supply the block functions
+ pStream->BlockCheck = (BLOCK_CHECK)FlatStream_BlockCheck;
+ pStream->BlockRead = (BLOCK_READ)FlatStream_BlockRead;
+ }
+ else
+ {
+ // Reset the base position to zero
+ pStream->BaseRead(pStream, &ByteOffset, NULL, 0);
+
+ // Setup stream size and position
+ pStream->StreamSize = pStream->Base.File.FileSize;
+ pStream->StreamPos = 0;
+
+ // Set the base functions
+ pStream->StreamRead = pStream->BaseRead;
+ pStream->StreamWrite = pStream->BaseWrite;
+ pStream->StreamResize = pStream->BaseResize;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamClose = pStream->BaseClose;
+ }
+
+ return pStream;
+}
+
+//-----------------------------------------------------------------------------
+// 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 DWORD PartStream_CheckFile(TBlockStream * pStream)
+{
+ PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap;
+ DWORD dwBlockCount;
+
+ // Get the number of blocks
+ dwBlockCount = (DWORD)((pStream->StreamSize + pStream->BlockSize - 1) / pStream->BlockSize);
+
+ // Check all blocks
+ for(DWORD i = 0; i < dwBlockCount; i++, FileBitmap++)
+ {
+ // Few sanity checks
+ assert(FileBitmap->LargeValueHi == 0);
+ assert(FileBitmap->LargeValueLo == 0);
+ assert(FileBitmap->Flags == 0 || FileBitmap->Flags == 3);
+
+ // Check if this block is present
+ if(FileBitmap->Flags != 3)
+ return 0;
+ }
+
+ // Yes, the file is complete
+ return 1;
+}
+
+static bool PartStream_LoadBitmap(TBlockStream * pStream)
+{
+ PPART_FILE_MAP_ENTRY FileBitmap;
+ PART_FILE_HEADER PartHdr;
+ ULONGLONG ByteOffset = 0;
+ ULONGLONG StreamSize = 0;
+ DWORD BlockCount;
+ DWORD BitmapSize;
+
+ // Only if the size is greater than size of the bitmap header
+ if(pStream->Base.File.FileSize > sizeof(PART_FILE_HEADER))
+ {
+ // 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_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER));
+
+ // Verify the PART file header
+ if(IsPartHeader(&PartHdr))
+ {
+ // Get the number of blocks and size of one block
+ StreamSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo);
+ ByteOffset = sizeof(PART_FILE_HEADER);
+ BlockCount = (DWORD)((StreamSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize);
+ BitmapSize = BlockCount * sizeof(PART_FILE_MAP_ENTRY);
+
+ // Check if sizes match
+ if((ByteOffset + BitmapSize) < pStream->Base.File.FileSize)
+ {
+ // Allocate space for the array of PART_FILE_MAP_ENTRY
+ FileBitmap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount);
+ if(FileBitmap != NULL)
+ {
+ // Load the block map
+ if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize))
+ {
+ STORM_FREE(FileBitmap);
+ return false;
+ }
+
+ // Make sure that the byte order is correct
+ BSWAP_ARRAY32_UNSIGNED(FileBitmap, BitmapSize);
+
+ // Update the stream size
+ pStream->BuildNumber = StringToInt(PartHdr.GameBuildNumber);
+ pStream->StreamSize = StreamSize;
+
+ // Fill the bitmap information
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = BitmapSize;
+ pStream->BlockSize = PartHdr.BlockSize;
+ pStream->BlockCount = BlockCount;
+ pStream->IsComplete = PartStream_CheckFile(pStream);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static void PartStream_UpdateBitmap(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ ULONGLONG RealOffset)
+{
+ PPART_FILE_MAP_ENTRY FileBitmap;
+ DWORD BlockSize = pStream->BlockSize;
+
+ // Sanity checks
+ assert((StartOffset & (BlockSize - 1)) == 0);
+ assert(pStream->FileBitmap != NULL);
+
+ // Calculate the first entry in the block map
+ FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (StartOffset / BlockSize);
+
+ // Set all bits for the specified range
+ while(StartOffset < EndOffset)
+ {
+ // Set the bit
+ FileBitmap->BlockOffsHi = (DWORD)(RealOffset >> 0x20);
+ FileBitmap->BlockOffsLo = (DWORD)(RealOffset & 0xFFFFFFFF);
+ FileBitmap->Flags = 3;
+
+ // Move all
+ StartOffset += BlockSize;
+ RealOffset += BlockSize;
+ FileBitmap++;
+ }
+
+ // Increment the bitmap update count
+ pStream->IsModified = 1;
+}
+
+static bool PartStream_BlockCheck(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG BlockOffset)
+{
+ PPART_FILE_MAP_ENTRY FileBitmap;
+
+ // Sanity checks
+ assert((BlockOffset & (pStream->BlockSize - 1)) == 0);
+ assert(pStream->FileBitmap != NULL);
+
+ // Calculate the block map entry
+ FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (BlockOffset / pStream->BlockSize);
+
+ // Check if the flags are present
+ return (FileBitmap->Flags & 0x03) ? true : false;
+}
+
+static bool PartStream_BlockRead(
+ TBlockStream * pStream,
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
+{
+ PPART_FILE_MAP_ENTRY FileBitmap;
+ ULONGLONG ByteOffset;
+ DWORD BytesToRead;
+ DWORD BlockIndex = (DWORD)(StartOffset / pStream->BlockSize);
+
+ // The starting offset must be aligned to size of the block
+ assert(pStream->FileBitmap != NULL);
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+
+ // If the blocks are not available, we need to load them from the master
+ // and then save to the mirror
+ if(bAvailable == false)
+ {
+ // If we have no master, we cannot satisfy read request
+ if(pStream->pMaster == NULL)
+ return false;
+
+ // Load the blocks from the master stream
+ // Note that we always have to read complete blocks
+ // so they get properly stored to the mirror stream
+ BytesToRead = (DWORD)(EndOffset - StartOffset);
+ if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead))
+ return false;
+
+ // The loaded blocks are going to be stored to the end of the file
+ // Note that this operation is not required to succeed
+ if(pStream->BaseGetSize(pStream, &ByteOffset))
+ {
+ // Store the loaded blocks to the mirror file.
+ if(pStream->BaseWrite(pStream, &ByteOffset, BlockBuffer, BytesToRead))
+ {
+ PartStream_UpdateBitmap(pStream, StartOffset, EndOffset, ByteOffset);
+ }
+ }
+ }
+ else
+ {
+ // Get the file map entry
+ FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex;
+
+ // Read all blocks
+ while(StartOffset < EndOffset)
+ {
+ // Get the number of bytes to be read
+ BytesToRead = (DWORD)(EndOffset - StartOffset);
+ if(BytesToRead > pStream->BlockSize)
+ BytesToRead = pStream->BlockSize;
+ if(BytesToRead > BytesNeeded)
+ BytesToRead = BytesNeeded;
+
+ // Read the block
+ ByteOffset = MAKE_OFFSET64(FileBitmap->BlockOffsHi, FileBitmap->BlockOffsLo);
+ if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead))
+ return false;
+
+ // Move the pointers
+ StartOffset += pStream->BlockSize;
+ BlockBuffer += pStream->BlockSize;
+ BytesNeeded -= pStream->BlockSize;
+ FileBitmap++;
+ }
+ }
+
+ return true;
+}
+
+static void PartStream_Close(TBlockStream * pStream)
+{
+ PART_FILE_HEADER PartHeader;
+ ULONGLONG ByteOffset = 0;
+
+ if(pStream->FileBitmap && pStream->IsModified)
+ {
+ // Prepare the part file header
+ memset(&PartHeader, 0, sizeof(PART_FILE_HEADER));
+ PartHeader.PartialVersion = 2;
+ PartHeader.FileSizeHi = (DWORD)(pStream->StreamSize >> 0x20);
+ PartHeader.FileSizeLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF);
+ PartHeader.BlockSize = pStream->BlockSize;
+
+ // Make sure that the header is properly BSWAPed
+ BSWAP_ARRAY32_UNSIGNED(&PartHeader, sizeof(PART_FILE_HEADER));
+ sprintf(PartHeader.GameBuildNumber, "%u", (unsigned int)pStream->BuildNumber);
+
+ // Write the part header
+ pStream->BaseWrite(pStream, &ByteOffset, &PartHeader, sizeof(PART_FILE_HEADER));
+
+ // Write the block bitmap
+ BSWAP_ARRAY32_UNSIGNED(pStream->FileBitmap, pStream->BitmapSize);
+ pStream->BaseWrite(pStream, NULL, pStream->FileBitmap, pStream->BitmapSize);
+ }
+
+ // Close the base class
+ BlockStream_Close(pStream);
+}
+
+static bool PartStream_CreateMirror(TBlockStream * pStream)
+{
+ ULONGLONG RemainingSize;
+ ULONGLONG MasterSize = 0;
+ ULONGLONG MirrorSize = 0;
+ LPBYTE FileBitmap = NULL;
+ DWORD dwBitmapSize;
+ DWORD dwBlockCount;
+ bool bNeedCreateMirrorStream = true;
+ bool bNeedResizeMirrorStream = true;
+
+ // Do we have master function and base creation function?
+ if(pStream->pMaster == NULL || pStream->BaseCreate == NULL)
+ return false;
+
+ // Retrieve the master file size, block count and bitmap size
+ FileStream_GetSize(pStream->pMaster, &MasterSize);
+ dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
+ dwBitmapSize = (DWORD)(dwBlockCount * sizeof(PART_FILE_MAP_ENTRY));
+
+ // Setup stream size and position
+ pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really???
+ pStream->StreamSize = MasterSize;
+ pStream->StreamPos = 0;
+
+ // Open the base stream for write access
+ if(pStream->BaseOpen(pStream, pStream->szFileName, 0))
+ {
+ // If the file open succeeded, check if the file size matches required size
+ pStream->BaseGetSize(pStream, &MirrorSize);
+ if(MirrorSize >= sizeof(PART_FILE_HEADER) + dwBitmapSize)
+ {
+ // Check if the remaining size is aligned to block
+ RemainingSize = MirrorSize - sizeof(PART_FILE_HEADER) - dwBitmapSize;
+ if((RemainingSize & (DEFAULT_BLOCK_SIZE - 1)) == 0 || RemainingSize == MasterSize)
+ {
+ // Attempt to load an existing file bitmap
+ if(PartStream_LoadBitmap(pStream))
+ return true;
+ }
+ }
+
+ // We need to create mirror stream
+ bNeedCreateMirrorStream = false;
+ }
+
+ // Create a new stream, if needed
+ if(bNeedCreateMirrorStream)
+ {
+ if(!pStream->BaseCreate(pStream))
+ return false;
+ }
+
+ // If we need to, then resize the mirror stream
+ if(bNeedResizeMirrorStream)
+ {
+ if(!pStream->BaseResize(pStream, sizeof(PART_FILE_HEADER) + dwBitmapSize))
+ return false;
+ }
+
+ // Allocate the bitmap array
+ FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize);
+ if(FileBitmap == NULL)
+ return false;
+
+ // Initialize the bitmap
+ memset(FileBitmap, 0, dwBitmapSize);
+ pStream->FileBitmap = FileBitmap;
+ pStream->BitmapSize = dwBitmapSize;
+ pStream->BlockSize = DEFAULT_BLOCK_SIZE;
+ pStream->BlockCount = dwBlockCount;
+ pStream->IsComplete = 0;
+ pStream->IsModified = 1;
+
+ // Note: Don't write the stream bitmap right away.
+ // Doing so would cause sparse file resize on NTFS,
+ // which would take long time on larger files.
+ return true;
+}
+
+
+static TFileStream * PartStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+ TBlockStream * pStream;
+
+ // Create new empty stream
+ pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
+
+ // Do we have a master stream?
+ if(pStream->pMaster != NULL)
+ {
+ if(!PartStream_CreateMirror(pStream))
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return NULL;
+ }
+ }
+ else
+ {
+ // Attempt to open the base stream
+ if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
+ {
+ FileStream_Close(pStream);
+ return NULL;
+ }
+
+ // Load the part stream block map
+ if(!PartStream_LoadBitmap(pStream))
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_BAD_FORMAT);
+ return NULL;
+ }
+ }
+
+ // Set the stream position to zero. Stream size is already set
+ assert(pStream->StreamSize != 0);
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+
+ // Set new function pointers
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamClose = (STREAM_CLOSE)PartStream_Close;
+
+ // Supply the block functions
+ pStream->BlockCheck = (BLOCK_CHECK)PartStream_BlockCheck;
+ pStream->BlockRead = (BLOCK_READ)PartStream_BlockRead;
+ return pStream;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - MPQE stream support
+
+static const char * szKeyTemplate = "expand 32-byte k000000000000000000000000000000000000000000000000";
+
+static const char * AuthCodeArray[] =
+{
+ // Starcraft II (Heart of the Swarm)
+ // Authentication code URL: http://dist.blizzard.com/mediakey/hots-authenticationcode-bgdl.txt
+ // -0C- -1C--08- -18--04- -14--00- -10-
+ "S48B6CDTN5XEQAKQDJNDLJBJ73FDFM3U", // SC2 Heart of the Swarm-all : "expand 32-byte kQAKQ0000FM3UN5XE000073FD6CDT0000LJBJS48B0000DJND"
+
+ // Diablo III: Agent.exe (1.0.0.954)
+ // Address of decryption routine: 00502b00
+ // Pointer to decryptor object: ECX
+ // Pointer to key: ECX+0x5C
+ // Authentication code URL: http://dist.blizzard.com/mediakey/d3-authenticationcode-enGB.txt
+ // -0C- -1C--08- -18--04- -14--00- -10-
+ "UCMXF6EJY352EFH4XFRXCFH2XC9MQRZK", // Diablo III Installer (deDE): "expand 32-byte kEFH40000QRZKY3520000XC9MF6EJ0000CFH2UCMX0000XFRX"
+ "MMKVHY48RP7WXP4GHYBQ7SL9J9UNPHBP", // Diablo III Installer (enGB): "expand 32-byte kXP4G0000PHBPRP7W0000J9UNHY4800007SL9MMKV0000HYBQ"
+ "8MXLWHQ7VGGLTZ9MQZQSFDCLJYET3CPP", // Diablo III Installer (enSG): "expand 32-byte kTZ9M00003CPPVGGL0000JYETWHQ70000FDCL8MXL0000QZQS"
+ "EJ2R5TM6XFE2GUNG5QDGHKQ9UAKPWZSZ", // Diablo III Installer (enUS): "expand 32-byte kGUNG0000WZSZXFE20000UAKP5TM60000HKQ9EJ2R00005QDG"
+ "PBGFBE42Z6LNK65UGJQ3WZVMCLP4HQQT", // Diablo III Installer (esES): "expand 32-byte kK65U0000HQQTZ6LN0000CLP4BE420000WZVMPBGF0000GJQ3"
+ "X7SEJJS9TSGCW5P28EBSC47AJPEY8VU2", // Diablo III Installer (esMX): "expand 32-byte kW5P200008VU2TSGC0000JPEYJJS90000C47AX7SE00008EBS"
+ "5KVBQA8VYE6XRY3DLGC5ZDE4XS4P7YA2", // Diablo III Installer (frFR): "expand 32-byte kRY3D00007YA2YE6X0000XS4PQA8V0000ZDE45KVB0000LGC5"
+ "478JD2K56EVNVVY4XX8TDWYT5B8KB254", // Diablo III Installer (itIT): "expand 32-byte kVVY40000B2546EVN00005B8KD2K50000DWYT478J0000XX8T"
+ "8TS4VNFQRZTN6YWHE9CHVDH9NVWD474A", // Diablo III Installer (koKR): "expand 32-byte k6YWH0000474ARZTN0000NVWDVNFQ0000VDH98TS40000E9CH"
+ "LJ52Z32DF4LZ4ZJJXVKK3AZQA6GABLJB", // Diablo III Installer (plPL): "expand 32-byte k4ZJJ0000BLJBF4LZ0000A6GAZ32D00003AZQLJ520000XVKK"
+ "K6BDHY2ECUE2545YKNLBJPVYWHE7XYAG", // Diablo III Installer (ptBR): "expand 32-byte k545Y0000XYAGCUE20000WHE7HY2E0000JPVYK6BD0000KNLB"
+ "NDVW8GWLAYCRPGRNY8RT7ZZUQU63VLPR", // Diablo III Installer (ruRU): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "6VWCQTN8V3ZZMRUCZXV8A8CGUX2TAA8H", // Diablo III Installer (zhTW): "expand 32-byte kMRUC0000AA8HV3ZZ0000UX2TQTN80000A8CG6VWC0000ZXV8"
+// "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Diablo III Installer (zhCN): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+
+ // Starcraft II (Wings of Liberty): Installer.exe (4.1.1.4219)
+ // Address of decryption routine: 0053A3D0
+ // Pointer to decryptor object: ECX
+ // Pointer to key: ECX+0x5C
+ // Authentication code URL: http://dist.blizzard.com/mediakey/sc2-authenticationcode-enUS.txt
+ // -0C- -1C--08- -18--04- -14--00- -10-
+ "Y45MD3CAK4KXSSXHYD9VY64Z8EKJ4XFX", // SC2 Wings of Liberty (deDE): "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V"
+ "G8MN8UDG6NA2ANGY6A3DNY82HRGF29ZH", // SC2 Wings of Liberty (enGB): "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D"
+ "W9RRHLB2FDU9WW5B3ECEBLRSFWZSF7HW", // SC2 Wings of Liberty (enSG): "expand 32-byte kWW5B0000F7HWFDU90000FWZSHLB20000BLRSW9RR00003ECE"
+ "3DH5RE5NVM5GTFD85LXGWT6FK859ETR5", // SC2 Wings of Liberty (enUS): "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG"
+ "8WLKUAXE94PFQU4Y249PAZ24N4R4XKTQ", // SC2 Wings of Liberty (esES): "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P"
+ "A34DXX3VHGGXSQBRFE5UFFDXMF9G4G54", // SC2 Wings of Liberty (esMX): "expand 32-byte kSQBR00004G54HGGX0000MF9GXX3V0000FFDXA34D0000FE5U"
+ "ZG7J9K938HJEFWPQUA768MA2PFER6EAJ", // SC2 Wings of Liberty (frFR): "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76"
+ "NE7CUNNNTVAPXV7E3G2BSVBWGVMW8BL2", // SC2 Wings of Liberty (itIT): "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B"
+ "3V9E2FTMBM9QQWK7U6MAMWAZWQDB838F", // SC2 Wings of Liberty (koKR): "expand 32-byte kQWK70000838FBM9Q0000WQDB2FTM0000MWAZ3V9E0000U6MA"
+ "2NSFB8MELULJ83U6YHA3UP6K4MQD48L6", // SC2 Wings of Liberty (plPL): "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3"
+ "QA2TZ9EWZ4CUU8BMB5WXCTY65F9CSW4E", // SC2 Wings of Liberty (ptBR): "expand 32-byte kU8BM0000SW4EZ4CU00005F9CZ9EW0000CTY6QA2T0000B5WX"
+ "VHB378W64BAT9SH7D68VV9NLQDK9YEGT", // SC2 Wings of Liberty (ruRU): "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V"
+ "U3NFQJV4M6GC7KBN9XQJ3BRDN3PLD9NE", // SC2 Wings of Liberty (zhTW): "expand 32-byte k7KBN0000D9NEM6GC0000N3PLQJV400003BRDU3NF00009XQJ"
+
+ NULL
+};
+
+static DWORD Rol32(DWORD dwValue, DWORD dwRolCount)
+{
+ DWORD dwShiftRight = 32 - dwRolCount;
+
+ return (dwValue << dwRolCount) | (dwValue >> dwShiftRight);
+}
+
+static void CreateKeyFromAuthCode(
+ LPBYTE pbKeyBuffer,
+ const char * szAuthCode)
+{
+ LPDWORD KeyPosition = (LPDWORD)(pbKeyBuffer + 0x10);
+ LPDWORD AuthCode32 = (LPDWORD)szAuthCode;
+
+ memcpy(pbKeyBuffer, szKeyTemplate, MPQE_CHUNK_SIZE);
+ KeyPosition[0x00] = AuthCode32[0x03];
+ KeyPosition[0x02] = AuthCode32[0x07];
+ KeyPosition[0x03] = AuthCode32[0x02];
+ KeyPosition[0x05] = AuthCode32[0x06];
+ KeyPosition[0x06] = AuthCode32[0x01];
+ KeyPosition[0x08] = AuthCode32[0x05];
+ KeyPosition[0x09] = AuthCode32[0x00];
+ KeyPosition[0x0B] = AuthCode32[0x04];
+ BSWAP_ARRAY32_UNSIGNED(pbKeyBuffer, MPQE_CHUNK_SIZE);
+}
+
+static void DecryptFileChunk(
+ DWORD * MpqData,
+ LPBYTE pbKey,
+ ULONGLONG ByteOffset,
+ DWORD dwLength)
+{
+ ULONGLONG ChunkOffset;
+ DWORD KeyShuffled[0x10];
+ DWORD KeyMirror[0x10];
+ DWORD RoundCount = 0x14;
+
+ // Prepare the key
+ ChunkOffset = ByteOffset / MPQE_CHUNK_SIZE;
+ memcpy(KeyMirror, pbKey, MPQE_CHUNK_SIZE);
+ BSWAP_ARRAY32_UNSIGNED(KeyMirror, MPQE_CHUNK_SIZE);
+ KeyMirror[0x05] = (DWORD)(ChunkOffset >> 32);
+ KeyMirror[0x08] = (DWORD)(ChunkOffset);
+
+ while(dwLength >= MPQE_CHUNK_SIZE)
+ {
+ // Shuffle the key - part 1
+ KeyShuffled[0x0E] = KeyMirror[0x00];
+ KeyShuffled[0x0C] = KeyMirror[0x01];
+ KeyShuffled[0x05] = KeyMirror[0x02];
+ KeyShuffled[0x0F] = KeyMirror[0x03];
+ KeyShuffled[0x0A] = KeyMirror[0x04];
+ KeyShuffled[0x07] = KeyMirror[0x05];
+ KeyShuffled[0x0B] = KeyMirror[0x06];
+ KeyShuffled[0x09] = KeyMirror[0x07];
+ KeyShuffled[0x03] = KeyMirror[0x08];
+ KeyShuffled[0x06] = KeyMirror[0x09];
+ KeyShuffled[0x08] = KeyMirror[0x0A];
+ KeyShuffled[0x0D] = KeyMirror[0x0B];
+ KeyShuffled[0x02] = KeyMirror[0x0C];
+ KeyShuffled[0x04] = KeyMirror[0x0D];
+ KeyShuffled[0x01] = KeyMirror[0x0E];
+ KeyShuffled[0x00] = KeyMirror[0x0F];
+
+ // Shuffle the key - part 2
+ for(DWORD i = 0; i < RoundCount; i += 2)
+ {
+ KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x02]), 0x07);
+ KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0E]), 0x09);
+ KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x0A]), 0x0D);
+ KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x03]), 0x12);
+
+ KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x04]), 0x07);
+ KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x0C]), 0x09);
+ KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x07]), 0x0D);
+ KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x06]), 0x12);
+
+ KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x01]), 0x07);
+ KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x05]), 0x09);
+ KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x0B]), 0x0D);
+ KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x08]), 0x12);
+
+ KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x00]), 0x07);
+ KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x0F]), 0x09);
+ KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x09]), 0x0D);
+ KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x0D]), 0x12);
+
+ KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x09]), 0x07);
+ KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x0E]), 0x09);
+ KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x04]), 0x0D);
+ KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x08]), 0x12);
+
+ KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x0A]), 0x07);
+ KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x0C]), 0x09);
+ KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x01]), 0x0D);
+ KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0D]), 0x12);
+
+ KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x07]), 0x07);
+ KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x05]), 0x09);
+ KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x00]), 0x0D);
+ KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x03]), 0x12);
+
+ KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x0B]), 0x07);
+ KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x0F]), 0x09);
+ KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x02]), 0x0D);
+ KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x06]), 0x12);
+ }
+
+ // Decrypt one data chunk
+ BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE);
+ MpqData[0x00] = MpqData[0x00] ^ (KeyShuffled[0x0E] + KeyMirror[0x00]);
+ MpqData[0x01] = MpqData[0x01] ^ (KeyShuffled[0x04] + KeyMirror[0x0D]);
+ MpqData[0x02] = MpqData[0x02] ^ (KeyShuffled[0x08] + KeyMirror[0x0A]);
+ MpqData[0x03] = MpqData[0x03] ^ (KeyShuffled[0x09] + KeyMirror[0x07]);
+ MpqData[0x04] = MpqData[0x04] ^ (KeyShuffled[0x0A] + KeyMirror[0x04]);
+ MpqData[0x05] = MpqData[0x05] ^ (KeyShuffled[0x0C] + KeyMirror[0x01]);
+ MpqData[0x06] = MpqData[0x06] ^ (KeyShuffled[0x01] + KeyMirror[0x0E]);
+ MpqData[0x07] = MpqData[0x07] ^ (KeyShuffled[0x0D] + KeyMirror[0x0B]);
+ MpqData[0x08] = MpqData[0x08] ^ (KeyShuffled[0x03] + KeyMirror[0x08]);
+ MpqData[0x09] = MpqData[0x09] ^ (KeyShuffled[0x07] + KeyMirror[0x05]);
+ MpqData[0x0A] = MpqData[0x0A] ^ (KeyShuffled[0x05] + KeyMirror[0x02]);
+ MpqData[0x0B] = MpqData[0x0B] ^ (KeyShuffled[0x00] + KeyMirror[0x0F]);
+ MpqData[0x0C] = MpqData[0x0C] ^ (KeyShuffled[0x02] + KeyMirror[0x0C]);
+ MpqData[0x0D] = MpqData[0x0D] ^ (KeyShuffled[0x06] + KeyMirror[0x09]);
+ MpqData[0x0E] = MpqData[0x0E] ^ (KeyShuffled[0x0B] + KeyMirror[0x06]);
+ MpqData[0x0F] = MpqData[0x0F] ^ (KeyShuffled[0x0F] + KeyMirror[0x03]);
+ BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE);
+
+ // Update byte offset in the key
+ KeyMirror[0x08]++;
+ if(KeyMirror[0x08] == 0)
+ KeyMirror[0x05]++;
+
+ // Move pointers and decrease number of bytes to decrypt
+ MpqData += (MPQE_CHUNK_SIZE / sizeof(DWORD));
+ dwLength -= MPQE_CHUNK_SIZE;
+ }
+}
+
+static bool MpqeStream_DetectFileKey(TEncryptedStream * pStream)
+{
+ ULONGLONG ByteOffset = 0;
+ BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
+ BYTE FileHeader[MPQE_CHUNK_SIZE];
+
+ // Read the first file chunk
+ if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader)))
+ {
+ // We just try all known keys one by one
+ for(int i = 0; AuthCodeArray[i] != NULL; i++)
+ {
+ // Prepare they decryption key from game serial number
+ CreateKeyFromAuthCode(pStream->Key, AuthCodeArray[i]);
+
+ // Try to decrypt with the given key
+ memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE);
+ DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE);
+
+ // 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')
+ {
+ // Update the stream size
+ pStream->StreamSize = pStream->Base.File.FileSize;
+
+ // Fill the block information
+ pStream->BlockSize = MPQE_CHUNK_SIZE;
+ pStream->BlockCount = (DWORD)(pStream->Base.File.FileSize + MPQE_CHUNK_SIZE - 1) / MPQE_CHUNK_SIZE;
+ pStream->IsComplete = 1;
+ return true;
+ }
+ }
+ }
+
+ // Key not found, sorry
+ return false;
+}
+
+static bool MpqeStream_BlockRead(
+ TEncryptedStream * pStream,
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
+{
+ DWORD dwBytesToRead;
+
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+ assert(bAvailable != false);
+ BytesNeeded = BytesNeeded;
+ bAvailable = bAvailable;
+
+ // Read the file from the stream as-is
+ // Limit the reading to number of blocks really needed
+ dwBytesToRead = (DWORD)(EndOffset - StartOffset);
+ if(!pStream->BaseRead(pStream, &StartOffset, BlockBuffer, dwBytesToRead))
+ return false;
+
+ // Decrypt the data
+ dwBytesToRead = (dwBytesToRead + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1);
+ DecryptFileChunk((LPDWORD)BlockBuffer, pStream->Key, StartOffset, dwBytesToRead);
+ return true;
+}
+
+static TFileStream * MpqeStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+ TEncryptedStream * pStream;
+
+ // Create new empty stream
+ pStream = (TEncryptedStream *)AllocateFileStream(szFileName, sizeof(TEncryptedStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
+
+ // Attempt to open the base stream
+ assert(pStream->BaseOpen != NULL);
+ if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
+ return NULL;
+
+ // Determine the encryption key for the MPQ
+ if(MpqeStream_DetectFileKey(pStream))
+ {
+ // Set the stream position and size
+ assert(pStream->StreamSize != 0);
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+
+ // Set new function pointers
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamClose = pStream->BaseClose;
+
+ // Supply the block functions
+ pStream->BlockRead = (BLOCK_READ)MpqeStream_BlockRead;
+ return pStream;
+ }
+
+ // Cleanup the stream and return
+ FileStream_Close(pStream);
+ SetLastError(ERROR_UNKNOWN_FILE_KEY);
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions - Block4 stream support
+
+#define BLOCK4_BLOCK_SIZE 0x4000 // Size of one block
+#define BLOCK4_HASH_SIZE 0x20 // Size of MD5 hash that is after each block
+#define BLOCK4_MAX_BLOCKS 0x00002000 // Maximum amount of blocks per file
+#define BLOCK4_MAX_FSIZE 0x08040000 // Max size of one file
+
+static bool Block4Stream_BlockRead(
+ TBlockStream * pStream, // Pointer to an open stream
+ ULONGLONG StartOffset,
+ ULONGLONG EndOffset,
+ LPBYTE BlockBuffer,
+ DWORD BytesNeeded,
+ bool bAvailable)
+{
+ TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap;
+ ULONGLONG ByteOffset;
+ DWORD BytesToRead;
+ DWORD StreamIndex;
+ DWORD BlockIndex;
+ bool bResult;
+
+ // The starting offset must be aligned to size of the block
+ assert(pStream->FileBitmap != NULL);
+ assert((StartOffset & (pStream->BlockSize - 1)) == 0);
+ assert(StartOffset < EndOffset);
+ assert(bAvailable == true);
+
+ // Keep compiler happy
+ bAvailable = bAvailable;
+ EndOffset = EndOffset;
+
+ while(BytesNeeded != 0)
+ {
+ // Calculate the block index and the file index
+ StreamIndex = (DWORD)((StartOffset / pStream->BlockSize) / BLOCK4_MAX_BLOCKS);
+ BlockIndex = (DWORD)((StartOffset / pStream->BlockSize) % BLOCK4_MAX_BLOCKS);
+ if(StreamIndex > pStream->BitmapSize)
+ return false;
+
+ // Calculate the block offset
+ ByteOffset = ((ULONGLONG)BlockIndex * (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE));
+ BytesToRead = STORMLIB_MIN(BytesNeeded, BLOCK4_BLOCK_SIZE);
+
+ // Read from the base stream
+ pStream->Base = BaseArray[StreamIndex];
+ bResult = pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead);
+ BaseArray[StreamIndex] = pStream->Base;
+
+ // Did the result succeed?
+ if(bResult == false)
+ return false;
+
+ // Move pointers
+ StartOffset += BytesToRead;
+ BlockBuffer += BytesToRead;
+ BytesNeeded -= BytesToRead;
+ }
+
+ return true;
+}
+
+
+static void Block4Stream_Close(TBlockStream * pStream)
+{
+ TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap;
+
+ // If we have a non-zero count of base streams,
+ // we have to close them all
+ if(BaseArray != NULL)
+ {
+ // Close all base streams
+ for(DWORD i = 0; i < pStream->BitmapSize; i++)
+ {
+ memcpy(&pStream->Base, BaseArray + i, sizeof(TBaseProviderData));
+ pStream->BaseClose(pStream);
+ }
+ }
+
+ // Free the data map, if any
+ if(pStream->FileBitmap != NULL)
+ STORM_FREE(pStream->FileBitmap);
+ pStream->FileBitmap = NULL;
+
+ // Do not call the BaseClose function,
+ // we closed all handles already
+ return;
+}
+
+static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
+{
+ TBaseProviderData * NewBaseArray = NULL;
+ ULONGLONG RemainderBlock;
+ ULONGLONG BlockCount;
+ ULONGLONG FileSize;
+ TBlockStream * pStream;
+ TCHAR * szNameBuff;
+ size_t nNameLength;
+ DWORD dwBaseFiles = 0;
+ DWORD dwBaseFlags;
+
+ // Create new empty stream
+ pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream == NULL)
+ return NULL;
+
+ // Sanity check
+ assert(pStream->BaseOpen != NULL);
+
+ // Get the length of the file name without numeric suffix
+ nNameLength = _tcslen(pStream->szFileName);
+ if(pStream->szFileName[nNameLength - 2] == '.' && pStream->szFileName[nNameLength - 1] == '0')
+ nNameLength -= 2;
+ pStream->szFileName[nNameLength] = 0;
+
+ // Supply the stream functions
+ pStream->StreamRead = (STREAM_READ)BlockStream_Read;
+ pStream->StreamGetSize = BlockStream_GetSize;
+ pStream->StreamGetPos = BlockStream_GetPos;
+ pStream->StreamClose = (STREAM_CLOSE)Block4Stream_Close;
+ pStream->BlockRead = (BLOCK_READ)Block4Stream_BlockRead;
+
+ // Allocate work space for numeric names
+ szNameBuff = STORM_ALLOC(TCHAR, nNameLength + 4);
+ if(szNameBuff != NULL)
+ {
+ // Set the base flags
+ dwBaseFlags = (dwStreamFlags & STREAM_PROVIDERS_MASK) | STREAM_FLAG_READ_ONLY;
+
+ // Go all suffixes from 0 to 30
+ for(int nSuffix = 0; nSuffix < 30; nSuffix++)
+ {
+ // Open the n-th file
+ _stprintf(szNameBuff, _T("%s.%u"), pStream->szFileName, nSuffix);
+ if(!pStream->BaseOpen(pStream, szNameBuff, dwBaseFlags))
+ break;
+
+ // If the open succeeded, we re-allocate the base provider array
+ NewBaseArray = STORM_ALLOC(TBaseProviderData, dwBaseFiles + 1);
+ if(NewBaseArray == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ // Copy the old base data array to the new base data array
+ if(pStream->FileBitmap != NULL)
+ {
+ memcpy(NewBaseArray, pStream->FileBitmap, sizeof(TBaseProviderData) * dwBaseFiles);
+ STORM_FREE(pStream->FileBitmap);
+ }
+
+ // Also copy the opened base array
+ memcpy(NewBaseArray + dwBaseFiles, &pStream->Base, sizeof(TBaseProviderData));
+ pStream->FileBitmap = NewBaseArray;
+ dwBaseFiles++;
+
+ // Get the size of the base stream
+ pStream->BaseGetSize(pStream, &FileSize);
+ assert(FileSize <= BLOCK4_MAX_FSIZE);
+ RemainderBlock = FileSize % (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE);
+ BlockCount = FileSize / (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE);
+
+ // Increment the stream size and number of blocks
+ pStream->StreamSize += (BlockCount * BLOCK4_BLOCK_SIZE);
+ pStream->BlockCount += (DWORD)BlockCount;
+
+ // Is this the last file?
+ if(FileSize < BLOCK4_MAX_FSIZE)
+ {
+ if(RemainderBlock)
+ {
+ pStream->StreamSize += (RemainderBlock - BLOCK4_HASH_SIZE);
+ pStream->BlockCount++;
+ }
+ break;
+ }
+ }
+
+ // Fill the remainining block stream variables
+ pStream->BitmapSize = dwBaseFiles;
+ pStream->BlockSize = BLOCK4_BLOCK_SIZE;
+ pStream->IsComplete = 1;
+ pStream->IsModified = 0;
+
+ // Fill the remaining stream variables
+ pStream->StreamPos = 0;
+ pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
+
+ STORM_FREE(szNameBuff);
+ }
+
+ // If we opened something, return success
+ if(dwBaseFiles == 0)
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ pStream = NULL;
+ }
+
+ return pStream;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+/**
+ * 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
+ * can open the file for read, but not for write)
+ * - If the file does not exist, the function must create new one
+ * - If the file exists, the function must rewrite it and set to zero size
+ * - The parameters of the function must be validate by the caller
+ * - 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 create
+ */
+
+TFileStream * FileStream_CreateFile(
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
+{
+ TFileStream * pStream;
+
+ // We only support creation of flat, local file
+ if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return NULL;
+ }
+
+ // Allocate file stream structure for flat stream
+ pStream = AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
+ if(pStream != NULL)
+ {
+ // Attempt to create the disk file
+ if(BaseFile_Create(pStream))
+ {
+ // Fill the stream provider functions
+ pStream->StreamRead = pStream->BaseRead;
+ pStream->StreamWrite = pStream->BaseWrite;
+ pStream->StreamResize = pStream->BaseResize;
+ pStream->StreamGetSize = pStream->BaseGetSize;
+ pStream->StreamGetPos = pStream->BaseGetPos;
+ pStream->StreamClose = pStream->BaseClose;
+ return pStream;
+ }
+
+ // File create failed, delete the stream
+ STORM_FREE(pStream);
+ pStream = NULL;
+ }
+
+ // Return the stream
+ return pStream;
+}
+
+/**
+ * This function opens an existing file for read or read-write access
+ * - If the current platform supports file sharing,
+ * the file must be open for read sharing (i.e. another application
+ * can open the file for read, but not for write)
+ * - If the file does not exist, the function must return NULL
+ * - 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 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 dwStreamFlags specifies the provider and base storage type
+ */
+
+TFileStream * FileStream_OpenFile(
+ const TCHAR * szFileName,
+ DWORD dwStreamFlags)
+{
+ DWORD dwProvider = dwStreamFlags & STREAM_PROVIDERS_MASK;
+ size_t nPrefixLength = FileStream_Prefix(szFileName, &dwProvider);
+
+ // Re-assemble the stream flags
+ dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | dwProvider;
+ szFileName += nPrefixLength;
+
+ // Perform provider-specific open
+ switch(dwStreamFlags & STREAM_PROVIDER_MASK)
+ {
+ case STREAM_PROVIDER_FLAT:
+ return FlatStream_Open(szFileName, dwStreamFlags);
+
+ case STREAM_PROVIDER_PARTIAL:
+ return PartStream_Open(szFileName, dwStreamFlags);
+
+ case STREAM_PROVIDER_MPQE:
+ return MpqeStream_Open(szFileName, dwStreamFlags);
+
+ case STREAM_PROVIDER_BLOCK4:
+ return Block4Stream_Open(szFileName, dwStreamFlags);
+
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return NULL;
+ }
+}
+
+/**
+ * Returns the file name of the stream
+ *
+ * \a pStream Pointer to an open stream
+ */
+const TCHAR * FileStream_GetFileName(TFileStream * pStream)
+{
+ assert(pStream != NULL);
+ return pStream->szFileName;
+}
+
+/**
+ * Returns the length of the provider prefix. Returns zero if no prefix
+ *
+ * \a szFileName Pointer to a stream name (file, mapped file, URL)
+ * \a pdwStreamProvider Pointer to a DWORD variable that receives stream provider (STREAM_PROVIDER_XXX)
+ */
+
+size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider)
+{
+ size_t nPrefixLength1 = 0;
+ size_t nPrefixLength2 = 0;
+ DWORD dwProvider = 0;
+
+ if(szFileName != NULL)
+ {
+ //
+ // Determine the stream provider
+ //
+
+ if(!_tcsnicmp(szFileName, _T("flat-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_FLAT;
+ nPrefixLength1 = 5;
+ }
+
+ else if(!_tcsnicmp(szFileName, _T("part-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_PARTIAL;
+ nPrefixLength1 = 5;
+ }
+
+ else if(!_tcsnicmp(szFileName, _T("mpqe-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_MPQE;
+ nPrefixLength1 = 5;
+ }
+
+ else if(!_tcsnicmp(szFileName, _T("blk4-"), 5))
+ {
+ dwProvider |= STREAM_PROVIDER_BLOCK4;
+ nPrefixLength1 = 5;
+ }
+
+ //
+ // Determine the base provider
+ //
+
+ if(!_tcsnicmp(szFileName+nPrefixLength1, _T("file:"), 5))
+ {
+ dwProvider |= BASE_PROVIDER_FILE;
+ nPrefixLength2 = 5;
+ }
+
+ else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4))
+ {
+ dwProvider |= BASE_PROVIDER_MAP;
+ nPrefixLength2 = 4;
+ }
+
+ else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5))
+ {
+ dwProvider |= BASE_PROVIDER_HTTP;
+ nPrefixLength2 = 5;
+ }
+
+ // Only accept stream provider if we recognized the base provider
+ if(nPrefixLength2 != 0)
+ {
+ // It is also allowed to put "//" after the base provider, e.g. "file://", "http://"
+ if(szFileName[nPrefixLength1+nPrefixLength2] == '/' && szFileName[nPrefixLength1+nPrefixLength2+1] == '/')
+ nPrefixLength2 += 2;
+
+ if(pdwProvider != NULL)
+ *pdwProvider = dwProvider;
+ return nPrefixLength1 + nPrefixLength2;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Sets a download callback. Whenever the stream needs to download one or more blocks
+ * from the server, the callback is called
+ *
+ * \a pStream Pointer to an open stream
+ * \a pfnCallback Pointer to callback function
+ * \a pvUserData Arbitrary user pointer passed to the download callback
+ */
+
+bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData)
+{
+ TBlockStream * pBlockStream = (TBlockStream *)pStream;
+
+ if(pStream->BlockRead == NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+ }
+
+ pBlockStream->pfnCallback = pfnCallback;
+ pBlockStream->UserData = pvUserData;
+ return true;
+}
+
+/**
+ * This function gives the block map. The 'pvBitmap' pointer must point to a buffer
+ * of at least sizeof(STREAM_BLOCK_MAP) size. It can also have size of the complete
+ * block map (i.e. sizeof(STREAM_BLOCK_MAP) + BitmapSize). In that case, the function
+ * also copies the bit-based block map.
+ *
+ * \a pStream Pointer to an open stream
+ * \a pvBitmap Pointer to buffer where the block map will be stored
+ * \a cbBitmap Length of the buffer, of the block map
+ * \a cbLengthNeeded Length of the bitmap, in bytes
+ */
+
+bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, DWORD * pcbLengthNeeded)
+{
+ TStreamBitmap * pBitmap = (TStreamBitmap *)pvBitmap;
+ TBlockStream * pBlockStream = (TBlockStream *)pStream;
+ ULONGLONG BlockOffset;
+ LPBYTE Bitmap = (LPBYTE)(pBitmap + 1);
+ DWORD BitmapSize;
+ DWORD BlockCount;
+ DWORD BlockSize;
+ bool bResult = false;
+
+ // Retrieve the size of one block
+ if(pStream->BlockCheck != NULL)
+ {
+ BlockCount = pBlockStream->BlockCount;
+ BlockSize = pBlockStream->BlockSize;
+ }
+ else
+ {
+ BlockCount = (DWORD)((pStream->StreamSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
+ BlockSize = DEFAULT_BLOCK_SIZE;
+ }
+
+ // Fill-in the variables
+ BitmapSize = (BlockCount + 7) / 8;
+
+ // Give the number of blocks
+ if(pcbLengthNeeded != NULL)
+ pcbLengthNeeded[0] = sizeof(TStreamBitmap) + BitmapSize;
+
+ // If the length of the buffer is not enough
+ if(pvBitmap != NULL && cbBitmap != 0)
+ {
+ // Give the STREAM_BLOCK_MAP structure
+ if(cbBitmap >= sizeof(TStreamBitmap))
+ {
+ pBitmap->StreamSize = pStream->StreamSize;
+ pBitmap->BitmapSize = BitmapSize;
+ pBitmap->BlockCount = BlockCount;
+ pBitmap->BlockSize = BlockSize;
+ pBitmap->IsComplete = (pStream->BlockCheck != NULL) ? pBlockStream->IsComplete : 1;
+ bResult = true;
+ }
+
+ // Give the block bitmap, if enough space
+ if(cbBitmap >= sizeof(TStreamBitmap) + BitmapSize)
+ {
+ // Version with bitmap present
+ if(pStream->BlockCheck != NULL)
+ {
+ DWORD ByteIndex = 0;
+ BYTE BitMask = 0x01;
+
+ // Initialize the map with zeros
+ memset(Bitmap, 0, BitmapSize);
+
+ // Fill the map
+ for(BlockOffset = 0; BlockOffset < pStream->StreamSize; BlockOffset += BlockSize)
+ {
+ // Set the bit if the block is present
+ if(pBlockStream->BlockCheck(pStream, BlockOffset))
+ Bitmap[ByteIndex] |= BitMask;
+
+ // Move bit position
+ ByteIndex += (BitMask >> 0x07);
+ BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
+ }
+ }
+ else
+ {
+ memset(Bitmap, 0xFF, BitmapSize);
+ }
+ }
+ }
+
+ // Set last error value and return
+ if(bResult == false)
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return bResult;
+}
+
+/**
+ * Reads data from the stream
+ *
+ * - Returns true if the read operation succeeded and all bytes have been read
+ * - Returns false if either read failed or not all bytes have been read
+ * - If the pByteOffset is NULL, the function must read the data from the current file position
+ * - The function can be called with dwBytesToRead = 0. In that case, pvBuffer is ignored
+ * and the function just adjusts file pointer.
+ *
+ * \a pStream Pointer to an open stream
+ * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position
+ * \a pvBuffer Pointer to data to be read
+ * \a dwBytesToRead Number of bytes to read from the file
+ *
+ * \returns
+ * - If the function reads the required amount of bytes, it returns true.
+ * - If the function reads less than required bytes, it returns false and GetLastError() returns ERROR_HANDLE_EOF
+ * - If the function fails, it reads false and GetLastError() returns an error code different from ERROR_HANDLE_EOF
+ */
+bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead)
+{
+ assert(pStream->StreamRead != NULL);
+ return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead);
+}
+
+/**
+ * This function writes data to the stream
+ *
+ * - Returns true if the write operation succeeded and all bytes have been written
+ * - Returns false if either write failed or not all bytes have been written
+ * - If the pByteOffset is NULL, the function must write the data to the current file position
+ *
+ * \a pStream Pointer to an open stream
+ * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position
+ * \a pvBuffer Pointer to data to be written
+ * \a dwBytesToWrite Number of bytes to write to the file
+ */
+bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
+{
+ if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ assert(pStream->StreamWrite != NULL);
+ return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite);
+}
+
+/**
+ * Returns the size of a file
+ *
+ * \a pStream Pointer to an open stream
+ * \a FileSize Pointer where to store the file size
+ */
+bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
+{
+ assert(pStream->StreamGetSize != NULL);
+ return pStream->StreamGetSize(pStream, pFileSize);
+}
+
+/**
+ * Sets the size of a file
+ *
+ * \a pStream Pointer to an open stream
+ * \a NewFileSize File size to set
+ */
+bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
+{
+ if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ assert(pStream->StreamResize != NULL);
+ return pStream->StreamResize(pStream, NewFileSize);
+}
+
+/**
+ * This function returns the current file position
+ * \a pStream
+ * \a pByteOffset
+ */
+bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ assert(pStream->StreamGetPos != NULL);
+ return pStream->StreamGetPos(pStream, pByteOffset);
+}
+
+/**
+ * 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)
+{
+ // Just use the saved filetime value
+ *pFileTime = pStream->Base.File.FileTime;
+ return true;
+}
+
+/**
+ * Returns the stream flags
+ *
+ * \a pStream Pointer to an open stream
+ * \a pdwStreamFlags Pointer where to store the stream flags
+ */
+bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags)
+{
+ *pdwStreamFlags = pStream->dwFlags;
+ return true;
+}
+
+/**
+ * Switches a stream with another. Used for final phase of archive compacting.
+ * Performs these steps:
+ *
+ * 1) Closes the handle to the existing MPQ
+ * 2) Renames the temporary MPQ to the original MPQ, overwrites existing one
+ * 3) Opens the MPQ stores the handle and stream position to the new stream structure
+ *
+ * \a pStream Pointer to an open stream
+ * \a pNewStream Temporary ("working") stream (created during archive compacting)
+ */
+bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream)
+{
+ // Only supported on flat files
+ if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+ }
+
+ // Not supported on read-only streams
+ if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // Close both stream's base providers
+ pNewStream->BaseClose(pNewStream);
+ pStream->BaseClose(pStream);
+
+ // Now we have to delete the (now closed) old file and rename the new file
+ if(!BaseFile_Replace(pStream, pNewStream))
+ return false;
+
+ // Now open the base file again
+ if(!BaseFile_Open(pStream, pStream->szFileName, pStream->dwFlags))
+ return false;
+
+ // Cleanup the new stream
+ FileStream_Close(pNewStream);
+ return true;
+}
+
+/**
+ * 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
+ * can be NULL, if there was an allocation failure during the process
+ *
+ * \a pStream Pointer to an open stream
+ */
+void FileStream_Close(TFileStream * pStream)
+{
+ // Check if the stream structure is allocated at all
+ if(pStream != NULL)
+ {
+ // Free the master stream, if any
+ if(pStream->pMaster != NULL)
+ FileStream_Close(pStream->pMaster);
+ pStream->pMaster = NULL;
+
+ // Close the stream provider.
+ if(pStream->StreamClose != NULL)
+ pStream->StreamClose(pStream);
+
+ // Also close base stream, if any
+ else if(pStream->BaseClose != NULL)
+ pStream->BaseClose(pStream);
+
+ // Free the stream itself
+ STORM_FREE(pStream);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Utility functions (ANSI)
+
+const char * GetPlainFileName(const char * szFileName)
+{
+ const char * szPlainName = szFileName;
+
+ while(*szFileName != 0)
+ {
+ if(*szFileName == '\\' || *szFileName == '/')
+ szPlainName = szFileName + 1;
+ szFileName++;
+ }
+
+ return szPlainName;
+}
+
+void CopyFileName(char * szTarget, const char * szSource, size_t cchLength)
+{
+ memcpy(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Utility functions (UNICODE) only exist in the ANSI version of the library
+// In ANSI builds, TCHAR = char, so we don't need these functions implemented
+
+#ifdef _UNICODE
+const TCHAR * GetPlainFileName(const TCHAR * szFileName)
+{
+ const TCHAR * szPlainName = szFileName;
+
+ while(*szFileName != 0)
+ {
+ if(*szFileName == '\\' || *szFileName == '/')
+ szPlainName = szFileName + 1;
+ szFileName++;
+ }
+
+ return szPlainName;
+}
+
+void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength)
+{
+ mbstowcs(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
+
+void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength)
+{
+ wcstombs(szTarget, szSource, cchLength);
+ szTarget[cchLength] = 0;
+}
+#endif
diff --git a/src/SBaseCommon.cpp b/src/SBaseCommon.cpp index b16472e..54fc6d0 100644 --- a/src/SBaseCommon.cpp +++ b/src/SBaseCommon.cpp @@ -1,1774 +1,1774 @@ -/*****************************************************************************/ -/* SBaseCommon.cpp Copyright (c) Ladislav Zezula 2003 */ -/*---------------------------------------------------------------------------*/ -/* Common functions for StormLib, used by all SFile*** modules */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 24.03.03 1.00 Lad The first version of SFileCommon.cpp */ -/* 19.11.03 1.01 Dan Big endian handling */ -/* 12.06.04 1.01 Lad Renamed to SCommon.cpp */ -/* 06.09.10 1.01 Lad Renamed to SBaseCommon.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014"; - -//----------------------------------------------------------------------------- -// Local variables - -LCID lcFileLocale = LANG_NEUTRAL; // File locale -USHORT wPlatform = 0; // File platform - -//----------------------------------------------------------------------------- -// Conversion to uppercase/lowercase - -// Converts ASCII characters to lowercase -// Converts slash (0x2F) to backslash (0x5C) -unsigned char AsciiToLowerTable[256] = -{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, - 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, - 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, - 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, - 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, - 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF -}; - -// Converts ASCII characters to uppercase -// Converts slash (0x2F) to backslash (0x5C) -unsigned char AsciiToUpperTable[256] = -{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, - 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, - 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, - 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, - 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, - 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF -}; - -// Converts ASCII characters to uppercase -// Does NOT convert slash (0x2F) to backslash (0x5C) -unsigned char AsciiToUpperTable_Slash[256] = -{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, - 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, - 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, - 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, - 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, - 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF -}; - -//----------------------------------------------------------------------------- -// Safe string functions - -void StringCopyA(char * dest, const char * src, size_t nMaxChars) -{ - size_t nLength = strlen(src); - - // Don't copy more than nMaxChars - nLength = STORMLIB_MIN(nLength, nMaxChars); - memcpy(dest, src, nLength); - dest[nLength] = 0; -} - -void StringCatA(char * dest, const char * src, size_t nMaxChars) -{ - size_t nLength1 = strlen(dest); - size_t nLength2 = strlen(src); - - // Don't copy more than nMaxChars - if(nLength1 < nMaxChars) - { - nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1)); - memcpy(dest + nLength1, src, nLength2); - dest[nLength1 + nLength2] = 0; - } -} - -void StringCopyT(TCHAR * dest, const TCHAR * src, size_t nMaxChars) -{ - size_t nLength = _tcslen(src); - - // Don't copy more than nMaxChars - nLength = STORMLIB_MIN(nLength, nMaxChars); - memcpy(dest, src, (nLength * sizeof(TCHAR))); - dest[nLength] = 0; -} - -void StringCatT(TCHAR * dest, const TCHAR * src, size_t nMaxChars) -{ - size_t nLength1 = _tcslen(dest); - size_t nLength2 = _tcslen(src); - - // Don't copy more than nMaxChars - if(nLength1 < nMaxChars) - { - nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1)); - memcpy(dest + nLength1, src, (nLength2 * sizeof(TCHAR))); - dest[nLength1 + nLength2] = 0; - } -} - -//----------------------------------------------------------------------------- -// Storm hashing functions - -#define STORM_BUFFER_SIZE 0x500 - -#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0) - -static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine -static bool bMpqCryptographyInitialized = false; - -void InitializeMpqCryptography() -{ - DWORD dwSeed = 0x00100001; - DWORD index1 = 0; - DWORD index2 = 0; - int i; - - // Initialize the decryption buffer. - // Do nothing if already done. - if(bMpqCryptographyInitialized == false) - { - for(index1 = 0; index1 < 0x100; index1++) - { - for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100) - { - DWORD temp1, temp2; - - dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB; - temp1 = (dwSeed & 0xFFFF) << 0x10; - - dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB; - temp2 = (dwSeed & 0xFFFF); - - StormBuffer[index2] = (temp1 | temp2); - } - } - - // Also register both MD5 and SHA1 hash algorithms - register_hash(&md5_desc); - register_hash(&sha1_desc); - - // Use LibTomMath as support math library for LibTomCrypt - ltc_mp = ltm_desc; - - // Don't do that again - bMpqCryptographyInitialized = true; - } -} - -DWORD HashString(const char * szFileName, DWORD dwHashType) -{ - LPBYTE pbKey = (BYTE *)szFileName; - DWORD dwSeed1 = 0x7FED7FED; - DWORD dwSeed2 = 0xEEEEEEEE; - DWORD ch; - - while(*pbKey != 0) - { - // Convert the input character to uppercase - // Convert slash (0x2F) to backslash (0x5C) - ch = AsciiToUpperTable[*pbKey++]; - - dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); - dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; - } - - return dwSeed1; -} - -DWORD HashStringSlash(const char * szFileName, DWORD dwHashType) -{ - LPBYTE pbKey = (BYTE *)szFileName; - DWORD dwSeed1 = 0x7FED7FED; - DWORD dwSeed2 = 0xEEEEEEEE; - DWORD ch; - - while(*pbKey != 0) - { - // Convert the input character to uppercase - // DON'T convert slash (0x2F) to backslash (0x5C) - ch = AsciiToUpperTable_Slash[*pbKey++]; - - dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); - dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; - } - - return dwSeed1; -} - -DWORD HashStringLower(const char * szFileName, DWORD dwHashType) -{ - LPBYTE pbKey = (BYTE *)szFileName; - DWORD dwSeed1 = 0x7FED7FED; - DWORD dwSeed2 = 0xEEEEEEEE; - DWORD ch; - - while(*pbKey != 0) - { - // Convert the input character to lower - // DON'T convert slash (0x2F) to backslash (0x5C) - ch = AsciiToLowerTable[*pbKey++]; - - dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2); - dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3; - } - - return dwSeed1; -} - -//----------------------------------------------------------------------------- -// Calculates the hash table size for a given amount of files - -DWORD GetHashTableSizeForFileCount(DWORD dwFileCount) -{ - DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN; - - // For zero files, there is no hash table needed - if(dwFileCount == 0) - return 0; - - // Round the hash table size up to the nearest power of two - // Don't allow the hash table size go over allowed maximum - while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount) - dwPowerOfTwo <<= 1; - return dwPowerOfTwo; -} - -//----------------------------------------------------------------------------- -// Calculates a Jenkin's Encrypting and decrypting MPQ file data - -ULONGLONG HashStringJenkins(const char * szFileName) -{ - LPBYTE pbFileName = (LPBYTE)szFileName; - char szNameBuff[0x108]; - size_t nLength = 0; - unsigned int primary_hash = 1; - unsigned int secondary_hash = 2; - - // Normalize the file name - convert to uppercase, and convert "/" to "\\". - if(pbFileName != NULL) - { - char * szNamePtr = szNameBuff; - char * szNameEnd = szNamePtr + sizeof(szNameBuff); - - // Normalize the file name. Doesn't have to be zero terminated for hashing - while(szNamePtr < szNameEnd && pbFileName[0] != 0) - *szNamePtr++ = (char)AsciiToLowerTable[*pbFileName++]; - nLength = szNamePtr - szNameBuff; - } - - // Thanks Quantam for finding out what the algorithm is. - // I am really getting old for reversing large chunks of assembly - // that does hashing :-) - hashlittle2(szNameBuff, nLength, &secondary_hash, &primary_hash); - - // Combine those 2 together - return ((ULONGLONG)primary_hash << 0x20) | (ULONGLONG)secondary_hash; -} - -//----------------------------------------------------------------------------- -// Default flags for (attributes) and (listfile) - -DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion) -{ - // Fixed for format 1.0 - if(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/Decrypting MPQ data block - -void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1) -{ - LPDWORD DataBlock = (LPDWORD)pvDataBlock; - DWORD dwValue32; - DWORD dwKey2 = 0xEEEEEEEE; - - // Round to DWORDs - dwLength >>= 2; - - // Encrypt the data block at array of DWORDs - for(DWORD i = 0; i < dwLength; i++) - { - // Modify the second key - dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; - - dwValue32 = DataBlock[i]; - DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2); - - dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); - dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3; - } -} - -void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1) -{ - LPDWORD DataBlock = (LPDWORD)pvDataBlock; - DWORD dwValue32; - DWORD dwKey2 = 0xEEEEEEEE; - - // Round to DWORDs - dwLength >>= 2; - - // Decrypt the data block at array of DWORDs - for(DWORD i = 0; i < dwLength; i++) - { - // Modify the second key - dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; - - DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2); - dwValue32 = DataBlock[i]; - - dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); - dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3; - } -} - -/** - * Functions tries to get file decryption key. This comes from these facts - * - * - We know the decrypted value of the first DWORD in the encrypted data - * - We know the decrypted value of the second DWORD (at least aproximately) - * - There is only 256 variants of how the second key is modified - * - * The first iteration of dwKey1 and dwKey2 is this: - * - * dwKey2 = 0xEEEEEEEE + StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)] - * dwDecrypted0 = DataBlock[0] ^ (dwKey1 + dwKey2); - * - * This means: - * - * (dwKey1 + dwKey2) = DataBlock[0] ^ dwDecrypted0; - * - */ - -DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwDecrypted0) -{ - DWORD dwDecrypted1Max = dwSectorSize + dwDecrypted0; - DWORD dwKey1PlusKey2; - DWORD DataBlock[2]; - - // We must have at least 2 DWORDs there to be able to decrypt something - if(dwSectorSize < 0x08) - return 0; - - // Get the value of the combined encryption key - dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE; - - // Try all 256 combinations of dwKey1 - for(DWORD i = 0; i < 0x100; i++) - { - DWORD dwSaveKey1; - DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i]; - DWORD dwKey2 = 0xEEEEEEEE; - - // Modify the second key and decrypt the first DWORD - dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; - DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2); - - // Did we obtain the same value like dwDecrypted0? - if(DataBlock[0] == dwDecrypted0) - { - // Save this key value. Increment by one because - // we are decrypting sector offset table - dwSaveKey1 = dwKey1 + 1; - - // Rotate both keys - dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); - dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3; - - // Modify the second key again and decrypt the second DWORD - dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; - DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2); - - // Now compare the results - if(DataBlock[1] <= dwDecrypted1Max) - return dwSaveKey1; - } - } - - // Key not found - return 0; -} - -// Function tries to detect file encryption key based on expected file content -// It is the same function like before, except that we know the value of the second DWORD -DWORD DetectFileKeyByKnownContent(void * pvEncryptedData, DWORD dwDecrypted0, DWORD dwDecrypted1) -{ - LPDWORD EncryptedData = (LPDWORD)pvEncryptedData; - DWORD dwKey1PlusKey2; - DWORD DataBlock[2]; - - // Get the value of the combined encryption key - dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE; - - // Try all 256 combinations of dwKey1 - for(DWORD i = 0; i < 0x100; i++) - { - DWORD dwSaveKey1; - DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i]; - DWORD dwKey2 = 0xEEEEEEEE; - - // Modify the second key and decrypt the first DWORD - dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; - DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2); - - // Did we obtain the same value like dwDecrypted0? - if(DataBlock[0] == dwDecrypted0) - { - // Save this key value - dwSaveKey1 = dwKey1; - - // Rotate both keys - dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B); - dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3; - - // Modify the second key again and decrypt the second DWORD - dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]; - DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2); - - // Now compare the results - if(DataBlock[1] == dwDecrypted1) - return dwSaveKey1; - } - } - - // Key not found - return 0; -} - -DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize) -{ - DWORD dwFileKey; - - // Try to break the file encryption key as if it was a WAVE file - if(dwSectorSize >= 0x0C) - { - dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x46464952, dwFileSize - 8); - if(dwFileKey != 0) - return dwFileKey; - } - - // Try to break the encryption key as if it was an EXE file - if(dwSectorSize > 0x40) - { - dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x00905A4D, 0x00000003); - if(dwFileKey != 0) - return dwFileKey; - } - - // Try to break the encryption key as if it was a XML file - if(dwSectorSize > 0x04) - { - dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x6D783F3C, 0x6576206C); - if(dwFileKey != 0) - return dwFileKey; - } - - // Not detected, sorry - return 0; -} - -DWORD DecryptFileKey( - const char * szFileName, - ULONGLONG MpqPos, - DWORD dwFileSize, - DWORD dwFlags) -{ - DWORD dwFileKey; - DWORD dwMpqPos = (DWORD)MpqPos; - - // File key is calculated from plain name - szFileName = GetPlainFileName(szFileName); - dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY); - - // Fix the key, if needed - if(dwFlags & MPQ_FILE_FIX_KEY) - dwFileKey = (dwFileKey + dwMpqPos) ^ dwFileSize; - - // Return the key - return dwFileKey; -} - -//----------------------------------------------------------------------------- -// Handle validation functions - -TMPQArchive * IsValidMpqHandle(HANDLE hMpq) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL; -} - -TMPQFile * IsValidFileHandle(HANDLE hFile) -{ - TMPQFile * hf = (TMPQFile *)hFile; - - // Must not be NULL - if(hf != NULL && hf->dwMagic == ID_MPQ_FILE) - { - // Local file handle? - if(hf->pStream != NULL) - return hf; - - // Also verify the MPQ handle within the file handle - if(IsValidMpqHandle(hf->ha)) - return hf; - } - - return NULL; -} - -//----------------------------------------------------------------------------- -// Hash table and block table manipulation - -// Attempts to search a free hash entry, or an entry whose names and locale matches -TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale) -{ - TMPQHash * pDeletedEntry = NULL; // If a deleted entry was found in the continuous hash range - TMPQHash * pFreeEntry = NULL; // If a free entry was found in the continuous hash range - DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); - DWORD dwIndex; - - // Set the initial index - dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask); - - // Search the hash table and return the found entries in the following priority: - // 1) <MATCHING_ENTRY> - // 2) <DELETED-ENTRY> - // 3) <FREE-ENTRY> - // 4) NULL - for(;;) - { - TMPQHash * pHash = ha->pHashTable + dwIndex; - - // If we found a matching entry, return that one - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == lcLocale) - return pHash; - - // If we found a deleted entry, remember it but keep searching - if(pHash->dwBlockIndex == HASH_ENTRY_DELETED && pDeletedEntry == NULL) - pDeletedEntry = pHash; - - // If we found a free entry, we need to stop searching - if(pHash->dwBlockIndex == HASH_ENTRY_FREE) - { - pFreeEntry = pHash; - break; - } - - // Move to the next hash entry. - // If we reached the starting entry, it's failure. - dwIndex = (dwIndex + 1) & dwHashIndexMask; - if(dwIndex == dwStartIndex) - break; - } - - // If we found a deleted entry, return that one preferentially - return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry; -} - -// Retrieves the first hash entry for the given file. -// Every locale version of a file has its own hash entry -TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName) -{ - DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); - DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX); - DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A); - DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B); - DWORD dwIndex; - - // Set the initial index - dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask); - - // Search the hash table - for(;;) - { - TMPQHash * pHash = ha->pHashTable + dwIndex; - - // If the entry matches, we found it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize) - return pHash; - - // If that hash entry is a free entry, it means we haven't found the file - if(pHash->dwBlockIndex == HASH_ENTRY_FREE) - return NULL; - - // Move to the next hash entry. Stop searching - // if we got reached the original hash entry - dwIndex = (dwIndex + 1) & dwHashIndexMask; - if(dwIndex == dwStartIndex) - return NULL; - } -} - -TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash) -{ - DWORD dwHashIndexMask = HASH_INDEX_MASK(ha); - DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable); - DWORD dwName1 = pHash->dwName1; - DWORD dwName2 = pHash->dwName2; - DWORD dwIndex = (DWORD)(pHash - ha->pHashTable); - - // Now go for any next entry that follows the pHash, - // until either free hash entry was found, or the start entry was reached - for(;;) - { - // Move to the next hash entry. Stop searching - // if we got reached the original hash entry - dwIndex = (dwIndex + 1) & dwHashIndexMask; - if(dwIndex == dwStartIndex) - return NULL; - pHash = ha->pHashTable + dwIndex; - - // If the entry matches, we found it. - if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize) - return pHash; - - // If that hash entry is a free entry, it means we haven't found the file - if(pHash->dwBlockIndex == HASH_ENTRY_FREE) - return NULL; - } -} - -// Allocates an entry in the hash table -TMPQHash * AllocateHashEntry( - TMPQArchive * ha, - TFileEntry * pFileEntry, - LCID lcLocale) -{ - TMPQHash * pHash; - DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX); - DWORD dwName1 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_A); - DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B); - - // Attempt to find a free hash entry - pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, lcLocale); - if(pHash != NULL) - { - // Fill the free hash entry - pHash->dwName1 = dwName1; - pHash->dwName2 = dwName2; - pHash->lcLocale = (USHORT)lcLocale; - pHash->wPlatform = 0; - pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); - } - - return pHash; -} - -// Finds a free space in the MPQ where to store next data -// The free space begins beyond the file that is stored at the fuhrtest -// position in the MPQ. (listfile), (attributes) and (signature) are ignored, -// unless the MPQ is being flushed. -ULONGLONG FindFreeMpqSpace(TMPQArchive * ha) -{ - TMPQHeader * pHeader = ha->pHeader; - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; - ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize; - DWORD dwChunkCount; - - // Parse the entire block table - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Only take existing files with nonzero size - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0)) - { - // If we are not saving MPQ tables, ignore internal MPQ files - if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0 && IsInternalMpqFileName(pFileEntry->szFileName)) - continue; - - // If the end of the file is bigger than current MPQ table pos, update it - if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos) - { - // Get the end of the file data - FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize; - - // Add the MD5 chunks, if present - if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0) - { - dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1; - FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE; - } - } - } - } - - // Give the free space position to the caller - return FreeSpacePos; -} - -//----------------------------------------------------------------------------- -// Common functions - MPQ File - -TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry) -{ - TMPQFile * hf; - - // Allocate space for TMPQFile - hf = STORM_ALLOC(TMPQFile, 1); - if(hf != NULL) - { - // Fill the file structure - memset(hf, 0, sizeof(TMPQFile)); - hf->dwMagic = ID_MPQ_FILE; - hf->pStream = NULL; - hf->ha = ha; - - // If the called entered a file entry, we also copy informations from the file entry - if(ha != NULL && pFileEntry != NULL) - { - // Set the raw position and MPQ position - hf->RawFilePos = FileOffsetFromMpqOffset(ha, pFileEntry->ByteOffset); - hf->MpqFilePos = pFileEntry->ByteOffset; - - // Set the data size - hf->dwDataSize = pFileEntry->dwFileSize; - hf->pFileEntry = pFileEntry; - } - } - - return hf; -} - -TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize) -{ - ULONGLONG FreeMpqSpace; - ULONGLONG TempPos; - TMPQFile * hf; - - // We need to find the position in the MPQ where we save the file data - FreeMpqSpace = FindFreeMpqSpace(ha); - - // When format V1, the size of the archive cannot exceed 4 GB - if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) - { - TempPos = FreeMpqSpace + - dwFileSize + - (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)) + - (ha->dwFileTableSize * sizeof(TMPQBlock)); - if((TempPos >> 32) != 0) - { - SetLastError(ERROR_DISK_FULL); - return NULL; - } - } - - // Allocate the file handle - hf = CreateFileHandle(ha, NULL); - if(hf == NULL) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return NULL; - } - - // We need to find the position in the MPQ where we save the file data - hf->MpqFilePos = FreeMpqSpace; - hf->bIsWriteHandle = true; - return hf; -} - -// Loads a table from MPQ. -// Can be used for hash table, block table, sector offset table or sector checksum table -void * LoadMpqTable( - TMPQArchive * ha, - ULONGLONG ByteOffset, - DWORD dwCompressedSize, - DWORD dwTableSize, - DWORD dwKey, - bool * pbTableIsCut) -{ - ULONGLONG FileSize = 0; - LPBYTE pbCompressed = NULL; - LPBYTE pbMpqTable; - LPBYTE pbToRead; - DWORD dwBytesToRead = dwCompressedSize; - int nError = ERROR_SUCCESS; - - // Allocate the MPQ table - pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize); - if(pbMpqTable != NULL) - { - // Check if the MPQ table is encrypted - if(dwCompressedSize < dwTableSize) - { - // Allocate temporary buffer for holding compressed data - pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize); - if(pbCompressed == NULL) - { - STORM_FREE(pbMpqTable); - return NULL; - } - } - - // Get the file offset from which we will read the table - // Note: According to Storm.dll from Warcraft III (version 2002), - // if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done - // and the table is loaded from the current file offset - if(ByteOffset == SFILE_INVALID_POS) - FileStream_GetPos(ha->pStream, &ByteOffset); - - // On archives v 1.0, hash table and block table can go beyond EOF. - // Storm.dll reads as much as possible, then fills the missing part with zeros. - // Abused by Spazzler map protector which sets hash table size to 0x00100000 - if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1) - { - // Cut the table size - FileStream_GetSize(ha->pStream, &FileSize); - if((ByteOffset + dwBytesToRead) > FileSize) - { - // Fill the extra data with zeros - dwBytesToRead = (DWORD)(FileSize - ByteOffset); - memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead)); - - // Give the caller information that the table was cut - if(pbTableIsCut != NULL) - pbTableIsCut[0] = true; - } - } - - // If everything succeeded, read the raw table form the MPQ - if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead)) - { - // First of all, decrypt the table - if(dwKey != 0) - { - BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); - DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey); - BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize); - } - - // If the table is compressed, decompress it - if(dwCompressedSize < dwTableSize) - { - int cbOutBuffer = (int)dwTableSize; - int cbInBuffer = (int)dwCompressedSize; - - if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer)) - nError = GetLastError(); - } - - // Make sure that the table is properly byte-swapped - BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize); - } - else - { - nError = GetLastError(); - } - - // If read failed, free the table and return - if(nError != ERROR_SUCCESS) - { - STORM_FREE(pbMpqTable); - pbMpqTable = NULL; - } - - // Free the compression buffer, if any - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - } - - // Return the MPQ table - return pbMpqTable; -} - -unsigned char * AllocateMd5Buffer( - DWORD dwRawDataSize, - DWORD dwChunkSize, - LPDWORD pcbMd5Size) -{ - unsigned char * md5_array; - DWORD cbMd5Size; - - // Sanity check - assert(dwRawDataSize != 0); - assert(dwChunkSize != 0); - - // Calculate how many MD5's we will calculate - cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE; - - // Allocate space for array or MD5s - md5_array = STORM_ALLOC(BYTE, cbMd5Size); - - // Give the size of the MD5 array - if(pcbMd5Size != NULL) - *pcbMd5Size = cbMd5Size; - return md5_array; -} - -// Allocates sector buffer and sector offset table -int AllocateSectorBuffer(TMPQFile * hf) -{ - TMPQArchive * ha = hf->ha; - - // Caller of AllocateSectorBuffer must ensure these - assert(hf->pbFileSector == NULL); - assert(hf->pFileEntry != NULL); - 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); - hf->dwSectorOffs = SFILE_INVALID_POS; - - // Return result - return (hf->pbFileSector != NULL) ? (int)ERROR_SUCCESS : (int)ERROR_NOT_ENOUGH_MEMORY; -} - -// Allocates sector offset table -int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile) -{ - TMPQArchive * ha = hf->ha; - DWORD dwLength = sizeof(TPatchInfo); - - // The following conditions must be true - assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE); - assert(hf->pPatchInfo == NULL); - -__AllocateAndLoadPatchInfo: - - // Allocate space for patch header. Start with default size, - // and if its size if bigger, then we reload them - hf->pPatchInfo = STORM_ALLOC(TPatchInfo, 1); - if(hf->pPatchInfo == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Do we have to load the patch header from the file ? - if(bLoadFromFile) - { - // Load the patch header - if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength)) - { - // Free the patch info - STORM_FREE(hf->pPatchInfo); - hf->pPatchInfo = NULL; - return GetLastError(); - } - - // Perform necessary swapping - hf->pPatchInfo->dwLength = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwLength); - hf->pPatchInfo->dwFlags = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwFlags); - hf->pPatchInfo->dwDataSize = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwDataSize); - - // Verify the size of the patch header - // 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; - } - - // Patch file data size according to the patch header - hf->dwDataSize = hf->pPatchInfo->dwDataSize; - } - else - { - memset(hf->pPatchInfo, 0, dwLength); - } - - // Save the final length to the patch header - hf->pPatchInfo->dwLength = dwLength; - hf->pPatchInfo->dwFlags = 0x80000000; - return ERROR_SUCCESS; -} - -// Allocates sector offset table -int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile) -{ - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - DWORD dwSectorOffsLen; - bool bSectorOffsetTableCorrupt = false; - - // Caller of AllocateSectorOffsets must ensure these - assert(hf->SectorOffsets == NULL); - assert(hf->pFileEntry != NULL); - assert(hf->dwDataSize != 0); - assert(hf->ha != NULL); - - // If the file is stored as single unit, just set number of sectors to 1 - if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) - { - hf->dwSectorCount = 1; - return ERROR_SUCCESS; - } - - // 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 - dwSectorOffsLen = (hf->dwSectorCount + 1) * sizeof(DWORD); - - // If MPQ_FILE_SECTOR_CRC flag is set, there will either be extra DWORD - // or an array of MD5's. Either way, we read at least 4 bytes more - // in order to save additional read from the file. - if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) - dwSectorOffsLen += sizeof(DWORD); - - // Only allocate and load the table if the file is compressed - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - __LoadSectorOffsets: - - // Allocate the sector offset table - hf->SectorOffsets = STORM_ALLOC(DWORD, (dwSectorOffsLen / sizeof(DWORD))); - if(hf->SectorOffsets == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Only read from the file if we are supposed to do so - if(bLoadFromFile) - { - ULONGLONG RawFilePos = hf->RawFilePos; - - // Append the length of the patch info, if any - if(hf->pPatchInfo != NULL) - { - if((RawFilePos + hf->pPatchInfo->dwLength) < RawFilePos) - return ERROR_FILE_CORRUPT; - RawFilePos += hf->pPatchInfo->dwLength; - } - - // Load the sector offsets from the file - if(!FileStream_Read(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen)) - { - // Free the sector offsets - STORM_FREE(hf->SectorOffsets); - hf->SectorOffsets = NULL; - return GetLastError(); - } - - // Swap the sector positions - BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen); - - // Decrypt loaded sector positions if necessary - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - // If we don't know the file key, try to find it. - if(hf->dwFileKey == 0) - { - hf->dwFileKey = DetectFileKeyBySectorSize(hf->SectorOffsets, ha->dwSectorSize, dwSectorOffsLen); - if(hf->dwFileKey == 0) - { - STORM_FREE(hf->SectorOffsets); - hf->SectorOffsets = NULL; - return ERROR_UNKNOWN_FILE_KEY; - } - } - - // Decrypt sector positions - DecryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1); - } - - // - // Validate the sector offset table - // - // Note: Some MPQ protectors put the actual file data before the sector offset table. - // In this case, the sector offsets are negative (> 0x80000000). - // - - for(DWORD i = 0; i < hf->dwSectorCount; i++) - { - DWORD dwSectorOffset1 = hf->SectorOffsets[i+1]; - DWORD dwSectorOffset0 = hf->SectorOffsets[i]; - - // Every following sector offset must be bigger than the previous one - if(dwSectorOffset1 <= dwSectorOffset0) - { - bSectorOffsetTableCorrupt = true; - break; - } - - // The sector size must not be bigger than compressed file size - // Edit: Yes, but apparently, in original Storm.dll, the compressed - // size is not checked anywhere. However, we need to do this check - // in order to sector offset table malformed by MPQ protectors - if((dwSectorOffset1 - dwSectorOffset0) > ha->dwSectorSize) - { - bSectorOffsetTableCorrupt = true; - break; - } - } - - // If data corruption detected, free the sector offset table - if(bSectorOffsetTableCorrupt) - { - STORM_FREE(hf->SectorOffsets); - hf->SectorOffsets = NULL; - return ERROR_FILE_CORRUPT; - } - - // - // 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. - // - // These extra values are, however, include in the dwCmpSize in the file - // table. We cannot ignore them, because compacting archive would fail - // - - if(hf->SectorOffsets[0] > dwSectorOffsLen) - { - // MPQ protectors put some ridiculous values there. We must limit the extra bytes - if(hf->SectorOffsets[0] > (dwSectorOffsLen + 0x400)) - return ERROR_FILE_CORRUPT; - - // Free the old sector offset table - dwSectorOffsLen = hf->SectorOffsets[0]; - STORM_FREE(hf->SectorOffsets); - goto __LoadSectorOffsets; - } - } - else - { - memset(hf->SectorOffsets, 0, dwSectorOffsLen); - hf->SectorOffsets[0] = dwSectorOffsLen; - } - } - - return ERROR_SUCCESS; -} - -int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile) -{ - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - ULONGLONG RawFilePos; - DWORD dwCompressedSize = 0; - DWORD dwExpectedSize; - DWORD dwCrcOffset; // Offset of the CRC table, relative to file offset in the MPQ - DWORD dwCrcSize; - - // Caller of AllocateSectorChecksums must ensure these - assert(hf->SectorChksums == NULL); - assert(hf->SectorOffsets != NULL); - assert(hf->pFileEntry != NULL); - assert(hf->ha != NULL); - - // Single unit files don't have sector checksums - if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) - return ERROR_SUCCESS; - - // Caller must ensure that we are only called when we have sector checksums - assert(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC); - - // - // Older MPQs store an array of CRC32's after - // the raw file data in the MPQ. - // - // In newer MPQs, the (since Cataclysm BETA) the (attributes) file - // contains additional 32-bit values beyond the sector table. - // Their number depends on size of the (attributes), but their - // meaning is unknown. They are usually zeroed in retail game files, - // but contain some sort of checksum in BETA MPQs - // - - // Does the size of the file table match with the CRC32-based checksums? - dwExpectedSize = (hf->dwSectorCount + 2) * sizeof(DWORD); - if(hf->SectorOffsets[0] != 0 && hf->SectorOffsets[0] == dwExpectedSize) - { - // If we are not loading from the MPQ file, we just allocate the sector table - // In that case, do not check any sizes - if(bLoadFromFile == false) - { - hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount); - if(hf->SectorChksums == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill the checksum table with zeros - memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD)); - return ERROR_SUCCESS; - } - else - { - // Is there valid size of the sector checksums? - if(hf->SectorOffsets[hf->dwSectorCount + 1] >= hf->SectorOffsets[hf->dwSectorCount]) - dwCompressedSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount]; - - // Ignore cases when the length is too small or too big. - if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize) - return ERROR_SUCCESS; - - // Calculate offset of the CRC table - dwCrcSize = hf->dwSectorCount * sizeof(DWORD); - dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount]; - RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset); - - // Now read the table from the MPQ - hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0, NULL); - if(hf->SectorChksums == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - } - } - - // If the size doesn't match, we ignore sector checksums -// assert(false); - return ERROR_SUCCESS; -} - -int WritePatchInfo(TMPQFile * hf) -{ - TMPQArchive * ha = hf->ha; - TPatchInfo * pPatchInfo = hf->pPatchInfo; - - // The caller must make sure that this function is only called - // when the following is true. - assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE); - assert(pPatchInfo != NULL); - - BSWAP_ARRAY32_UNSIGNED(pPatchInfo, 3 * sizeof(DWORD)); - if(!FileStream_Write(ha->pStream, &hf->RawFilePos, pPatchInfo, sizeof(TPatchInfo))) - return GetLastError(); - - return ERROR_SUCCESS; -} - -int WriteSectorOffsets(TMPQFile * hf) -{ - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - ULONGLONG RawFilePos = hf->RawFilePos; - DWORD dwSectorOffsLen; - - // The caller must make sure that this function is only called - // when the following is true. - assert(hf->pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK); - assert(hf->SectorOffsets != NULL); - dwSectorOffsLen = hf->SectorOffsets[0]; - - // If file is encrypted, sector positions are also encrypted - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - EncryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1); - BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen); - - // Adjust sector offset table position, if we also have patch info - if(hf->pPatchInfo != NULL) - RawFilePos += hf->pPatchInfo->dwLength; - - // Write sector offsets to the archive - if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen)) - return GetLastError(); - - // Not necessary, as the sector checksums - // are going to be freed when this is done. -// BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen); - return ERROR_SUCCESS; -} - - -int WriteSectorChecksums(TMPQFile * hf) -{ - TMPQArchive * ha = hf->ha; - ULONGLONG RawFilePos; - TFileEntry * pFileEntry = hf->pFileEntry; - LPBYTE pbCompressed; - DWORD dwCompressedSize = 0; - DWORD dwCrcSize; - int nOutSize; - int nError = ERROR_SUCCESS; - - // The caller must make sure that this function is only called - // when the following is true. - assert(hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC); - assert(hf->SectorOffsets != NULL); - assert(hf->SectorChksums != NULL); - - // If the MPQ has MD5 of each raw data chunk, - // we leave sector offsets empty - if(ha->pHeader->dwRawChunkSize != 0) - { - hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount]; - return ERROR_SUCCESS; - } - - // Calculate size of the checksum array - dwCrcSize = hf->dwSectorCount * sizeof(DWORD); - - // Allocate buffer for compressed sector CRCs. - pbCompressed = STORM_ALLOC(BYTE, dwCrcSize); - if(pbCompressed == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Perform the compression - BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize); - - nOutSize = (int)dwCrcSize; - SCompCompress(pbCompressed, &nOutSize, hf->SectorChksums, (int)dwCrcSize, MPQ_COMPRESSION_ZLIB, 0, 0); - dwCompressedSize = (DWORD)nOutSize; - - // Write the sector CRCs to the archive - RawFilePos = hf->RawFilePos + hf->SectorOffsets[hf->dwSectorCount]; - if(hf->pPatchInfo != NULL) - RawFilePos += hf->pPatchInfo->dwLength; - if(!FileStream_Write(ha->pStream, &RawFilePos, pbCompressed, dwCompressedSize)) - nError = GetLastError(); - - // Not necessary, as the sector checksums - // are going to be freed when this is done. -// BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize); - - // Store the sector CRCs - hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount] + dwCompressedSize; - pFileEntry->dwCmpSize += dwCompressedSize; - STORM_FREE(pbCompressed); - return nError; -} - -int WriteMemDataMD5( - TFileStream * pStream, - ULONGLONG RawDataOffs, - void * pvRawData, - DWORD dwRawDataSize, - DWORD dwChunkSize, - LPDWORD pcbTotalSize) -{ - unsigned char * md5_array; - unsigned char * md5; - LPBYTE pbRawData = (LPBYTE)pvRawData; - DWORD dwBytesRemaining = dwRawDataSize; - DWORD dwMd5ArraySize = 0; - int nError = ERROR_SUCCESS; - - // Allocate buffer for array of MD5 - md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize); - if(md5_array == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // For every file chunk, calculate MD5 - while(dwBytesRemaining != 0) - { - // Get the remaining number of bytes to read - dwChunkSize = STORMLIB_MIN(dwBytesRemaining, dwChunkSize); - - // Calculate MD5 - CalculateDataBlockHash(pbRawData, dwChunkSize, md5); - md5 += MD5_DIGEST_SIZE; - - // Move offset and size - dwBytesRemaining -= dwChunkSize; - pbRawData += dwChunkSize; - } - - // Write the array od MD5's to the file - RawDataOffs += dwRawDataSize; - if(!FileStream_Write(pStream, &RawDataOffs, md5_array, dwMd5ArraySize)) - nError = GetLastError(); - - // Give the caller the size of the MD5 array - if(pcbTotalSize != NULL) - *pcbTotalSize = dwRawDataSize + dwMd5ArraySize; - - // Free buffers and exit - STORM_FREE(md5_array); - return nError; -} - - -// Writes the MD5 for each chunk of the raw file data -int WriteMpqDataMD5( - TFileStream * pStream, - ULONGLONG RawDataOffs, - DWORD dwRawDataSize, - DWORD dwChunkSize) -{ - unsigned char * md5_array; - unsigned char * md5; - LPBYTE pbFileChunk; - DWORD dwMd5ArraySize = 0; - DWORD dwToRead = dwRawDataSize; - int nError = ERROR_SUCCESS; - - // Allocate buffer for array of MD5 - md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize); - if(md5_array == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Allocate space for file chunk - pbFileChunk = STORM_ALLOC(BYTE, dwChunkSize); - if(pbFileChunk == NULL) - { - STORM_FREE(md5_array); - return ERROR_NOT_ENOUGH_MEMORY; - } - - // For every file chunk, calculate MD5 - while(dwRawDataSize != 0) - { - // Get the remaining number of bytes to read - dwToRead = STORMLIB_MIN(dwRawDataSize, dwChunkSize); - - // Read the chunk - if(!FileStream_Read(pStream, &RawDataOffs, pbFileChunk, dwToRead)) - { - nError = GetLastError(); - break; - } - - // Calculate MD5 - CalculateDataBlockHash(pbFileChunk, dwToRead, md5); - md5 += MD5_DIGEST_SIZE; - - // Move offset and size - RawDataOffs += dwToRead; - dwRawDataSize -= dwToRead; - } - - // Write the array od MD5's to the file - if(nError == ERROR_SUCCESS) - { - if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize)) - nError = GetLastError(); - } - - // Free buffers and exit - STORM_FREE(pbFileChunk); - STORM_FREE(md5_array); - return nError; -} - -// Frees the structure for MPQ file -void FreeFileHandle(TMPQFile *& hf) -{ - if(hf != NULL) - { - // If we have patch file attached to this one, free it first - if(hf->hfPatch != NULL) - FreeFileHandle(hf->hfPatch); - - // Then free all buffers allocated in the file structure - if(hf->pbFileData != NULL) - STORM_FREE(hf->pbFileData); - if(hf->pPatchInfo != NULL) - STORM_FREE(hf->pPatchInfo); - if(hf->SectorOffsets != NULL) - STORM_FREE(hf->SectorOffsets); - if(hf->SectorChksums != NULL) - STORM_FREE(hf->SectorChksums); - if(hf->pbFileSector != NULL) - STORM_FREE(hf->pbFileSector); - if(hf->pStream != NULL) - FileStream_Close(hf->pStream); - STORM_FREE(hf); - hf = NULL; - } -} - -// Frees the MPQ archive -void FreeArchiveHandle(TMPQArchive *& ha) -{ - if(ha != NULL) - { - // First of all, free the patch archive, if any - if(ha->haPatch != NULL) - FreeArchiveHandle(ha->haPatch); - - // Free the patch prefix, if any - if(ha->pPatchPrefix != NULL) - STORM_FREE(ha->pPatchPrefix); - - // Close the file stream - FileStream_Close(ha->pStream); - ha->pStream = NULL; - - // Free the file names from the file table - if(ha->pFileTable != NULL) - { - for(DWORD i = 0; i < ha->dwFileTableSize; i++) - { - if(ha->pFileTable[i].szFileName != NULL) - STORM_FREE(ha->pFileTable[i].szFileName); - ha->pFileTable[i].szFileName = NULL; - } - - // Then free all buffers allocated in the archive structure - STORM_FREE(ha->pFileTable); - } - - if(ha->pHashTable != NULL) - STORM_FREE(ha->pHashTable); - if(ha->pHetTable != NULL) - FreeHetTable(ha->pHetTable); - STORM_FREE(ha); - ha = NULL; - } -} - -bool IsInternalMpqFileName(const char * szFileName) -{ - if(szFileName != NULL && szFileName[0] == '(') - { - if(!_stricmp(szFileName, LISTFILE_NAME) || - !_stricmp(szFileName, ATTRIBUTES_NAME) || - !_stricmp(szFileName, SIGNATURE_NAME)) - { - return true; - } - } - - return false; -} - -// Verifies if the file name is a pseudo-name -bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex) -{ - DWORD dwFileIndex = 0; - - if(szFileName != NULL) - { - // Must be "File########.ext" - if(!_strnicmp(szFileName, "File", 4)) - { - // Check 8 digits - for(int i = 4; i < 4+8; i++) - { - if(szFileName[i] < '0' || szFileName[i] > '9') - return false; - dwFileIndex = (dwFileIndex * 10) + (szFileName[i] - '0'); - } - - // An extension must follow - if(szFileName[12] == '.') - { - if(pdwFileIndex != NULL) - *pdwFileIndex = dwFileIndex; - return true; - } - } - } - - // Not a pseudo-name - return false; -} - -//----------------------------------------------------------------------------- -// Functions calculating and verifying the MD5 signature - -bool IsValidMD5(LPBYTE pbMd5) -{ - LPDWORD Md5 = (LPDWORD)pbMd5; - - return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false; -} - -bool IsValidSignature(LPBYTE pbSignature) -{ - LPDWORD Signature = (LPDWORD)pbSignature; - DWORD SigValid = 0; - - for(int i = 0; i < MPQ_WEAK_SIGNATURE_SIZE / sizeof(DWORD); i++) - SigValid |= Signature[i]; - - return (SigValid != 0) ? true : false; -} - - -bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5) -{ - hash_state md5_state; - BYTE md5_digest[MD5_DIGEST_SIZE]; - - // Don't verify the block if the MD5 is not valid. - if(!IsValidMD5(expected_md5)) - return true; - - // Calculate the MD5 of the data block - md5_init(&md5_state); - md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock); - md5_done(&md5_state, md5_digest); - - // Does the MD5's match? - return (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0); -} - -void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash) -{ - hash_state md5_state; - - md5_init(&md5_state); - md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock); - md5_done(&md5_state, md5_hash); -} - - -//----------------------------------------------------------------------------- -// Swapping functions - -#ifndef PLATFORM_LITTLE_ENDIAN - -// -// Note that those functions are implemented for Mac operating system, -// as this is the only supported platform that uses big endian. -// - -// Swaps a signed 16-bit integer -int16_t SwapInt16(uint16_t data) -{ - return (int16_t)CFSwapInt16(data); -} - -// Swaps an unsigned 16-bit integer -uint16_t SwapUInt16(uint16_t data) -{ - return CFSwapInt16(data); -} - -// Swaps signed 32-bit integer -int32_t SwapInt32(uint32_t data) -{ - return (int32_t)CFSwapInt32(data); -} - -// Swaps an unsigned 32-bit integer -uint32_t SwapUInt32(uint32_t data) -{ - return CFSwapInt32(data); -} - -// Swaps signed 64-bit integer -int64_t SwapInt64(int64_t data) -{ - return (int64_t)CFSwapInt64(data); -} - -// Swaps an unsigned 64-bit integer -uint64_t SwapUInt64(uint64_t data) -{ - return CFSwapInt64(data); -} - -// Swaps array of unsigned 16-bit integers -void ConvertUInt16Buffer(void * ptr, size_t length) -{ - uint16_t * buffer = (uint16_t *)ptr; - uint32_t nElements = (uint32_t)(length / sizeof(uint16_t)); - - while(nElements-- > 0) - { - *buffer = SwapUInt16(*buffer); - buffer++; - } -} - -// Swaps array of unsigned 32-bit integers -void ConvertUInt32Buffer(void * ptr, size_t length) -{ - uint32_t * buffer = (uint32_t *)ptr; - uint32_t nElements = (uint32_t)(length / sizeof(uint32_t)); - - while(nElements-- > 0) - { - *buffer = SwapUInt32(*buffer); - buffer++; - } -} - -// Swaps array of unsigned 64-bit integers -void ConvertUInt64Buffer(void * ptr, size_t length) -{ - uint64_t * buffer = (uint64_t *)ptr; - uint32_t nElements = (uint32_t)(length / sizeof(uint64_t)); - - while(nElements-- > 0) - { - *buffer = SwapUInt64(*buffer); - buffer++; - } -} - -// Swaps the TMPQHeader structure -void ConvertTMPQHeader(void *header, uint16_t version) -{ - TMPQHeader * theHeader = (TMPQHeader *)header; - - // Swap header part version 1 - if(version == MPQ_FORMAT_VERSION_1) - { - theHeader->dwID = SwapUInt32(theHeader->dwID); - theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize); - theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize); - theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion); - theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize); - theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos); - theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos); - theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize); - theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize); - } - - if(version == MPQ_FORMAT_VERSION_2) - { - theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64); - theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi); - theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi); - } - - if(version == MPQ_FORMAT_VERSION_3) - { - theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64); - theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64); - theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64); - } - - if(version == MPQ_FORMAT_VERSION_4) - { - theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64); - theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64); - theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64); - theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64); - theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64); - } -} - -#endif // PLATFORM_LITTLE_ENDIAN +/*****************************************************************************/
+/* SBaseCommon.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Common functions for StormLib, used by all SFile*** modules */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 24.03.03 1.00 Lad The first version of SFileCommon.cpp */
+/* 19.11.03 1.01 Dan Big endian handling */
+/* 12.06.04 1.01 Lad Renamed to SCommon.cpp */
+/* 06.09.10 1.01 Lad Renamed to SBaseCommon.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014";
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+LCID lcFileLocale = LANG_NEUTRAL; // File locale
+USHORT wPlatform = 0; // File platform
+
+//-----------------------------------------------------------------------------
+// Conversion to uppercase/lowercase
+
+// Converts ASCII characters to lowercase
+// Converts slash (0x2F) to backslash (0x5C)
+unsigned char AsciiToLowerTable[256] =
+{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+// Converts ASCII characters to uppercase
+// Converts slash (0x2F) to backslash (0x5C)
+unsigned char AsciiToUpperTable[256] =
+{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+// Converts ASCII characters to uppercase
+// Does NOT convert slash (0x2F) to backslash (0x5C)
+unsigned char AsciiToUpperTable_Slash[256] =
+{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+ 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+ 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+//-----------------------------------------------------------------------------
+// Safe string functions
+
+void StringCopyA(char * dest, const char * src, size_t nMaxChars)
+{
+ size_t nLength = strlen(src);
+
+ // Don't copy more than nMaxChars
+ nLength = STORMLIB_MIN(nLength, nMaxChars);
+ memcpy(dest, src, nLength);
+ dest[nLength] = 0;
+}
+
+void StringCatA(char * dest, const char * src, size_t nMaxChars)
+{
+ size_t nLength1 = strlen(dest);
+ size_t nLength2 = strlen(src);
+
+ // Don't copy more than nMaxChars
+ if(nLength1 < nMaxChars)
+ {
+ nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1));
+ memcpy(dest + nLength1, src, nLength2);
+ dest[nLength1 + nLength2] = 0;
+ }
+}
+
+void StringCopyT(TCHAR * dest, const TCHAR * src, size_t nMaxChars)
+{
+ size_t nLength = _tcslen(src);
+
+ // Don't copy more than nMaxChars
+ nLength = STORMLIB_MIN(nLength, nMaxChars);
+ memcpy(dest, src, (nLength * sizeof(TCHAR)));
+ dest[nLength] = 0;
+}
+
+void StringCatT(TCHAR * dest, const TCHAR * src, size_t nMaxChars)
+{
+ size_t nLength1 = _tcslen(dest);
+ size_t nLength2 = _tcslen(src);
+
+ // Don't copy more than nMaxChars
+ if(nLength1 < nMaxChars)
+ {
+ nLength2 = STORMLIB_MIN(nLength2, (nMaxChars - nLength1));
+ memcpy(dest + nLength1, src, (nLength2 * sizeof(TCHAR)));
+ dest[nLength1 + nLength2] = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Storm hashing functions
+
+#define STORM_BUFFER_SIZE 0x500
+
+#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0)
+
+static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine
+static bool bMpqCryptographyInitialized = false;
+
+void InitializeMpqCryptography()
+{
+ DWORD dwSeed = 0x00100001;
+ DWORD index1 = 0;
+ DWORD index2 = 0;
+ int i;
+
+ // Initialize the decryption buffer.
+ // Do nothing if already done.
+ if(bMpqCryptographyInitialized == false)
+ {
+ for(index1 = 0; index1 < 0x100; index1++)
+ {
+ for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
+ {
+ DWORD temp1, temp2;
+
+ dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
+ temp1 = (dwSeed & 0xFFFF) << 0x10;
+
+ dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
+ temp2 = (dwSeed & 0xFFFF);
+
+ StormBuffer[index2] = (temp1 | temp2);
+ }
+ }
+
+ // Also register both MD5 and SHA1 hash algorithms
+ register_hash(&md5_desc);
+ register_hash(&sha1_desc);
+
+ // Use LibTomMath as support math library for LibTomCrypt
+ ltc_mp = ltm_desc;
+
+ // Don't do that again
+ bMpqCryptographyInitialized = true;
+ }
+}
+
+DWORD HashString(const char * szFileName, DWORD dwHashType)
+{
+ LPBYTE pbKey = (BYTE *)szFileName;
+ DWORD dwSeed1 = 0x7FED7FED;
+ DWORD dwSeed2 = 0xEEEEEEEE;
+ DWORD ch;
+
+ while(*pbKey != 0)
+ {
+ // Convert the input character to uppercase
+ // Convert slash (0x2F) to backslash (0x5C)
+ ch = AsciiToUpperTable[*pbKey++];
+
+ dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
+ dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
+ }
+
+ return dwSeed1;
+}
+
+DWORD HashStringSlash(const char * szFileName, DWORD dwHashType)
+{
+ LPBYTE pbKey = (BYTE *)szFileName;
+ DWORD dwSeed1 = 0x7FED7FED;
+ DWORD dwSeed2 = 0xEEEEEEEE;
+ DWORD ch;
+
+ while(*pbKey != 0)
+ {
+ // Convert the input character to uppercase
+ // DON'T convert slash (0x2F) to backslash (0x5C)
+ ch = AsciiToUpperTable_Slash[*pbKey++];
+
+ dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
+ dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
+ }
+
+ return dwSeed1;
+}
+
+DWORD HashStringLower(const char * szFileName, DWORD dwHashType)
+{
+ LPBYTE pbKey = (BYTE *)szFileName;
+ DWORD dwSeed1 = 0x7FED7FED;
+ DWORD dwSeed2 = 0xEEEEEEEE;
+ DWORD ch;
+
+ while(*pbKey != 0)
+ {
+ // Convert the input character to lower
+ // DON'T convert slash (0x2F) to backslash (0x5C)
+ ch = AsciiToLowerTable[*pbKey++];
+
+ dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
+ dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
+ }
+
+ return dwSeed1;
+}
+
+//-----------------------------------------------------------------------------
+// Calculates the hash table size for a given amount of files
+
+DWORD GetHashTableSizeForFileCount(DWORD dwFileCount)
+{
+ DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN;
+
+ // For zero files, there is no hash table needed
+ if(dwFileCount == 0)
+ return 0;
+
+ // Round the hash table size up to the nearest power of two
+ // Don't allow the hash table size go over allowed maximum
+ while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount)
+ dwPowerOfTwo <<= 1;
+ return dwPowerOfTwo;
+}
+
+//-----------------------------------------------------------------------------
+// Calculates a Jenkin's Encrypting and decrypting MPQ file data
+
+ULONGLONG HashStringJenkins(const char * szFileName)
+{
+ LPBYTE pbFileName = (LPBYTE)szFileName;
+ char szNameBuff[0x108];
+ size_t nLength = 0;
+ unsigned int primary_hash = 1;
+ unsigned int secondary_hash = 2;
+
+ // Normalize the file name - convert to uppercase, and convert "/" to "\\".
+ if(pbFileName != NULL)
+ {
+ char * szNamePtr = szNameBuff;
+ char * szNameEnd = szNamePtr + sizeof(szNameBuff);
+
+ // Normalize the file name. Doesn't have to be zero terminated for hashing
+ while(szNamePtr < szNameEnd && pbFileName[0] != 0)
+ *szNamePtr++ = (char)AsciiToLowerTable[*pbFileName++];
+ nLength = szNamePtr - szNameBuff;
+ }
+
+ // Thanks Quantam for finding out what the algorithm is.
+ // I am really getting old for reversing large chunks of assembly
+ // that does hashing :-)
+ hashlittle2(szNameBuff, nLength, &secondary_hash, &primary_hash);
+
+ // Combine those 2 together
+ return ((ULONGLONG)primary_hash << 0x20) | (ULONGLONG)secondary_hash;
+}
+
+//-----------------------------------------------------------------------------
+// Default flags for (attributes) and (listfile)
+
+DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion)
+{
+ // Fixed for format 1.0
+ if(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/Decrypting MPQ data block
+
+void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
+{
+ LPDWORD DataBlock = (LPDWORD)pvDataBlock;
+ DWORD dwValue32;
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Round to DWORDs
+ dwLength >>= 2;
+
+ // Encrypt the data block at array of DWORDs
+ for(DWORD i = 0; i < dwLength; i++)
+ {
+ // Modify the second key
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+
+ dwValue32 = DataBlock[i];
+ DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2);
+
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
+ }
+}
+
+void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
+{
+ LPDWORD DataBlock = (LPDWORD)pvDataBlock;
+ DWORD dwValue32;
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Round to DWORDs
+ dwLength >>= 2;
+
+ // Decrypt the data block at array of DWORDs
+ for(DWORD i = 0; i < dwLength; i++)
+ {
+ // Modify the second key
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+
+ DataBlock[i] = DataBlock[i] ^ (dwKey1 + dwKey2);
+ dwValue32 = DataBlock[i];
+
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
+ }
+}
+
+/**
+ * Functions tries to get file decryption key. This comes from these facts
+ *
+ * - We know the decrypted value of the first DWORD in the encrypted data
+ * - We know the decrypted value of the second DWORD (at least aproximately)
+ * - There is only 256 variants of how the second key is modified
+ *
+ * The first iteration of dwKey1 and dwKey2 is this:
+ *
+ * dwKey2 = 0xEEEEEEEE + StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]
+ * dwDecrypted0 = DataBlock[0] ^ (dwKey1 + dwKey2);
+ *
+ * This means:
+ *
+ * (dwKey1 + dwKey2) = DataBlock[0] ^ dwDecrypted0;
+ *
+ */
+
+DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwDecrypted0)
+{
+ DWORD dwDecrypted1Max = dwSectorSize + dwDecrypted0;
+ DWORD dwKey1PlusKey2;
+ DWORD DataBlock[2];
+
+ // We must have at least 2 DWORDs there to be able to decrypt something
+ if(dwSectorSize < 0x08)
+ return 0;
+
+ // Get the value of the combined encryption key
+ dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
+
+ // Try all 256 combinations of dwKey1
+ for(DWORD i = 0; i < 0x100; i++)
+ {
+ DWORD dwSaveKey1;
+ DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Modify the second key and decrypt the first DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
+
+ // Did we obtain the same value like dwDecrypted0?
+ if(DataBlock[0] == dwDecrypted0)
+ {
+ // Save this key value. Increment by one because
+ // we are decrypting sector offset table
+ dwSaveKey1 = dwKey1 + 1;
+
+ // Rotate both keys
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
+
+ // Modify the second key again and decrypt the second DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
+
+ // Now compare the results
+ if(DataBlock[1] <= dwDecrypted1Max)
+ return dwSaveKey1;
+ }
+ }
+
+ // Key not found
+ return 0;
+}
+
+// Function tries to detect file encryption key based on expected file content
+// It is the same function like before, except that we know the value of the second DWORD
+DWORD DetectFileKeyByKnownContent(void * pvEncryptedData, DWORD dwDecrypted0, DWORD dwDecrypted1)
+{
+ LPDWORD EncryptedData = (LPDWORD)pvEncryptedData;
+ DWORD dwKey1PlusKey2;
+ DWORD DataBlock[2];
+
+ // Get the value of the combined encryption key
+ dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
+
+ // Try all 256 combinations of dwKey1
+ for(DWORD i = 0; i < 0x100; i++)
+ {
+ DWORD dwSaveKey1;
+ DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
+ DWORD dwKey2 = 0xEEEEEEEE;
+
+ // Modify the second key and decrypt the first DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
+
+ // Did we obtain the same value like dwDecrypted0?
+ if(DataBlock[0] == dwDecrypted0)
+ {
+ // Save this key value
+ dwSaveKey1 = dwKey1;
+
+ // Rotate both keys
+ dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
+ dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
+
+ // Modify the second key again and decrypt the second DWORD
+ dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
+ DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
+
+ // Now compare the results
+ if(DataBlock[1] == dwDecrypted1)
+ return dwSaveKey1;
+ }
+ }
+
+ // Key not found
+ return 0;
+}
+
+DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize)
+{
+ DWORD dwFileKey;
+
+ // Try to break the file encryption key as if it was a WAVE file
+ if(dwSectorSize >= 0x0C)
+ {
+ dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x46464952, dwFileSize - 8);
+ if(dwFileKey != 0)
+ return dwFileKey;
+ }
+
+ // Try to break the encryption key as if it was an EXE file
+ if(dwSectorSize > 0x40)
+ {
+ dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x00905A4D, 0x00000003);
+ if(dwFileKey != 0)
+ return dwFileKey;
+ }
+
+ // Try to break the encryption key as if it was a XML file
+ if(dwSectorSize > 0x04)
+ {
+ dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x6D783F3C, 0x6576206C);
+ if(dwFileKey != 0)
+ return dwFileKey;
+ }
+
+ // Not detected, sorry
+ return 0;
+}
+
+DWORD DecryptFileKey(
+ const char * szFileName,
+ ULONGLONG MpqPos,
+ DWORD dwFileSize,
+ DWORD dwFlags)
+{
+ DWORD dwFileKey;
+ DWORD dwMpqPos = (DWORD)MpqPos;
+
+ // File key is calculated from plain name
+ szFileName = GetPlainFileName(szFileName);
+ dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY);
+
+ // Fix the key, if needed
+ if(dwFlags & MPQ_FILE_FIX_KEY)
+ dwFileKey = (dwFileKey + dwMpqPos) ^ dwFileSize;
+
+ // Return the key
+ return dwFileKey;
+}
+
+//-----------------------------------------------------------------------------
+// Handle validation functions
+
+TMPQArchive * IsValidMpqHandle(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == ID_MPQ) ? ha : NULL;
+}
+
+TMPQFile * IsValidFileHandle(HANDLE hFile)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+
+ // Must not be NULL
+ if(hf != NULL && hf->dwMagic == ID_MPQ_FILE)
+ {
+ // Local file handle?
+ if(hf->pStream != NULL)
+ return hf;
+
+ // Also verify the MPQ handle within the file handle
+ if(IsValidMpqHandle(hf->ha))
+ return hf;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Hash table and block table manipulation
+
+// Attempts to search a free hash entry, or an entry whose names and locale matches
+TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale)
+{
+ TMPQHash * pDeletedEntry = NULL; // If a deleted entry was found in the continuous hash range
+ TMPQHash * pFreeEntry = NULL; // If a free entry was found in the continuous hash range
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
+ DWORD dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
+
+ // Search the hash table and return the found entries in the following priority:
+ // 1) <MATCHING_ENTRY>
+ // 2) <DELETED-ENTRY>
+ // 3) <FREE-ENTRY>
+ // 4) NULL
+ for(;;)
+ {
+ TMPQHash * pHash = ha->pHashTable + dwIndex;
+
+ // If we found a matching entry, return that one
+ if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == lcLocale)
+ return pHash;
+
+ // If we found a deleted entry, remember it but keep searching
+ if(pHash->dwBlockIndex == HASH_ENTRY_DELETED && pDeletedEntry == NULL)
+ pDeletedEntry = pHash;
+
+ // If we found a free entry, we need to stop searching
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ {
+ pFreeEntry = pHash;
+ break;
+ }
+
+ // Move to the next hash entry.
+ // If we reached the starting entry, it's failure.
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ break;
+ }
+
+ // If we found a deleted entry, return that one preferentially
+ return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry;
+}
+
+// Retrieves the first hash entry for the given file.
+// Every locale version of a file has its own hash entry
+TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName)
+{
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
+ DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX);
+ DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A);
+ DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B);
+ DWORD dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
+
+ // Search the hash table
+ for(;;)
+ {
+ TMPQHash * pHash = ha->pHashTable + dwIndex;
+
+ // If the entry matches, we found it.
+ if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize)
+ return pHash;
+
+ // If that hash entry is a free entry, it means we haven't found the file
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ return NULL;
+
+ // Move to the next hash entry. Stop searching
+ // if we got reached the original hash entry
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ return NULL;
+ }
+}
+
+TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
+{
+ DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
+ DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
+ DWORD dwName1 = pHash->dwName1;
+ DWORD dwName2 = pHash->dwName2;
+ DWORD dwIndex = (DWORD)(pHash - ha->pHashTable);
+
+ // Now go for any next entry that follows the pHash,
+ // until either free hash entry was found, or the start entry was reached
+ for(;;)
+ {
+ // Move to the next hash entry. Stop searching
+ // if we got reached the original hash entry
+ dwIndex = (dwIndex + 1) & dwHashIndexMask;
+ if(dwIndex == dwStartIndex)
+ return NULL;
+ pHash = ha->pHashTable + dwIndex;
+
+ // If the entry matches, we found it.
+ if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex < ha->dwFileTableSize)
+ return pHash;
+
+ // If that hash entry is a free entry, it means we haven't found the file
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ return NULL;
+ }
+}
+
+// Allocates an entry in the hash table
+TMPQHash * AllocateHashEntry(
+ TMPQArchive * ha,
+ TFileEntry * pFileEntry,
+ LCID lcLocale)
+{
+ TMPQHash * pHash;
+ DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX);
+ DWORD dwName1 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_A);
+ DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B);
+
+ // Attempt to find a free hash entry
+ pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, lcLocale);
+ if(pHash != NULL)
+ {
+ // Fill the free hash entry
+ pHash->dwName1 = dwName1;
+ pHash->dwName2 = dwName2;
+ pHash->lcLocale = (USHORT)lcLocale;
+ pHash->wPlatform = 0;
+ pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
+ }
+
+ return pHash;
+}
+
+// Finds a free space in the MPQ where to store next data
+// The free space begins beyond the file that is stored at the fuhrtest
+// position in the MPQ. (listfile), (attributes) and (signature) are ignored,
+// unless the MPQ is being flushed.
+ULONGLONG FindFreeMpqSpace(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize;
+ DWORD dwChunkCount;
+
+ // Parse the entire block table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Only take existing files with nonzero size
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0))
+ {
+ // If we are not saving MPQ tables, ignore internal MPQ files
+ if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0 && IsInternalMpqFileName(pFileEntry->szFileName))
+ continue;
+
+ // If the end of the file is bigger than current MPQ table pos, update it
+ if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos)
+ {
+ // Get the end of the file data
+ FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize;
+
+ // Add the MD5 chunks, if present
+ if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0)
+ {
+ dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1;
+ FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE;
+ }
+ }
+ }
+ }
+
+ // Give the free space position to the caller
+ return FreeSpacePos;
+}
+
+//-----------------------------------------------------------------------------
+// Common functions - MPQ File
+
+TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry)
+{
+ TMPQFile * hf;
+
+ // Allocate space for TMPQFile
+ hf = STORM_ALLOC(TMPQFile, 1);
+ if(hf != NULL)
+ {
+ // Fill the file structure
+ memset(hf, 0, sizeof(TMPQFile));
+ hf->dwMagic = ID_MPQ_FILE;
+ hf->pStream = NULL;
+ hf->ha = ha;
+
+ // If the called entered a file entry, we also copy informations from the file entry
+ if(ha != NULL && pFileEntry != NULL)
+ {
+ // Set the raw position and MPQ position
+ hf->RawFilePos = FileOffsetFromMpqOffset(ha, pFileEntry->ByteOffset);
+ hf->MpqFilePos = pFileEntry->ByteOffset;
+
+ // Set the data size
+ hf->dwDataSize = pFileEntry->dwFileSize;
+ hf->pFileEntry = pFileEntry;
+ }
+ }
+
+ return hf;
+}
+
+TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize)
+{
+ ULONGLONG FreeMpqSpace;
+ ULONGLONG TempPos;
+ TMPQFile * hf;
+
+ // We need to find the position in the MPQ where we save the file data
+ FreeMpqSpace = FindFreeMpqSpace(ha);
+
+ // When format V1, the size of the archive cannot exceed 4 GB
+ if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ {
+ TempPos = FreeMpqSpace +
+ dwFileSize +
+ (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)) +
+ (ha->dwFileTableSize * sizeof(TMPQBlock));
+ if((TempPos >> 32) != 0)
+ {
+ SetLastError(ERROR_DISK_FULL);
+ return NULL;
+ }
+ }
+
+ // Allocate the file handle
+ hf = CreateFileHandle(ha, NULL);
+ if(hf == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return NULL;
+ }
+
+ // We need to find the position in the MPQ where we save the file data
+ hf->MpqFilePos = FreeMpqSpace;
+ hf->bIsWriteHandle = true;
+ return hf;
+}
+
+// Loads a table from MPQ.
+// Can be used for hash table, block table, sector offset table or sector checksum table
+void * LoadMpqTable(
+ TMPQArchive * ha,
+ ULONGLONG ByteOffset,
+ DWORD dwCompressedSize,
+ DWORD dwTableSize,
+ DWORD dwKey,
+ bool * pbTableIsCut)
+{
+ ULONGLONG FileSize = 0;
+ LPBYTE pbCompressed = NULL;
+ LPBYTE pbMpqTable;
+ LPBYTE pbToRead;
+ DWORD dwBytesToRead = dwCompressedSize;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate the MPQ table
+ pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize);
+ if(pbMpqTable != NULL)
+ {
+ // Check if the MPQ table is encrypted
+ if(dwCompressedSize < dwTableSize)
+ {
+ // Allocate temporary buffer for holding compressed data
+ pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
+ if(pbCompressed == NULL)
+ {
+ STORM_FREE(pbMpqTable);
+ return NULL;
+ }
+ }
+
+ // Get the file offset from which we will read the table
+ // Note: According to Storm.dll from Warcraft III (version 2002),
+ // if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
+ // and the table is loaded from the current file offset
+ if(ByteOffset == SFILE_INVALID_POS)
+ FileStream_GetPos(ha->pStream, &ByteOffset);
+
+ // On archives v 1.0, hash table and block table can go beyond EOF.
+ // Storm.dll reads as much as possible, then fills the missing part with zeros.
+ // Abused by Spazzler map protector which sets hash table size to 0x00100000
+ if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ {
+ // Cut the table size
+ FileStream_GetSize(ha->pStream, &FileSize);
+ if((ByteOffset + dwBytesToRead) > FileSize)
+ {
+ // Fill the extra data with zeros
+ dwBytesToRead = (DWORD)(FileSize - ByteOffset);
+ memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
+
+ // Give the caller information that the table was cut
+ if(pbTableIsCut != NULL)
+ pbTableIsCut[0] = true;
+ }
+ }
+
+ // If everything succeeded, read the raw table form the MPQ
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
+ {
+ // First of all, decrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
+ }
+
+ // If the table is compressed, decompress it
+ if(dwCompressedSize < dwTableSize)
+ {
+ int cbOutBuffer = (int)dwTableSize;
+ int cbInBuffer = (int)dwCompressedSize;
+
+ if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer))
+ nError = GetLastError();
+ }
+
+ // Make sure that the table is properly byte-swapped
+ BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ // If read failed, free the table and return
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pbMpqTable);
+ pbMpqTable = NULL;
+ }
+
+ // Free the compression buffer, if any
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+ }
+
+ // Return the MPQ table
+ return pbMpqTable;
+}
+
+unsigned char * AllocateMd5Buffer(
+ DWORD dwRawDataSize,
+ DWORD dwChunkSize,
+ LPDWORD pcbMd5Size)
+{
+ unsigned char * md5_array;
+ DWORD cbMd5Size;
+
+ // Sanity check
+ assert(dwRawDataSize != 0);
+ assert(dwChunkSize != 0);
+
+ // Calculate how many MD5's we will calculate
+ cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE;
+
+ // Allocate space for array or MD5s
+ md5_array = STORM_ALLOC(BYTE, cbMd5Size);
+
+ // Give the size of the MD5 array
+ if(pcbMd5Size != NULL)
+ *pcbMd5Size = cbMd5Size;
+ return md5_array;
+}
+
+// Allocates sector buffer and sector offset table
+int AllocateSectorBuffer(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+
+ // Caller of AllocateSectorBuffer must ensure these
+ assert(hf->pbFileSector == NULL);
+ assert(hf->pFileEntry != NULL);
+ 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);
+ hf->dwSectorOffs = SFILE_INVALID_POS;
+
+ // Return result
+ return (hf->pbFileSector != NULL) ? (int)ERROR_SUCCESS : (int)ERROR_NOT_ENOUGH_MEMORY;
+}
+
+// Allocates sector offset table
+int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile)
+{
+ TMPQArchive * ha = hf->ha;
+ DWORD dwLength = sizeof(TPatchInfo);
+
+ // The following conditions must be true
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
+ assert(hf->pPatchInfo == NULL);
+
+__AllocateAndLoadPatchInfo:
+
+ // Allocate space for patch header. Start with default size,
+ // and if its size if bigger, then we reload them
+ hf->pPatchInfo = STORM_ALLOC(TPatchInfo, 1);
+ if(hf->pPatchInfo == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Do we have to load the patch header from the file ?
+ if(bLoadFromFile)
+ {
+ // Load the patch header
+ if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength))
+ {
+ // Free the patch info
+ STORM_FREE(hf->pPatchInfo);
+ hf->pPatchInfo = NULL;
+ return GetLastError();
+ }
+
+ // Perform necessary swapping
+ hf->pPatchInfo->dwLength = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwLength);
+ hf->pPatchInfo->dwFlags = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwFlags);
+ hf->pPatchInfo->dwDataSize = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwDataSize);
+
+ // Verify the size of the patch header
+ // 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;
+ }
+
+ // Patch file data size according to the patch header
+ hf->dwDataSize = hf->pPatchInfo->dwDataSize;
+ }
+ else
+ {
+ memset(hf->pPatchInfo, 0, dwLength);
+ }
+
+ // Save the final length to the patch header
+ hf->pPatchInfo->dwLength = dwLength;
+ hf->pPatchInfo->dwFlags = 0x80000000;
+ return ERROR_SUCCESS;
+}
+
+// Allocates sector offset table
+int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ DWORD dwSectorOffsLen;
+ bool bSectorOffsetTableCorrupt = false;
+
+ // Caller of AllocateSectorOffsets must ensure these
+ assert(hf->SectorOffsets == NULL);
+ assert(hf->pFileEntry != NULL);
+ assert(hf->dwDataSize != 0);
+ assert(hf->ha != NULL);
+
+ // If the file is stored as single unit, just set number of sectors to 1
+ if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
+ {
+ hf->dwSectorCount = 1;
+ return ERROR_SUCCESS;
+ }
+
+ // 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
+ dwSectorOffsLen = (hf->dwSectorCount + 1) * sizeof(DWORD);
+
+ // If MPQ_FILE_SECTOR_CRC flag is set, there will either be extra DWORD
+ // or an array of MD5's. Either way, we read at least 4 bytes more
+ // in order to save additional read from the file.
+ if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
+ dwSectorOffsLen += sizeof(DWORD);
+
+ // Only allocate and load the table if the file is compressed
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ __LoadSectorOffsets:
+
+ // Allocate the sector offset table
+ hf->SectorOffsets = STORM_ALLOC(DWORD, (dwSectorOffsLen / sizeof(DWORD)));
+ if(hf->SectorOffsets == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Only read from the file if we are supposed to do so
+ if(bLoadFromFile)
+ {
+ ULONGLONG RawFilePos = hf->RawFilePos;
+
+ // Append the length of the patch info, if any
+ if(hf->pPatchInfo != NULL)
+ {
+ if((RawFilePos + hf->pPatchInfo->dwLength) < RawFilePos)
+ return ERROR_FILE_CORRUPT;
+ RawFilePos += hf->pPatchInfo->dwLength;
+ }
+
+ // Load the sector offsets from the file
+ if(!FileStream_Read(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
+ {
+ // Free the sector offsets
+ STORM_FREE(hf->SectorOffsets);
+ hf->SectorOffsets = NULL;
+ return GetLastError();
+ }
+
+ // Swap the sector positions
+ BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
+
+ // Decrypt loaded sector positions if necessary
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ // If we don't know the file key, try to find it.
+ if(hf->dwFileKey == 0)
+ {
+ hf->dwFileKey = DetectFileKeyBySectorSize(hf->SectorOffsets, ha->dwSectorSize, dwSectorOffsLen);
+ if(hf->dwFileKey == 0)
+ {
+ STORM_FREE(hf->SectorOffsets);
+ hf->SectorOffsets = NULL;
+ return ERROR_UNKNOWN_FILE_KEY;
+ }
+ }
+
+ // Decrypt sector positions
+ DecryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
+ }
+
+ //
+ // Validate the sector offset table
+ //
+ // Note: Some MPQ protectors put the actual file data before the sector offset table.
+ // In this case, the sector offsets are negative (> 0x80000000).
+ //
+
+ for(DWORD i = 0; i < hf->dwSectorCount; i++)
+ {
+ DWORD dwSectorOffset1 = hf->SectorOffsets[i+1];
+ DWORD dwSectorOffset0 = hf->SectorOffsets[i];
+
+ // Every following sector offset must be bigger than the previous one
+ if(dwSectorOffset1 <= dwSectorOffset0)
+ {
+ bSectorOffsetTableCorrupt = true;
+ break;
+ }
+
+ // The sector size must not be bigger than compressed file size
+ // Edit: Yes, but apparently, in original Storm.dll, the compressed
+ // size is not checked anywhere. However, we need to do this check
+ // in order to sector offset table malformed by MPQ protectors
+ if((dwSectorOffset1 - dwSectorOffset0) > ha->dwSectorSize)
+ {
+ bSectorOffsetTableCorrupt = true;
+ break;
+ }
+ }
+
+ // If data corruption detected, free the sector offset table
+ if(bSectorOffsetTableCorrupt)
+ {
+ STORM_FREE(hf->SectorOffsets);
+ hf->SectorOffsets = NULL;
+ return ERROR_FILE_CORRUPT;
+ }
+
+ //
+ // 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.
+ //
+ // These extra values are, however, include in the dwCmpSize in the file
+ // table. We cannot ignore them, because compacting archive would fail
+ //
+
+ if(hf->SectorOffsets[0] > dwSectorOffsLen)
+ {
+ // MPQ protectors put some ridiculous values there. We must limit the extra bytes
+ if(hf->SectorOffsets[0] > (dwSectorOffsLen + 0x400))
+ return ERROR_FILE_CORRUPT;
+
+ // Free the old sector offset table
+ dwSectorOffsLen = hf->SectorOffsets[0];
+ STORM_FREE(hf->SectorOffsets);
+ goto __LoadSectorOffsets;
+ }
+ }
+ else
+ {
+ memset(hf->SectorOffsets, 0, dwSectorOffsLen);
+ hf->SectorOffsets[0] = dwSectorOffsLen;
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ ULONGLONG RawFilePos;
+ DWORD dwCompressedSize = 0;
+ DWORD dwExpectedSize;
+ DWORD dwCrcOffset; // Offset of the CRC table, relative to file offset in the MPQ
+ DWORD dwCrcSize;
+
+ // Caller of AllocateSectorChecksums must ensure these
+ assert(hf->SectorChksums == NULL);
+ assert(hf->SectorOffsets != NULL);
+ assert(hf->pFileEntry != NULL);
+ assert(hf->ha != NULL);
+
+ // Single unit files don't have sector checksums
+ if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
+ return ERROR_SUCCESS;
+
+ // Caller must ensure that we are only called when we have sector checksums
+ assert(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
+
+ //
+ // Older MPQs store an array of CRC32's after
+ // the raw file data in the MPQ.
+ //
+ // In newer MPQs, the (since Cataclysm BETA) the (attributes) file
+ // contains additional 32-bit values beyond the sector table.
+ // Their number depends on size of the (attributes), but their
+ // meaning is unknown. They are usually zeroed in retail game files,
+ // but contain some sort of checksum in BETA MPQs
+ //
+
+ // Does the size of the file table match with the CRC32-based checksums?
+ dwExpectedSize = (hf->dwSectorCount + 2) * sizeof(DWORD);
+ if(hf->SectorOffsets[0] != 0 && hf->SectorOffsets[0] == dwExpectedSize)
+ {
+ // If we are not loading from the MPQ file, we just allocate the sector table
+ // In that case, do not check any sizes
+ if(bLoadFromFile == false)
+ {
+ hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount);
+ if(hf->SectorChksums == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the checksum table with zeros
+ memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD));
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ // Is there valid size of the sector checksums?
+ if(hf->SectorOffsets[hf->dwSectorCount + 1] >= hf->SectorOffsets[hf->dwSectorCount])
+ dwCompressedSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
+
+ // Ignore cases when the length is too small or too big.
+ if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize)
+ return ERROR_SUCCESS;
+
+ // Calculate offset of the CRC table
+ dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
+ dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount];
+ RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
+
+ // Now read the table from the MPQ
+ hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, dwCompressedSize, dwCrcSize, 0, NULL);
+ if(hf->SectorChksums == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // If the size doesn't match, we ignore sector checksums
+// assert(false);
+ return ERROR_SUCCESS;
+}
+
+int WritePatchInfo(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ TPatchInfo * pPatchInfo = hf->pPatchInfo;
+
+ // The caller must make sure that this function is only called
+ // when the following is true.
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
+ assert(pPatchInfo != NULL);
+
+ BSWAP_ARRAY32_UNSIGNED(pPatchInfo, 3 * sizeof(DWORD));
+ if(!FileStream_Write(ha->pStream, &hf->RawFilePos, pPatchInfo, sizeof(TPatchInfo)))
+ return GetLastError();
+
+ return ERROR_SUCCESS;
+}
+
+int WriteSectorOffsets(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ ULONGLONG RawFilePos = hf->RawFilePos;
+ DWORD dwSectorOffsLen;
+
+ // The caller must make sure that this function is only called
+ // when the following is true.
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
+ assert(hf->SectorOffsets != NULL);
+ dwSectorOffsLen = hf->SectorOffsets[0];
+
+ // If file is encrypted, sector positions are also encrypted
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ EncryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
+ BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
+
+ // Adjust sector offset table position, if we also have patch info
+ if(hf->pPatchInfo != NULL)
+ RawFilePos += hf->pPatchInfo->dwLength;
+
+ // Write sector offsets to the archive
+ if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
+ return GetLastError();
+
+ // Not necessary, as the sector checksums
+ // are going to be freed when this is done.
+// BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
+ return ERROR_SUCCESS;
+}
+
+
+int WriteSectorChecksums(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ ULONGLONG RawFilePos;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ LPBYTE pbCompressed;
+ DWORD dwCompressedSize = 0;
+ DWORD dwCrcSize;
+ int nOutSize;
+ int nError = ERROR_SUCCESS;
+
+ // The caller must make sure that this function is only called
+ // when the following is true.
+ assert(hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
+ assert(hf->SectorOffsets != NULL);
+ assert(hf->SectorChksums != NULL);
+
+ // If the MPQ has MD5 of each raw data chunk,
+ // we leave sector offsets empty
+ if(ha->pHeader->dwRawChunkSize != 0)
+ {
+ hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount];
+ return ERROR_SUCCESS;
+ }
+
+ // Calculate size of the checksum array
+ dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
+
+ // Allocate buffer for compressed sector CRCs.
+ pbCompressed = STORM_ALLOC(BYTE, dwCrcSize);
+ if(pbCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Perform the compression
+ BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
+
+ nOutSize = (int)dwCrcSize;
+ SCompCompress(pbCompressed, &nOutSize, hf->SectorChksums, (int)dwCrcSize, MPQ_COMPRESSION_ZLIB, 0, 0);
+ dwCompressedSize = (DWORD)nOutSize;
+
+ // Write the sector CRCs to the archive
+ RawFilePos = hf->RawFilePos + hf->SectorOffsets[hf->dwSectorCount];
+ if(hf->pPatchInfo != NULL)
+ RawFilePos += hf->pPatchInfo->dwLength;
+ if(!FileStream_Write(ha->pStream, &RawFilePos, pbCompressed, dwCompressedSize))
+ nError = GetLastError();
+
+ // Not necessary, as the sector checksums
+ // are going to be freed when this is done.
+// BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
+
+ // Store the sector CRCs
+ hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount] + dwCompressedSize;
+ pFileEntry->dwCmpSize += dwCompressedSize;
+ STORM_FREE(pbCompressed);
+ return nError;
+}
+
+int WriteMemDataMD5(
+ TFileStream * pStream,
+ ULONGLONG RawDataOffs,
+ void * pvRawData,
+ DWORD dwRawDataSize,
+ DWORD dwChunkSize,
+ LPDWORD pcbTotalSize)
+{
+ unsigned char * md5_array;
+ unsigned char * md5;
+ LPBYTE pbRawData = (LPBYTE)pvRawData;
+ DWORD dwBytesRemaining = dwRawDataSize;
+ DWORD dwMd5ArraySize = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate buffer for array of MD5
+ md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
+ if(md5_array == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // For every file chunk, calculate MD5
+ while(dwBytesRemaining != 0)
+ {
+ // Get the remaining number of bytes to read
+ dwChunkSize = STORMLIB_MIN(dwBytesRemaining, dwChunkSize);
+
+ // Calculate MD5
+ CalculateDataBlockHash(pbRawData, dwChunkSize, md5);
+ md5 += MD5_DIGEST_SIZE;
+
+ // Move offset and size
+ dwBytesRemaining -= dwChunkSize;
+ pbRawData += dwChunkSize;
+ }
+
+ // Write the array od MD5's to the file
+ RawDataOffs += dwRawDataSize;
+ if(!FileStream_Write(pStream, &RawDataOffs, md5_array, dwMd5ArraySize))
+ nError = GetLastError();
+
+ // Give the caller the size of the MD5 array
+ if(pcbTotalSize != NULL)
+ *pcbTotalSize = dwRawDataSize + dwMd5ArraySize;
+
+ // Free buffers and exit
+ STORM_FREE(md5_array);
+ return nError;
+}
+
+
+// Writes the MD5 for each chunk of the raw file data
+int WriteMpqDataMD5(
+ TFileStream * pStream,
+ ULONGLONG RawDataOffs,
+ DWORD dwRawDataSize,
+ DWORD dwChunkSize)
+{
+ unsigned char * md5_array;
+ unsigned char * md5;
+ LPBYTE pbFileChunk;
+ DWORD dwMd5ArraySize = 0;
+ DWORD dwToRead = dwRawDataSize;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate buffer for array of MD5
+ md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
+ if(md5_array == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate space for file chunk
+ pbFileChunk = STORM_ALLOC(BYTE, dwChunkSize);
+ if(pbFileChunk == NULL)
+ {
+ STORM_FREE(md5_array);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // For every file chunk, calculate MD5
+ while(dwRawDataSize != 0)
+ {
+ // Get the remaining number of bytes to read
+ dwToRead = STORMLIB_MIN(dwRawDataSize, dwChunkSize);
+
+ // Read the chunk
+ if(!FileStream_Read(pStream, &RawDataOffs, pbFileChunk, dwToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Calculate MD5
+ CalculateDataBlockHash(pbFileChunk, dwToRead, md5);
+ md5 += MD5_DIGEST_SIZE;
+
+ // Move offset and size
+ RawDataOffs += dwToRead;
+ dwRawDataSize -= dwToRead;
+ }
+
+ // Write the array od MD5's to the file
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize))
+ nError = GetLastError();
+ }
+
+ // Free buffers and exit
+ STORM_FREE(pbFileChunk);
+ STORM_FREE(md5_array);
+ return nError;
+}
+
+// Frees the structure for MPQ file
+void FreeFileHandle(TMPQFile *& hf)
+{
+ if(hf != NULL)
+ {
+ // If we have patch file attached to this one, free it first
+ if(hf->hfPatch != NULL)
+ FreeFileHandle(hf->hfPatch);
+
+ // Then free all buffers allocated in the file structure
+ if(hf->pbFileData != NULL)
+ STORM_FREE(hf->pbFileData);
+ if(hf->pPatchInfo != NULL)
+ STORM_FREE(hf->pPatchInfo);
+ if(hf->SectorOffsets != NULL)
+ STORM_FREE(hf->SectorOffsets);
+ if(hf->SectorChksums != NULL)
+ STORM_FREE(hf->SectorChksums);
+ if(hf->pbFileSector != NULL)
+ STORM_FREE(hf->pbFileSector);
+ if(hf->pStream != NULL)
+ FileStream_Close(hf->pStream);
+ STORM_FREE(hf);
+ hf = NULL;
+ }
+}
+
+// Frees the MPQ archive
+void FreeArchiveHandle(TMPQArchive *& ha)
+{
+ if(ha != NULL)
+ {
+ // First of all, free the patch archive, if any
+ if(ha->haPatch != NULL)
+ FreeArchiveHandle(ha->haPatch);
+
+ // Free the patch prefix, if any
+ if(ha->pPatchPrefix != NULL)
+ STORM_FREE(ha->pPatchPrefix);
+
+ // Close the file stream
+ FileStream_Close(ha->pStream);
+ ha->pStream = NULL;
+
+ // Free the file names from the file table
+ if(ha->pFileTable != NULL)
+ {
+ for(DWORD i = 0; i < ha->dwFileTableSize; i++)
+ {
+ if(ha->pFileTable[i].szFileName != NULL)
+ STORM_FREE(ha->pFileTable[i].szFileName);
+ ha->pFileTable[i].szFileName = NULL;
+ }
+
+ // Then free all buffers allocated in the archive structure
+ STORM_FREE(ha->pFileTable);
+ }
+
+ if(ha->pHashTable != NULL)
+ STORM_FREE(ha->pHashTable);
+ if(ha->pHetTable != NULL)
+ FreeHetTable(ha->pHetTable);
+ STORM_FREE(ha);
+ ha = NULL;
+ }
+}
+
+bool IsInternalMpqFileName(const char * szFileName)
+{
+ if(szFileName != NULL && szFileName[0] == '(')
+ {
+ if(!_stricmp(szFileName, LISTFILE_NAME) ||
+ !_stricmp(szFileName, ATTRIBUTES_NAME) ||
+ !_stricmp(szFileName, SIGNATURE_NAME))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Verifies if the file name is a pseudo-name
+bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex)
+{
+ DWORD dwFileIndex = 0;
+
+ if(szFileName != NULL)
+ {
+ // Must be "File########.ext"
+ if(!_strnicmp(szFileName, "File", 4))
+ {
+ // Check 8 digits
+ for(int i = 4; i < 4+8; i++)
+ {
+ if(szFileName[i] < '0' || szFileName[i] > '9')
+ return false;
+ dwFileIndex = (dwFileIndex * 10) + (szFileName[i] - '0');
+ }
+
+ // An extension must follow
+ if(szFileName[12] == '.')
+ {
+ if(pdwFileIndex != NULL)
+ *pdwFileIndex = dwFileIndex;
+ return true;
+ }
+ }
+ }
+
+ // Not a pseudo-name
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Functions calculating and verifying the MD5 signature
+
+bool IsValidMD5(LPBYTE pbMd5)
+{
+ LPDWORD Md5 = (LPDWORD)pbMd5;
+
+ return (Md5[0] | Md5[1] | Md5[2] | Md5[3]) ? true : false;
+}
+
+bool IsValidSignature(LPBYTE pbSignature)
+{
+ LPDWORD Signature = (LPDWORD)pbSignature;
+ DWORD SigValid = 0;
+
+ for(int i = 0; i < MPQ_WEAK_SIGNATURE_SIZE / sizeof(DWORD); i++)
+ SigValid |= Signature[i];
+
+ return (SigValid != 0) ? true : false;
+}
+
+
+bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
+{
+ hash_state md5_state;
+ BYTE md5_digest[MD5_DIGEST_SIZE];
+
+ // Don't verify the block if the MD5 is not valid.
+ if(!IsValidMD5(expected_md5))
+ return true;
+
+ // Calculate the MD5 of the data block
+ md5_init(&md5_state);
+ md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
+ md5_done(&md5_state, md5_digest);
+
+ // Does the MD5's match?
+ return (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
+}
+
+void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
+{
+ hash_state md5_state;
+
+ md5_init(&md5_state);
+ md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
+ md5_done(&md5_state, md5_hash);
+}
+
+
+//-----------------------------------------------------------------------------
+// Swapping functions
+
+#ifndef PLATFORM_LITTLE_ENDIAN
+
+//
+// Note that those functions are implemented for Mac operating system,
+// as this is the only supported platform that uses big endian.
+//
+
+// Swaps a signed 16-bit integer
+int16_t SwapInt16(uint16_t data)
+{
+ return (int16_t)CFSwapInt16(data);
+}
+
+// Swaps an unsigned 16-bit integer
+uint16_t SwapUInt16(uint16_t data)
+{
+ return CFSwapInt16(data);
+}
+
+// Swaps signed 32-bit integer
+int32_t SwapInt32(uint32_t data)
+{
+ return (int32_t)CFSwapInt32(data);
+}
+
+// Swaps an unsigned 32-bit integer
+uint32_t SwapUInt32(uint32_t data)
+{
+ return CFSwapInt32(data);
+}
+
+// Swaps signed 64-bit integer
+int64_t SwapInt64(int64_t data)
+{
+ return (int64_t)CFSwapInt64(data);
+}
+
+// Swaps an unsigned 64-bit integer
+uint64_t SwapUInt64(uint64_t data)
+{
+ return CFSwapInt64(data);
+}
+
+// Swaps array of unsigned 16-bit integers
+void ConvertUInt16Buffer(void * ptr, size_t length)
+{
+ uint16_t * buffer = (uint16_t *)ptr;
+ uint32_t nElements = (uint32_t)(length / sizeof(uint16_t));
+
+ while(nElements-- > 0)
+ {
+ *buffer = SwapUInt16(*buffer);
+ buffer++;
+ }
+}
+
+// Swaps array of unsigned 32-bit integers
+void ConvertUInt32Buffer(void * ptr, size_t length)
+{
+ uint32_t * buffer = (uint32_t *)ptr;
+ uint32_t nElements = (uint32_t)(length / sizeof(uint32_t));
+
+ while(nElements-- > 0)
+ {
+ *buffer = SwapUInt32(*buffer);
+ buffer++;
+ }
+}
+
+// Swaps array of unsigned 64-bit integers
+void ConvertUInt64Buffer(void * ptr, size_t length)
+{
+ uint64_t * buffer = (uint64_t *)ptr;
+ uint32_t nElements = (uint32_t)(length / sizeof(uint64_t));
+
+ while(nElements-- > 0)
+ {
+ *buffer = SwapUInt64(*buffer);
+ buffer++;
+ }
+}
+
+// Swaps the TMPQHeader structure
+void ConvertTMPQHeader(void *header, uint16_t version)
+{
+ TMPQHeader * theHeader = (TMPQHeader *)header;
+
+ // Swap header part version 1
+ if(version == MPQ_FORMAT_VERSION_1)
+ {
+ theHeader->dwID = SwapUInt32(theHeader->dwID);
+ theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize);
+ theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize);
+ theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion);
+ theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize);
+ theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos);
+ theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos);
+ theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize);
+ theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize);
+ }
+
+ if(version == MPQ_FORMAT_VERSION_2)
+ {
+ theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
+ theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
+ theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
+ }
+
+ if(version == MPQ_FORMAT_VERSION_3)
+ {
+ theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
+ theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
+ theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
+ }
+
+ if(version == MPQ_FORMAT_VERSION_4)
+ {
+ theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64);
+ theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64);
+ theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64);
+ theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64);
+ theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64);
+ }
+}
+
+#endif // PLATFORM_LITTLE_ENDIAN
diff --git a/src/SFileAddFile.cpp b/src/SFileAddFile.cpp index b70eeb1..0b70d35 100644 --- a/src/SFileAddFile.cpp +++ b/src/SFileAddFile.cpp @@ -1,1313 +1,1313 @@ -/*****************************************************************************/ -/* SFileAddFile.cpp Copyright (c) Ladislav Zezula 2010 */ -/*---------------------------------------------------------------------------*/ -/* MPQ Editing functions */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 27.03.10 1.00 Lad Splitted from SFileCreateArchiveEx.cpp */ -/* 21.04.13 1.01 Dea AddFile callback now part of TMPQArchive */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Local variables - -// Mask for lossy compressions -#define MPQ_LOSSY_COMPRESSION_MASK (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN) - -// Data compression for SFileAddFile -// Kept here for compatibility with code that was created with StormLib version < 6.50 -static DWORD DefaultDataCompression = MPQ_COMPRESSION_PKWARE; - -//----------------------------------------------------------------------------- -// WAVE verification - -#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; - -static bool IsWaveFile_16BitsPerAdpcmSample( - LPBYTE pbFileData, - DWORD cbFileData, - LPDWORD pdwChannels) -{ - PWAVE_FILE_HEADER pWaveHdr = (PWAVE_FILE_HEADER)pbFileData; - - // The amount of file data must be at least size of WAVE header - if(cbFileData > sizeof(WAVE_FILE_HEADER)) - { - // Check for the RIFF header - if(pWaveHdr->dwChunkId == FILE_SIGNATURE_RIFF && pWaveHdr->dwFormat == FILE_SIGNATURE_WAVE) - { - // Check for ADPCM format - if(pWaveHdr->dwSubChunk1Id == FILE_SIGNATURE_FMT && pWaveHdr->wAudioFormat == AUDIO_FORMAT_PCM) - { - // Now the number of bits per sample must be at least 16. - // If not, the WAVE file gets corrupted by the ADPCM compression - if(pWaveHdr->wBitsPerSample >= 0x10) - { - *pdwChannels = pWaveHdr->wChannels; - return true; - } - } - } - } - - return false; -} - -static int FillWritableHandle( - TMPQArchive * ha, - TMPQFile * hf, - ULONGLONG FileTime, - DWORD dwFileSize, - DWORD dwFlags) -{ - TFileEntry * pFileEntry = hf->pFileEntry; - - // Initialize the hash entry for the file - hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; - hf->dwDataSize = dwFileSize; - - // Initialize the block table entry for the file - pFileEntry->ByteOffset = hf->MpqFilePos; - pFileEntry->dwFileSize = dwFileSize; - pFileEntry->dwCmpSize = 0; - pFileEntry->dwFlags = dwFlags | MPQ_FILE_EXISTS; - - // Initialize the file time, CRC32 and MD5 - assert(sizeof(hf->hctx) >= sizeof(hash_state)); - memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE); - md5_init((hash_state *)hf->hctx); - pFileEntry->dwCrc32 = crc32(0, Z_NULL, 0); - - // If the caller gave us a file time, use it. - pFileEntry->FileTime = FileTime; - - // Mark the archive as modified - ha->dwFlags |= MPQ_FLAG_CHANGED; - - // Call the callback, if needed - if(ha->pfnAddFileCB != NULL) - ha->pfnAddFileCB(ha->pvAddFileUserData, 0, hf->dwDataSize, false); - hf->nAddFileError = ERROR_SUCCESS; - - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// MPQ write data functions - -static int WriteDataToMpqFile( - TMPQArchive * ha, - TMPQFile * hf, - LPBYTE pbFileData, - DWORD dwDataSize, - DWORD dwCompression) -{ - TFileEntry * pFileEntry = hf->pFileEntry; - ULONGLONG ByteOffset; - LPBYTE pbCompressed = NULL; // Compressed (target) data - LPBYTE pbToWrite = hf->pbFileSector; // Data to write to the file - int nCompressionLevel; // ADPCM compression level (only used for wave files) - int nError = ERROR_SUCCESS; - - // Make sure that the caller won't overrun the previously initiated file size - assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize); - assert(hf->dwSectorCount != 0); - assert(hf->pbFileSector != NULL); - if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize) - return ERROR_DISK_FULL; - - // Now write all data to the file sector buffer - if(nError == ERROR_SUCCESS) - { - DWORD dwBytesInSector = hf->dwFilePos % hf->dwSectorSize; - DWORD dwSectorIndex = hf->dwFilePos / hf->dwSectorSize; - DWORD dwBytesToCopy; - - // Process all data. - while(dwDataSize != 0) - { - dwBytesToCopy = dwDataSize; - - // Check for sector overflow - if(dwBytesToCopy > (hf->dwSectorSize - dwBytesInSector)) - dwBytesToCopy = (hf->dwSectorSize - dwBytesInSector); - - // Copy the data to the file sector - memcpy(hf->pbFileSector + dwBytesInSector, pbFileData, dwBytesToCopy); - dwBytesInSector += dwBytesToCopy; - pbFileData += dwBytesToCopy; - dwDataSize -= dwBytesToCopy; - - // Update the file position - hf->dwFilePos += dwBytesToCopy; - - // If the current sector is full, or if the file is already full, - // then write the data to the MPQ - if(dwBytesInSector >= hf->dwSectorSize || hf->dwFilePos >= pFileEntry->dwFileSize) - { - // Set the position in the file - ByteOffset = hf->RawFilePos + pFileEntry->dwCmpSize; - - // Update CRC32 and MD5 of the file - md5_process((hash_state *)hf->hctx, hf->pbFileSector, dwBytesInSector); - hf->dwCrc32 = crc32(hf->dwCrc32, hf->pbFileSector, dwBytesInSector); - - // Compress the file sector, if needed - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) - { - int nOutBuffer = (int)dwBytesInSector; - int nInBuffer = (int)dwBytesInSector; - - // 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 copy data as-is, - // if they are unable to compress the data. - // - - if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE) - { - SCompImplode(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer); - } - - if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS) - { - // If this is the first sector, we need to override the given compression - // by the first sector compression. This is because the entire sector must - // be compressed by the same compression. - // - // Test case: - // - // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_PKWARE) // Write 0x10 bytes (sector 0) - // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) - // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) - // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0) - dwCompression = (dwSectorIndex == 0) ? hf->dwCompression0 : dwCompression; - - // If the caller wants ADPCM compression, we will set wave compression level to 4, - // which corresponds to medium quality - nCompressionLevel = (dwCompression & MPQ_LOSSY_COMPRESSION_MASK) ? 4 : -1; - SCompCompress(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel); - } - - // Update sector positions - dwBytesInSector = nOutBuffer; - if(hf->SectorOffsets != NULL) - hf->SectorOffsets[dwSectorIndex+1] = hf->SectorOffsets[dwSectorIndex] + dwBytesInSector; - - // We have to calculate sector CRC, if enabled - if(hf->SectorChksums != NULL) - hf->SectorChksums[dwSectorIndex] = adler32(0, pbCompressed, nOutBuffer); - } - - // Encrypt the sector, if necessary - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector); - EncryptMpqBlock(pbToWrite, dwBytesInSector, hf->dwFileKey + dwSectorIndex); - BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector); - } - - // Write the file sector - if(!FileStream_Write(ha->pStream, &ByteOffset, pbToWrite, dwBytesInSector)) - { - nError = GetLastError(); - break; - } - - // Call the compact callback, if any - if(ha->pfnAddFileCB != NULL) - ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwFilePos, hf->dwDataSize, false); - - // Update the compressed file size - pFileEntry->dwCmpSize += dwBytesInSector; - dwBytesInSector = 0; - dwSectorIndex++; - } - } - } - - // Cleanup - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - return nError; -} - -//----------------------------------------------------------------------------- -// Recrypts file data for file renaming - -static int RecryptFileData( - TMPQArchive * ha, - TMPQFile * hf, - const char * szFileName, - const char * szNewFileName) -{ - ULONGLONG RawFilePos; - TFileEntry * pFileEntry = hf->pFileEntry; - DWORD dwBytesToRecrypt = pFileEntry->dwCmpSize; - DWORD dwOldKey; - DWORD dwNewKey; - int nError = ERROR_SUCCESS; - - // The file must be encrypted - assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED); - - // File decryption key is calculated from the plain name - szNewFileName = GetPlainFileName(szNewFileName); - szFileName = GetPlainFileName(szFileName); - - // Calculate both file keys - dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); - dwNewKey = DecryptFileKey(szNewFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); - - // Incase the keys are equal, don't recrypt the file - if(dwNewKey == dwOldKey) - return ERROR_SUCCESS; - hf->dwFileKey = dwOldKey; - - // Calculate the raw position of the file in the archive - hf->MpqFilePos = pFileEntry->ByteOffset; - hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; - - // Allocate buffer for file transfer - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - - // Also allocate buffer for sector offsets - // Note: Don't load sector checksums, we don't need to recrypt them - nError = AllocateSectorOffsets(hf, true); - if(nError != ERROR_SUCCESS) - return nError; - - // If we have sector offsets, recrypt these as well - if(hf->SectorOffsets != NULL) - { - // Allocate secondary buffer for sectors copy - DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD)); - DWORD dwSectorOffsLen = hf->SectorOffsets[0]; - - if(SectorOffsetsCopy == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Recrypt the array of sector offsets - memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen); - EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwNewKey - 1); - BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen); - - // Write the recrypted array back - if(!FileStream_Write(ha->pStream, &hf->RawFilePos, SectorOffsetsCopy, dwSectorOffsLen)) - nError = GetLastError(); - STORM_FREE(SectorOffsetsCopy); - } - - // Now we have to recrypt all file sectors. We do it without - // recompression, because recompression is not necessary in this case - if(nError == ERROR_SUCCESS) - { - for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++) - { - 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 > dwBytesToRecrypt) - dwRawDataInSector = dwBytesToRecrypt; - - // Fix the raw data length if the file is compressed - if(hf->SectorOffsets != NULL) - { - dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector]; - dwRawByteOffset = hf->SectorOffsets[dwSector]; - } - - // Calculate the raw file offset of the file sector - RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset); - - // Read the file sector - if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector)) - { - nError = GetLastError(); - break; - } - - // If necessary, re-encrypt the sector - // Note: Recompression is not necessary here. Unlike encryption, - // the compression does not depend on the position of the file in MPQ. - BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); - DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwOldKey + dwSector); - EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwNewKey + dwSector); - BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); - - // Write the sector back - if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector)) - { - nError = GetLastError(); - break; - } - - // Decrement number of bytes remaining - dwBytesToRecrypt -= hf->dwSectorSize; - } - } - - return nError; -} - -//----------------------------------------------------------------------------- -// Internal support for MPQ modifications - -int SFileAddFile_Init( - TMPQArchive * ha, - const char * szFileName, - ULONGLONG FileTime, - DWORD dwFileSize, - LCID lcLocale, - DWORD dwFlags, - TMPQFile ** phf) -{ - TFileEntry * pFileEntry = NULL; - TMPQFile * hf = NULL; // File structure for newly added file - DWORD dwHashIndex = HASH_ENTRY_FREE; - int nError = ERROR_SUCCESS; - - // - // Note: This is an internal function so no validity checks are done. - // It is the caller's responsibility to make sure that no invalid - // flags get to this point - // - - // 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_COMPRESS_MASK)) - 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 - if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3) - lcLocale = 0; - - // Allocate the TMPQFile entry for newly added file - hf = CreateWritableHandle(ha, dwFileSize); - if(hf == NULL) - return false; - - // Allocate file entry in the MPQ - if(nError == ERROR_SUCCESS) - { - // Check if the file already exists in the archive - pFileEntry = GetFileEntryExact(ha, szFileName, lcLocale, &dwHashIndex); - if(pFileEntry != NULL) - { - if(dwFlags & MPQ_FILE_REPLACEEXISTING) - InvalidateInternalFiles(ha); - else - nError = ERROR_ALREADY_EXISTS; - } - else - { - // Free all internal files - (listfile), (attributes), (signature) - InvalidateInternalFiles(ha); - - // Attempt to allocate new file entry - pFileEntry = AllocateFileEntry(ha, szFileName, lcLocale, &dwHashIndex); - if(pFileEntry == NULL) - nError = ERROR_DISK_FULL; - } - - // Set the file entry to the file structure - hf->pFileEntry = pFileEntry; - } - - // Prepare the pointer to hash table entry - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL && dwHashIndex < ha->pHeader->dwHashTableSize) - { - hf->pHashEntry = ha->pHashTable + dwHashIndex; - hf->pHashEntry->lcLocale = (USHORT)lcLocale; - } - - // Prepare the file key - if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED)) - { - hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags); - if(hf->dwFileKey == 0) - nError = ERROR_UNKNOWN_FILE_KEY; - } - - // Fill the file entry and TMPQFile structure - if(nError == ERROR_SUCCESS) - { - // At this point, the file name in the file entry must be set - assert(pFileEntry->szFileName != NULL); - assert(_stricmp(pFileEntry->szFileName, szFileName) == 0); - - nError = FillWritableHandle(ha, hf, FileTime, dwFileSize, dwFlags); - } - - // Free the file handle if failed - if(nError != ERROR_SUCCESS && hf != NULL) - FreeFileHandle(hf); - - // Give the handle to the caller - *phf = hf; - return nError; -} - -int SFileAddFile_Init( - TMPQArchive * ha, - TMPQFile * hfSrc, - TMPQFile ** phf) -{ - TFileEntry * pFileEntry = NULL; - TMPQFile * hf = NULL; // File structure for newly added file - ULONGLONG FileTime = hfSrc->pFileEntry->FileTime; - DWORD dwFileSize = hfSrc->pFileEntry->dwFileSize; - DWORD dwFlags = hfSrc->pFileEntry->dwFlags; - int nError = ERROR_SUCCESS; - - // Allocate the TMPQFile entry for newly added file - hf = CreateWritableHandle(ha, dwFileSize); - if(hf == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - - // We need to keep the file entry index the same like in the source archive - // This is because multiple hash table entries can point to the same file entry - if(nError == ERROR_SUCCESS) - { - // Retrieve the file entry for the target file - pFileEntry = ha->pFileTable + (hfSrc->pFileEntry - hfSrc->ha->pFileTable); - - // Copy all variables except file name - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - pFileEntry[0] = hfSrc->pFileEntry[0]; - pFileEntry->szFileName = NULL; - } - else - nError = ERROR_ALREADY_EXISTS; - - // Set the file entry to the file structure - hf->pFileEntry = pFileEntry; - } - - // Prepare the pointer to hash table entry - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL && hfSrc->pHashEntry != NULL) - { - hf->dwHashIndex = (DWORD)(hfSrc->pHashEntry - hfSrc->ha->pHashTable); - hf->pHashEntry = ha->pHashTable + hf->dwHashIndex; - } - - // Prepare the file key (copy from source file) - if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED)) - { - hf->dwFileKey = hfSrc->dwFileKey; - if(hf->dwFileKey == 0) - nError = ERROR_UNKNOWN_FILE_KEY; - } - - // Fill the file entry and TMPQFile structure - if(nError == ERROR_SUCCESS) - { - nError = FillWritableHandle(ha, hf, FileTime, dwFileSize, dwFlags); - } - - // Free the file handle if failed - if(nError != ERROR_SUCCESS && hf != NULL) - FreeFileHandle(hf); - - // Give the handle to the caller - *phf = hf; - return nError; -} - -int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD dwCompression) -{ - TMPQArchive * ha; - TFileEntry * pFileEntry; - int nError = ERROR_SUCCESS; - - // Don't bother if the caller gave us zero size - if(pvData == NULL || dwSize == 0) - return ERROR_SUCCESS; - - // Get pointer to the MPQ archive - pFileEntry = hf->pFileEntry; - ha = hf->ha; - - // Allocate file buffers - if(hf->pbFileSector == NULL) - { - ULONGLONG RawFilePos = hf->RawFilePos; - - // Allocate buffer for file sector - hf->nAddFileError = nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - return nError; - - // Allocate patch info, if the data is patch - if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize)) - { - // Set the MPQ_FILE_PATCH_FILE flag - hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE; - - // Allocate the patch info - hf->nAddFileError = nError = AllocatePatchInfo(hf, false); - if(nError != ERROR_SUCCESS) - return nError; - } - - // Allocate sector offsets - if(hf->SectorOffsets == NULL) - { - hf->nAddFileError = nError = AllocateSectorOffsets(hf, false); - if(nError != ERROR_SUCCESS) - return nError; - } - - // Create array of sector checksums - if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)) - { - hf->nAddFileError = nError = AllocateSectorChecksums(hf, false); - if(nError != ERROR_SUCCESS) - return nError; - } - - // Pre-save the patch info, if any - if(hf->pPatchInfo != NULL) - { - if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pPatchInfo, hf->pPatchInfo->dwLength)) - nError = GetLastError(); - - pFileEntry->dwCmpSize += hf->pPatchInfo->dwLength; - RawFilePos += hf->pPatchInfo->dwLength; - } - - // Pre-save the sector offset table, just to reserve space in the file. - // Note that we dont need to swap the sector positions, nor encrypt the table - // at the moment, as it will be written again after writing all file sectors. - if(hf->SectorOffsets != NULL) - { - if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, hf->SectorOffsets[0])) - nError = GetLastError(); - - pFileEntry->dwCmpSize += hf->SectorOffsets[0]; - RawFilePos += hf->SectorOffsets[0]; - } - } - - // Write the MPQ data to the file - if(nError == ERROR_SUCCESS) - { - // Save the first sector compression to the file structure - // Note that the entire first file sector will be compressed - // by compression that was passed to the first call of SFileAddFile_Write - if(hf->dwFilePos == 0) - hf->dwCompression0 = dwCompression; - - // Write the data to the MPQ - nError = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression); - } - - // If it succeeded and we wrote all the file data, - // we need to re-save sector offset table - if(nError == ERROR_SUCCESS) - { - if(hf->dwFilePos >= pFileEntry->dwFileSize) - { - // Finish calculating CRC32 - hf->pFileEntry->dwCrc32 = hf->dwCrc32; - - // Finish calculating MD5 - md5_done((hash_state *)hf->hctx, hf->pFileEntry->md5); - - // If we also have sector checksums, write them to the file - if(hf->SectorChksums != NULL) - { - nError = WriteSectorChecksums(hf); - } - - // Now write patch info - if(hf->pPatchInfo != NULL) - { - memcpy(hf->pPatchInfo->md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE); - hf->pPatchInfo->dwDataSize = hf->pFileEntry->dwFileSize; - hf->pFileEntry->dwFileSize = hf->dwPatchedFileSize; - nError = WritePatchInfo(hf); - } - - // Now write sector offsets to the file - if(hf->SectorOffsets != NULL) - { - nError = WriteSectorOffsets(hf); - } - - // Write the MD5 hashes of each file chunk, if required - if(ha->pHeader->dwRawChunkSize != 0) - { - nError = WriteMpqDataMD5(ha->pStream, - ha->MpqPos + hf->pFileEntry->ByteOffset, - hf->pFileEntry->dwCmpSize, - ha->pHeader->dwRawChunkSize); - } - } - } - - // Store the error code from the Write File operation - hf->nAddFileError = nError; - return nError; -} - -int SFileAddFile_Finish(TMPQFile * hf) -{ - TMPQArchive * ha = hf->ha; - TFileEntry * pFileEntry = hf->pFileEntry; - int nError = hf->nAddFileError; - - // If all previous operations succeeded, we can update the MPQ - if(nError == ERROR_SUCCESS) - { - // Verify if the caller wrote the file properly - if(hf->pPatchInfo == NULL) - { - assert(pFileEntry != NULL); - if(hf->dwFilePos != pFileEntry->dwFileSize) - nError = ERROR_CAN_NOT_COMPLETE; - } - else - { - if(hf->dwFilePos != hf->pPatchInfo->dwDataSize) - nError = ERROR_CAN_NOT_COMPLETE; - } - } - - // Now we need to recreate the HET table, if exists - if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) - { - nError = RebuildHetTable(ha); - } - - // Update the block table size - if(nError == ERROR_SUCCESS) - { - // Call the user callback, if any - if(ha->pfnAddFileCB != NULL) - ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwDataSize, hf->dwDataSize, true); - } - else - { - // Free the file entry in MPQ tables - if(pFileEntry != NULL) - DeleteFileEntry(ha, hf); - } - - // Clear the add file callback - FreeFileHandle(hf); - return nError; -} - -//----------------------------------------------------------------------------- -// Adds data as file to the archive - -bool WINAPI SFileCreateFile( - HANDLE hMpq, - const char * szArchivedName, - ULONGLONG FileTime, - DWORD dwFileSize, - LCID lcLocale, - DWORD dwFlags, - HANDLE * phFile) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - int nError = ERROR_SUCCESS; - - // Check valid parameters - if(!IsValidMpqHandle(hMpq)) - nError = ERROR_INVALID_HANDLE; - if(szArchivedName == NULL || *szArchivedName == 0) - nError = ERROR_INVALID_PARAMETER; - if(phFile == NULL) - nError = ERROR_INVALID_PARAMETER; - - // Don't allow to add file if the MPQ is open for read only - if(nError == ERROR_SUCCESS) - { - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - nError = ERROR_ACCESS_DENIED; - - // Don't allow to add a file under pseudo-file name - if(IsPseudoFileName(szArchivedName, NULL)) - nError = ERROR_INVALID_PARAMETER; - - // Don't allow to add any of the internal files - if(IsInternalMpqFileName(szArchivedName)) - nError = ERROR_INTERNAL_FILE; - } - - // Perform validity check of the MPQ flags - if(nError == ERROR_SUCCESS) - { - // Mask all unsupported flags out - dwFlags &= MPQ_FILE_VALID_FLAGS; - - // Check for valid flag combinations - if((dwFlags & (MPQ_FILE_IMPLODE | MPQ_FILE_COMPRESS)) == (MPQ_FILE_IMPLODE | MPQ_FILE_COMPRESS)) - nError = ERROR_INVALID_PARAMETER; - } - - // Initiate the add file operation - if(nError == ERROR_SUCCESS) - nError = SFileAddFile_Init(ha, szArchivedName, FileTime, dwFileSize, lcLocale, dwFlags, (TMPQFile **)phFile); - - // Deal with the errors - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -bool WINAPI SFileWriteFile( - HANDLE hFile, - const void * pvData, - DWORD dwSize, - DWORD dwCompression) -{ - TMPQFile * hf = (TMPQFile *)hFile; - int nError = ERROR_SUCCESS; - - // Check the proper parameters - if(!IsValidFileHandle(hFile)) - nError = ERROR_INVALID_HANDLE; - if(hf->bIsWriteHandle == false) - nError = ERROR_INVALID_HANDLE; - - // Special checks for single unit files - if(nError == ERROR_SUCCESS && (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)) - { - // - // Note: Blizzard doesn't support single unit files - // that are stored as encrypted or imploded. We will allow them here, - // the calling application must ensure that such flag combination doesn't get here - // - -// if(dwFlags & MPQ_FILE_IMPLODE) -// nError = ERROR_INVALID_PARAMETER; -// -// if(dwFlags & MPQ_FILE_ENCRYPTED) -// nError = ERROR_INVALID_PARAMETER; - - // Lossy compression is not allowed on single unit files - if(dwCompression & MPQ_LOSSY_COMPRESSION_MASK) - nError = ERROR_INVALID_PARAMETER; - } - - - // Write the data to the file - if(nError == ERROR_SUCCESS) - nError = SFileAddFile_Write(hf, pvData, dwSize, dwCompression); - - // Deal with errors - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -bool WINAPI SFileFinishFile(HANDLE hFile) -{ - TMPQFile * hf = (TMPQFile *)hFile; - int nError = ERROR_SUCCESS; - - // Check the proper parameters - if(!IsValidFileHandle(hFile)) - nError = ERROR_INVALID_HANDLE; - if(hf->bIsWriteHandle == false) - nError = ERROR_INVALID_HANDLE; - - // Finish the file - if(nError == ERROR_SUCCESS) - nError = SFileAddFile_Finish(hf); - - // Deal with errors - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Adds a file to the archive - -bool WINAPI SFileAddFileEx( - HANDLE hMpq, - const TCHAR * szFileName, - const char * szArchivedName, - DWORD dwFlags, - DWORD dwCompression, // Compression of the first sector - DWORD dwCompressionNext) // Compression of next sectors -{ - ULONGLONG FileSize = 0; - ULONGLONG FileTime = 0; - TFileStream * pStream = NULL; - HANDLE hMpqFile = NULL; - LPBYTE pbFileData = NULL; - DWORD dwBytesRemaining = 0; - DWORD dwBytesToRead; - DWORD dwSectorSize = 0x1000; - DWORD dwChannels = 0; - bool bIsAdpcmCompression = false; - bool bIsFirstSector = true; - int nError = ERROR_SUCCESS; - - // Check parameters - if(hMpq == NULL || szFileName == NULL || *szFileName == 0) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // Open added file - pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); - if(pStream == NULL) - return false; - - // Files bigger than 4GB cannot be added to MPQ - FileStream_GetTime(pStream, &FileTime); - FileStream_GetSize(pStream, &FileSize); - if(FileSize >> 32) - nError = ERROR_DISK_FULL; - - // Allocate data buffer for reading from the source file - if(nError == ERROR_SUCCESS) - { - dwBytesRemaining = (DWORD)FileSize; - pbFileData = STORM_ALLOC(BYTE, dwSectorSize); - if(pbFileData == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Deal with various combination of compressions - if(nError == ERROR_SUCCESS) - { - // When the compression for next blocks is set to default, - // we will copy the compression for the first sector - 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_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) - { - // The compression of the first file sector must not be ADPCM - // in order not to corrupt the headers - if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) - dwCompression = MPQ_COMPRESSION_PKWARE; - - // Remove both flag mono and stereo flags. - // They will be re-added according to WAVE type - dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO); - bIsAdpcmCompression = true; - } - - // Initiate adding file to the MPQ - if(!SFileCreateFile(hMpq, szArchivedName, FileTime, (DWORD)FileSize, lcFileLocale, dwFlags, &hMpqFile)) - nError = GetLastError(); - } - - // Write the file data to the MPQ - while(nError == ERROR_SUCCESS && dwBytesRemaining != 0) - { - // Get the number of bytes remaining in the source file - dwBytesToRead = dwBytesRemaining; - if(dwBytesToRead > dwSectorSize) - dwBytesToRead = dwSectorSize; - - // Read data from the local file - if(!FileStream_Read(pStream, NULL, pbFileData, dwBytesToRead)) - { - nError = GetLastError(); - 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 with at least 16 bits per sample, - // otherwise the ADPCM compression will corrupt it - if(IsWaveFile_16BitsPerAdpcmSample(pbFileData, dwBytesToRead, &dwChannels)) - { - // Setup the compression of next sectors according to number of channels - dwCompressionNext |= (dwChannels == 1) ? MPQ_COMPRESSION_ADPCM_MONO : MPQ_COMPRESSION_ADPCM_STEREO; - } - else - { - // Setup the compression of next sectors to a lossless compression - dwCompressionNext = (dwCompression & MPQ_LOSSY_COMPRESSION_MASK) ? MPQ_COMPRESSION_PKWARE : dwCompression; - } - - bIsFirstSector = false; - } - - // Add the file sectors to the MPQ - if(!SFileWriteFile(hMpqFile, pbFileData, dwBytesToRead, dwCompression)) - { - nError = GetLastError(); - break; - } - - // Set the next data compression - dwBytesRemaining -= dwBytesToRead; - dwCompression = dwCompressionNext; - } - - // Finish the file writing - if(hMpqFile != NULL) - { - if(!SFileFinishFile(hMpqFile)) - nError = GetLastError(); - } - - // Cleanup and exit - if(pbFileData != NULL) - STORM_FREE(pbFileData); - if(pStream != NULL) - FileStream_Close(pStream); - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -// Adds a data file into the archive -bool WINAPI SFileAddFile(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags) -{ - return SFileAddFileEx(hMpq, - szFileName, - szArchivedName, - dwFlags, - DefaultDataCompression, - DefaultDataCompression); -} - -// Adds a WAVE file into the archive -bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwQuality) -{ - DWORD dwCompression = 0; - - // - // Note to wave compression level: - // The following conversion table applied: - // High quality: WaveCompressionLevel = -1 - // Medium quality: WaveCompressionLevel = 4 - // Low quality: WaveCompressionLevel = 2 - // - // Starcraft files are packed as Mono (0x41) on medium quality. - // Because this compression is not used anymore, our compression functions - // will default to WaveCompressionLevel = 4 when using ADPCM compression - // - - // Convert quality to data compression - switch(dwQuality) - { - case MPQ_WAVE_QUALITY_HIGH: -// WaveCompressionLevel = -1; - dwCompression = MPQ_COMPRESSION_PKWARE; - break; - - case MPQ_WAVE_QUALITY_MEDIUM: -// WaveCompressionLevel = 4; - dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN; - break; - - case MPQ_WAVE_QUALITY_LOW: -// WaveCompressionLevel = 2; - dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN; - break; - } - - return SFileAddFileEx(hMpq, - szFileName, - szArchivedName, - dwFlags, - MPQ_COMPRESSION_PKWARE, // First sector should be compressed as data - dwCompression); // Next sectors should be compressed as WAVE -} - -//----------------------------------------------------------------------------- -// bool SFileRemoveFile(HANDLE hMpq, char * szFileName) -// -// This function removes a file from the archive. -// - -bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope) -{ - TMPQArchive * ha = IsValidMpqHandle(hMpq); - TMPQFile * hf = NULL; - int nError = ERROR_SUCCESS; - - // Keep compiler happy - dwSearchScope = dwSearchScope; - - // Check the parameters - if(ha == NULL) - nError = ERROR_INVALID_HANDLE; - if(szFileName == NULL || *szFileName == 0) - nError = ERROR_INVALID_PARAMETER; - if(IsInternalMpqFileName(szFileName)) - nError = ERROR_INTERNAL_FILE; - - // Do not allow to remove files from read-only or patched MPQs - if(nError == ERROR_SUCCESS) - { - if((ha->dwFlags & MPQ_FLAG_READ_ONLY) || (ha->haPatch != NULL)) - nError = ERROR_ACCESS_DENIED; - } - - // If all checks have passed, we can delete the file from the MPQ - if(nError == ERROR_SUCCESS) - { - // Open the file from the MPQ - if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf)) - { - // Delete the file entry - nError = DeleteFileEntry(ha, hf); - FreeFileHandle(hf); - } - else - nError = GetLastError(); - } - - // If the file has been deleted, we need to invalidate - // the internal files and recreate HET table - if(nError == ERROR_SUCCESS) - { - // Invalidate the entries for internal files - // After we are done with MPQ changes, we need to re-create them anyway - InvalidateInternalFiles(ha); - - // - // Don't rebuild HET table now; the file's flags indicate - // that it's been deleted, which is enough - // - } - - // Resolve error and exit - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -// Renames the file within the archive. -bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName) -{ - TMPQArchive * ha = IsValidMpqHandle(hMpq); - TMPQFile * hf; - int nError = ERROR_SUCCESS; - - // Test the valid parameters - if(ha == NULL) - nError = ERROR_INVALID_HANDLE; - if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0) - nError = ERROR_INVALID_PARAMETER; - if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName)) - nError = ERROR_INTERNAL_FILE; - - // Do not allow to rename files in MPQ open for read only - if(nError == ERROR_SUCCESS) - { - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - nError = ERROR_ACCESS_DENIED; - } - - // Open the new file. If exists, we don't allow rename operation - if(nError == ERROR_SUCCESS) - { - if(GetFileEntryLocale(ha, szNewFileName, lcFileLocale) != NULL) - nError = ERROR_ALREADY_EXISTS; - } - - // Open the file from the MPQ - if(nError == ERROR_SUCCESS) - { - // Attempt to open the file - if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf)) - { - ULONGLONG RawDataOffs; - TFileEntry * pFileEntry = hf->pFileEntry; - - // Invalidate the entries for internal files - InvalidateInternalFiles(ha); - - // Rename the file entry in the table - nError = RenameFileEntry(ha, hf, szNewFileName); - - // If the file is encrypted, we have to re-crypt the file content - // with the new decryption key - if((nError == ERROR_SUCCESS) && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) - { - // Recrypt the file data in the MPQ - nError = RecryptFileData(ha, hf, szFileName, szNewFileName); - - // Update the MD5 of the raw block - if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0) - { - RawDataOffs = ha->MpqPos + pFileEntry->ByteOffset; - WriteMpqDataMD5(ha->pStream, - RawDataOffs, - pFileEntry->dwCmpSize, - ha->pHeader->dwRawChunkSize); - } - } - - // Free the file handle - FreeFileHandle(hf); - } - else - { - nError = GetLastError(); - } - } - - // We also need to rebuild the HET table, if present - if(nError == ERROR_SUCCESS && ha->pHetTable != NULL) - nError = RebuildHetTable(ha); - - // Resolve error and exit - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Sets default data compression for SFileAddFile - -bool WINAPI SFileSetDataCompression(DWORD DataCompression) -{ - unsigned int uValidMask = (MPQ_COMPRESSION_ZLIB | MPQ_COMPRESSION_PKWARE | MPQ_COMPRESSION_BZIP2 | MPQ_COMPRESSION_SPARSE); - - if((DataCompression & uValidMask) != DataCompression) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - DefaultDataCompression = DataCompression; - return true; -} - -//----------------------------------------------------------------------------- -// Changes locale ID of a file - -bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale) -{ - TMPQArchive * ha; - TFileEntry * pFileEntry; - TMPQFile * hf = IsValidFileHandle(hFile); - - // Invalid handle => do nothing - if(hf == NULL) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - // Do not allow to rename files in MPQ open for read only - ha = hf->ha; - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - // Do not allow unnamed access - if(hf->pFileEntry->szFileName == NULL) - { - SetLastError(ERROR_CAN_NOT_COMPLETE); - return false; - } - - // Do not allow to change locale of any internal file - if(IsInternalMpqFileName(hf->pFileEntry->szFileName)) - { - SetLastError(ERROR_INTERNAL_FILE); - return false; - } - - // Do not allow changing file locales if there is no hash table - if(hf->pHashEntry == NULL) - { - SetLastError(ERROR_NOT_SUPPORTED); - return false; - } - - // We have to check if the file+locale is not already there - pFileEntry = GetFileEntryExact(ha, hf->pFileEntry->szFileName, lcNewLocale, NULL); - if(pFileEntry != NULL) - { - SetLastError(ERROR_ALREADY_EXISTS); - return false; - } - - // Update the locale in the hash table entry - hf->pHashEntry->lcLocale = (USHORT)lcNewLocale; - ha->dwFlags |= MPQ_FLAG_CHANGED; - return true; -} - -//----------------------------------------------------------------------------- -// Sets add file callback - -bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileCB, void * pvUserData) -{ - TMPQArchive * ha = (TMPQArchive *) hMpq; - - if(!IsValidMpqHandle(hMpq)) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - ha->pvAddFileUserData = pvUserData; - ha->pfnAddFileCB = AddFileCB; - return true; -} +/*****************************************************************************/
+/* SFileAddFile.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* MPQ Editing functions */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 27.03.10 1.00 Lad Splitted from SFileCreateArchiveEx.cpp */
+/* 21.04.13 1.01 Dea AddFile callback now part of TMPQArchive */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+// Mask for lossy compressions
+#define MPQ_LOSSY_COMPRESSION_MASK (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN)
+
+// Data compression for SFileAddFile
+// Kept here for compatibility with code that was created with StormLib version < 6.50
+static DWORD DefaultDataCompression = MPQ_COMPRESSION_PKWARE;
+
+//-----------------------------------------------------------------------------
+// WAVE verification
+
+#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;
+
+static bool IsWaveFile_16BitsPerAdpcmSample(
+ LPBYTE pbFileData,
+ DWORD cbFileData,
+ LPDWORD pdwChannels)
+{
+ PWAVE_FILE_HEADER pWaveHdr = (PWAVE_FILE_HEADER)pbFileData;
+
+ // The amount of file data must be at least size of WAVE header
+ if(cbFileData > sizeof(WAVE_FILE_HEADER))
+ {
+ // Check for the RIFF header
+ if(pWaveHdr->dwChunkId == FILE_SIGNATURE_RIFF && pWaveHdr->dwFormat == FILE_SIGNATURE_WAVE)
+ {
+ // Check for ADPCM format
+ if(pWaveHdr->dwSubChunk1Id == FILE_SIGNATURE_FMT && pWaveHdr->wAudioFormat == AUDIO_FORMAT_PCM)
+ {
+ // Now the number of bits per sample must be at least 16.
+ // If not, the WAVE file gets corrupted by the ADPCM compression
+ if(pWaveHdr->wBitsPerSample >= 0x10)
+ {
+ *pdwChannels = pWaveHdr->wChannels;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static int FillWritableHandle(
+ TMPQArchive * ha,
+ TMPQFile * hf,
+ ULONGLONG FileTime,
+ DWORD dwFileSize,
+ DWORD dwFlags)
+{
+ TFileEntry * pFileEntry = hf->pFileEntry;
+
+ // Initialize the hash entry for the file
+ hf->RawFilePos = ha->MpqPos + hf->MpqFilePos;
+ hf->dwDataSize = dwFileSize;
+
+ // Initialize the block table entry for the file
+ pFileEntry->ByteOffset = hf->MpqFilePos;
+ pFileEntry->dwFileSize = dwFileSize;
+ pFileEntry->dwCmpSize = 0;
+ pFileEntry->dwFlags = dwFlags | MPQ_FILE_EXISTS;
+
+ // Initialize the file time, CRC32 and MD5
+ assert(sizeof(hf->hctx) >= sizeof(hash_state));
+ memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE);
+ md5_init((hash_state *)hf->hctx);
+ pFileEntry->dwCrc32 = crc32(0, Z_NULL, 0);
+
+ // If the caller gave us a file time, use it.
+ pFileEntry->FileTime = FileTime;
+
+ // Mark the archive as modified
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+
+ // Call the callback, if needed
+ if(ha->pfnAddFileCB != NULL)
+ ha->pfnAddFileCB(ha->pvAddFileUserData, 0, hf->dwDataSize, false);
+ hf->nAddFileError = ERROR_SUCCESS;
+
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// MPQ write data functions
+
+static int WriteDataToMpqFile(
+ TMPQArchive * ha,
+ TMPQFile * hf,
+ LPBYTE pbFileData,
+ DWORD dwDataSize,
+ DWORD dwCompression)
+{
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ ULONGLONG ByteOffset;
+ LPBYTE pbCompressed = NULL; // Compressed (target) data
+ LPBYTE pbToWrite = hf->pbFileSector; // Data to write to the file
+ int nCompressionLevel; // ADPCM compression level (only used for wave files)
+ int nError = ERROR_SUCCESS;
+
+ // Make sure that the caller won't overrun the previously initiated file size
+ assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize);
+ assert(hf->dwSectorCount != 0);
+ assert(hf->pbFileSector != NULL);
+ if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize)
+ return ERROR_DISK_FULL;
+
+ // Now write all data to the file sector buffer
+ if(nError == ERROR_SUCCESS)
+ {
+ DWORD dwBytesInSector = hf->dwFilePos % hf->dwSectorSize;
+ DWORD dwSectorIndex = hf->dwFilePos / hf->dwSectorSize;
+ DWORD dwBytesToCopy;
+
+ // Process all data.
+ while(dwDataSize != 0)
+ {
+ dwBytesToCopy = dwDataSize;
+
+ // Check for sector overflow
+ if(dwBytesToCopy > (hf->dwSectorSize - dwBytesInSector))
+ dwBytesToCopy = (hf->dwSectorSize - dwBytesInSector);
+
+ // Copy the data to the file sector
+ memcpy(hf->pbFileSector + dwBytesInSector, pbFileData, dwBytesToCopy);
+ dwBytesInSector += dwBytesToCopy;
+ pbFileData += dwBytesToCopy;
+ dwDataSize -= dwBytesToCopy;
+
+ // Update the file position
+ hf->dwFilePos += dwBytesToCopy;
+
+ // If the current sector is full, or if the file is already full,
+ // then write the data to the MPQ
+ if(dwBytesInSector >= hf->dwSectorSize || hf->dwFilePos >= pFileEntry->dwFileSize)
+ {
+ // Set the position in the file
+ ByteOffset = hf->RawFilePos + pFileEntry->dwCmpSize;
+
+ // Update CRC32 and MD5 of the file
+ md5_process((hash_state *)hf->hctx, hf->pbFileSector, dwBytesInSector);
+ hf->dwCrc32 = crc32(hf->dwCrc32, hf->pbFileSector, dwBytesInSector);
+
+ // Compress the file sector, if needed
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ int nOutBuffer = (int)dwBytesInSector;
+ int nInBuffer = (int)dwBytesInSector;
+
+ // 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 copy data as-is,
+ // if they are unable to compress the data.
+ //
+
+ if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
+ {
+ SCompImplode(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer);
+ }
+
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
+ {
+ // If this is the first sector, we need to override the given compression
+ // by the first sector compression. This is because the entire sector must
+ // be compressed by the same compression.
+ //
+ // Test case:
+ //
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_PKWARE) // Write 0x10 bytes (sector 0)
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
+ // WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
+ dwCompression = (dwSectorIndex == 0) ? hf->dwCompression0 : dwCompression;
+
+ // If the caller wants ADPCM compression, we will set wave compression level to 4,
+ // which corresponds to medium quality
+ nCompressionLevel = (dwCompression & MPQ_LOSSY_COMPRESSION_MASK) ? 4 : -1;
+ SCompCompress(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel);
+ }
+
+ // Update sector positions
+ dwBytesInSector = nOutBuffer;
+ if(hf->SectorOffsets != NULL)
+ hf->SectorOffsets[dwSectorIndex+1] = hf->SectorOffsets[dwSectorIndex] + dwBytesInSector;
+
+ // We have to calculate sector CRC, if enabled
+ if(hf->SectorChksums != NULL)
+ hf->SectorChksums[dwSectorIndex] = adler32(0, pbCompressed, nOutBuffer);
+ }
+
+ // Encrypt the sector, if necessary
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector);
+ EncryptMpqBlock(pbToWrite, dwBytesInSector, hf->dwFileKey + dwSectorIndex);
+ BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector);
+ }
+
+ // Write the file sector
+ if(!FileStream_Write(ha->pStream, &ByteOffset, pbToWrite, dwBytesInSector))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Call the compact callback, if any
+ if(ha->pfnAddFileCB != NULL)
+ ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwFilePos, hf->dwDataSize, false);
+
+ // Update the compressed file size
+ pFileEntry->dwCmpSize += dwBytesInSector;
+ dwBytesInSector = 0;
+ dwSectorIndex++;
+ }
+ }
+ }
+
+ // Cleanup
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Recrypts file data for file renaming
+
+static int RecryptFileData(
+ TMPQArchive * ha,
+ TMPQFile * hf,
+ const char * szFileName,
+ const char * szNewFileName)
+{
+ ULONGLONG RawFilePos;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ DWORD dwBytesToRecrypt = pFileEntry->dwCmpSize;
+ DWORD dwOldKey;
+ DWORD dwNewKey;
+ int nError = ERROR_SUCCESS;
+
+ // The file must be encrypted
+ assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED);
+
+ // File decryption key is calculated from the plain name
+ szNewFileName = GetPlainFileName(szNewFileName);
+ szFileName = GetPlainFileName(szFileName);
+
+ // Calculate both file keys
+ dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags);
+ dwNewKey = DecryptFileKey(szNewFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags);
+
+ // Incase the keys are equal, don't recrypt the file
+ if(dwNewKey == dwOldKey)
+ return ERROR_SUCCESS;
+ hf->dwFileKey = dwOldKey;
+
+ // Calculate the raw position of the file in the archive
+ hf->MpqFilePos = pFileEntry->ByteOffset;
+ hf->RawFilePos = ha->MpqPos + hf->MpqFilePos;
+
+ // Allocate buffer for file transfer
+ nError = AllocateSectorBuffer(hf);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Also allocate buffer for sector offsets
+ // Note: Don't load sector checksums, we don't need to recrypt them
+ nError = AllocateSectorOffsets(hf, true);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // If we have sector offsets, recrypt these as well
+ if(hf->SectorOffsets != NULL)
+ {
+ // Allocate secondary buffer for sectors copy
+ DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD));
+ DWORD dwSectorOffsLen = hf->SectorOffsets[0];
+
+ if(SectorOffsetsCopy == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Recrypt the array of sector offsets
+ memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen);
+ EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwNewKey - 1);
+ BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen);
+
+ // Write the recrypted array back
+ if(!FileStream_Write(ha->pStream, &hf->RawFilePos, SectorOffsetsCopy, dwSectorOffsLen))
+ nError = GetLastError();
+ STORM_FREE(SectorOffsetsCopy);
+ }
+
+ // Now we have to recrypt all file sectors. We do it without
+ // recompression, because recompression is not necessary in this case
+ if(nError == ERROR_SUCCESS)
+ {
+ for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++)
+ {
+ 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 > dwBytesToRecrypt)
+ dwRawDataInSector = dwBytesToRecrypt;
+
+ // Fix the raw data length if the file is compressed
+ if(hf->SectorOffsets != NULL)
+ {
+ dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector];
+ dwRawByteOffset = hf->SectorOffsets[dwSector];
+ }
+
+ // Calculate the raw file offset of the file sector
+ RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset);
+
+ // Read the file sector
+ if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // If necessary, re-encrypt the sector
+ // Note: Recompression is not necessary here. Unlike encryption,
+ // the compression does not depend on the position of the file in MPQ.
+ BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
+ DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwOldKey + dwSector);
+ EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwNewKey + dwSector);
+ BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
+
+ // Write the sector back
+ if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Decrement number of bytes remaining
+ dwBytesToRecrypt -= hf->dwSectorSize;
+ }
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Internal support for MPQ modifications
+
+int SFileAddFile_Init(
+ TMPQArchive * ha,
+ const char * szFileName,
+ ULONGLONG FileTime,
+ DWORD dwFileSize,
+ LCID lcLocale,
+ DWORD dwFlags,
+ TMPQFile ** phf)
+{
+ TFileEntry * pFileEntry = NULL;
+ TMPQFile * hf = NULL; // File structure for newly added file
+ DWORD dwHashIndex = HASH_ENTRY_FREE;
+ int nError = ERROR_SUCCESS;
+
+ //
+ // Note: This is an internal function so no validity checks are done.
+ // It is the caller's responsibility to make sure that no invalid
+ // flags get to this point
+ //
+
+ // 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_COMPRESS_MASK))
+ 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
+ if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3)
+ lcLocale = 0;
+
+ // Allocate the TMPQFile entry for newly added file
+ hf = CreateWritableHandle(ha, dwFileSize);
+ if(hf == NULL)
+ return false;
+
+ // Allocate file entry in the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ // Check if the file already exists in the archive
+ pFileEntry = GetFileEntryExact(ha, szFileName, lcLocale, &dwHashIndex);
+ if(pFileEntry != NULL)
+ {
+ if(dwFlags & MPQ_FILE_REPLACEEXISTING)
+ InvalidateInternalFiles(ha);
+ else
+ nError = ERROR_ALREADY_EXISTS;
+ }
+ else
+ {
+ // Free all internal files - (listfile), (attributes), (signature)
+ InvalidateInternalFiles(ha);
+
+ // Attempt to allocate new file entry
+ pFileEntry = AllocateFileEntry(ha, szFileName, lcLocale, &dwHashIndex);
+ if(pFileEntry == NULL)
+ nError = ERROR_DISK_FULL;
+ }
+
+ // Set the file entry to the file structure
+ hf->pFileEntry = pFileEntry;
+ }
+
+ // Prepare the pointer to hash table entry
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL && dwHashIndex < ha->pHeader->dwHashTableSize)
+ {
+ hf->pHashEntry = ha->pHashTable + dwHashIndex;
+ hf->pHashEntry->lcLocale = (USHORT)lcLocale;
+ }
+
+ // Prepare the file key
+ if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED))
+ {
+ hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags);
+ if(hf->dwFileKey == 0)
+ nError = ERROR_UNKNOWN_FILE_KEY;
+ }
+
+ // Fill the file entry and TMPQFile structure
+ if(nError == ERROR_SUCCESS)
+ {
+ // At this point, the file name in the file entry must be set
+ assert(pFileEntry->szFileName != NULL);
+ assert(_stricmp(pFileEntry->szFileName, szFileName) == 0);
+
+ nError = FillWritableHandle(ha, hf, FileTime, dwFileSize, dwFlags);
+ }
+
+ // Free the file handle if failed
+ if(nError != ERROR_SUCCESS && hf != NULL)
+ FreeFileHandle(hf);
+
+ // Give the handle to the caller
+ *phf = hf;
+ return nError;
+}
+
+int SFileAddFile_Init(
+ TMPQArchive * ha,
+ TMPQFile * hfSrc,
+ TMPQFile ** phf)
+{
+ TFileEntry * pFileEntry = NULL;
+ TMPQFile * hf = NULL; // File structure for newly added file
+ ULONGLONG FileTime = hfSrc->pFileEntry->FileTime;
+ DWORD dwFileSize = hfSrc->pFileEntry->dwFileSize;
+ DWORD dwFlags = hfSrc->pFileEntry->dwFlags;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate the TMPQFile entry for newly added file
+ hf = CreateWritableHandle(ha, dwFileSize);
+ if(hf == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // We need to keep the file entry index the same like in the source archive
+ // This is because multiple hash table entries can point to the same file entry
+ if(nError == ERROR_SUCCESS)
+ {
+ // Retrieve the file entry for the target file
+ pFileEntry = ha->pFileTable + (hfSrc->pFileEntry - hfSrc->ha->pFileTable);
+
+ // Copy all variables except file name
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
+ {
+ pFileEntry[0] = hfSrc->pFileEntry[0];
+ pFileEntry->szFileName = NULL;
+ }
+ else
+ nError = ERROR_ALREADY_EXISTS;
+
+ // Set the file entry to the file structure
+ hf->pFileEntry = pFileEntry;
+ }
+
+ // Prepare the pointer to hash table entry
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL && hfSrc->pHashEntry != NULL)
+ {
+ hf->dwHashIndex = (DWORD)(hfSrc->pHashEntry - hfSrc->ha->pHashTable);
+ hf->pHashEntry = ha->pHashTable + hf->dwHashIndex;
+ }
+
+ // Prepare the file key (copy from source file)
+ if(nError == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED))
+ {
+ hf->dwFileKey = hfSrc->dwFileKey;
+ if(hf->dwFileKey == 0)
+ nError = ERROR_UNKNOWN_FILE_KEY;
+ }
+
+ // Fill the file entry and TMPQFile structure
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = FillWritableHandle(ha, hf, FileTime, dwFileSize, dwFlags);
+ }
+
+ // Free the file handle if failed
+ if(nError != ERROR_SUCCESS && hf != NULL)
+ FreeFileHandle(hf);
+
+ // Give the handle to the caller
+ *phf = hf;
+ return nError;
+}
+
+int SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD dwCompression)
+{
+ TMPQArchive * ha;
+ TFileEntry * pFileEntry;
+ int nError = ERROR_SUCCESS;
+
+ // Don't bother if the caller gave us zero size
+ if(pvData == NULL || dwSize == 0)
+ return ERROR_SUCCESS;
+
+ // Get pointer to the MPQ archive
+ pFileEntry = hf->pFileEntry;
+ ha = hf->ha;
+
+ // Allocate file buffers
+ if(hf->pbFileSector == NULL)
+ {
+ ULONGLONG RawFilePos = hf->RawFilePos;
+
+ // Allocate buffer for file sector
+ hf->nAddFileError = nError = AllocateSectorBuffer(hf);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Allocate patch info, if the data is patch
+ if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize))
+ {
+ // Set the MPQ_FILE_PATCH_FILE flag
+ hf->pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE;
+
+ // Allocate the patch info
+ hf->nAddFileError = nError = AllocatePatchInfo(hf, false);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+ // Allocate sector offsets
+ if(hf->SectorOffsets == NULL)
+ {
+ hf->nAddFileError = nError = AllocateSectorOffsets(hf, false);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+ // Create array of sector checksums
+ if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC))
+ {
+ hf->nAddFileError = nError = AllocateSectorChecksums(hf, false);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+ // Pre-save the patch info, if any
+ if(hf->pPatchInfo != NULL)
+ {
+ if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pPatchInfo, hf->pPatchInfo->dwLength))
+ nError = GetLastError();
+
+ pFileEntry->dwCmpSize += hf->pPatchInfo->dwLength;
+ RawFilePos += hf->pPatchInfo->dwLength;
+ }
+
+ // Pre-save the sector offset table, just to reserve space in the file.
+ // Note that we dont need to swap the sector positions, nor encrypt the table
+ // at the moment, as it will be written again after writing all file sectors.
+ if(hf->SectorOffsets != NULL)
+ {
+ if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, hf->SectorOffsets[0]))
+ nError = GetLastError();
+
+ pFileEntry->dwCmpSize += hf->SectorOffsets[0];
+ RawFilePos += hf->SectorOffsets[0];
+ }
+ }
+
+ // Write the MPQ data to the file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Save the first sector compression to the file structure
+ // Note that the entire first file sector will be compressed
+ // by compression that was passed to the first call of SFileAddFile_Write
+ if(hf->dwFilePos == 0)
+ hf->dwCompression0 = dwCompression;
+
+ // Write the data to the MPQ
+ nError = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression);
+ }
+
+ // If it succeeded and we wrote all the file data,
+ // we need to re-save sector offset table
+ if(nError == ERROR_SUCCESS)
+ {
+ if(hf->dwFilePos >= pFileEntry->dwFileSize)
+ {
+ // Finish calculating CRC32
+ hf->pFileEntry->dwCrc32 = hf->dwCrc32;
+
+ // Finish calculating MD5
+ md5_done((hash_state *)hf->hctx, hf->pFileEntry->md5);
+
+ // If we also have sector checksums, write them to the file
+ if(hf->SectorChksums != NULL)
+ {
+ nError = WriteSectorChecksums(hf);
+ }
+
+ // Now write patch info
+ if(hf->pPatchInfo != NULL)
+ {
+ memcpy(hf->pPatchInfo->md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE);
+ hf->pPatchInfo->dwDataSize = hf->pFileEntry->dwFileSize;
+ hf->pFileEntry->dwFileSize = hf->dwPatchedFileSize;
+ nError = WritePatchInfo(hf);
+ }
+
+ // Now write sector offsets to the file
+ if(hf->SectorOffsets != NULL)
+ {
+ nError = WriteSectorOffsets(hf);
+ }
+
+ // Write the MD5 hashes of each file chunk, if required
+ if(ha->pHeader->dwRawChunkSize != 0)
+ {
+ nError = WriteMpqDataMD5(ha->pStream,
+ ha->MpqPos + hf->pFileEntry->ByteOffset,
+ hf->pFileEntry->dwCmpSize,
+ ha->pHeader->dwRawChunkSize);
+ }
+ }
+ }
+
+ // Store the error code from the Write File operation
+ hf->nAddFileError = nError;
+ return nError;
+}
+
+int SFileAddFile_Finish(TMPQFile * hf)
+{
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ int nError = hf->nAddFileError;
+
+ // If all previous operations succeeded, we can update the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify if the caller wrote the file properly
+ if(hf->pPatchInfo == NULL)
+ {
+ assert(pFileEntry != NULL);
+ if(hf->dwFilePos != pFileEntry->dwFileSize)
+ nError = ERROR_CAN_NOT_COMPLETE;
+ }
+ else
+ {
+ if(hf->dwFilePos != hf->pPatchInfo->dwDataSize)
+ nError = ERROR_CAN_NOT_COMPLETE;
+ }
+ }
+
+ // Now we need to recreate the HET table, if exists
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ {
+ nError = RebuildHetTable(ha);
+ }
+
+ // Update the block table size
+ if(nError == ERROR_SUCCESS)
+ {
+ // Call the user callback, if any
+ if(ha->pfnAddFileCB != NULL)
+ ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwDataSize, hf->dwDataSize, true);
+ }
+ else
+ {
+ // Free the file entry in MPQ tables
+ if(pFileEntry != NULL)
+ DeleteFileEntry(ha, hf);
+ }
+
+ // Clear the add file callback
+ FreeFileHandle(hf);
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Adds data as file to the archive
+
+bool WINAPI SFileCreateFile(
+ HANDLE hMpq,
+ const char * szArchivedName,
+ ULONGLONG FileTime,
+ DWORD dwFileSize,
+ LCID lcLocale,
+ DWORD dwFlags,
+ HANDLE * phFile)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ int nError = ERROR_SUCCESS;
+
+ // Check valid parameters
+ if(!IsValidMpqHandle(hMpq))
+ nError = ERROR_INVALID_HANDLE;
+ if(szArchivedName == NULL || *szArchivedName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+ if(phFile == NULL)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // Don't allow to add file if the MPQ is open for read only
+ if(nError == ERROR_SUCCESS)
+ {
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ nError = ERROR_ACCESS_DENIED;
+
+ // Don't allow to add a file under pseudo-file name
+ if(IsPseudoFileName(szArchivedName, NULL))
+ nError = ERROR_INVALID_PARAMETER;
+
+ // Don't allow to add any of the internal files
+ if(IsInternalMpqFileName(szArchivedName))
+ nError = ERROR_INTERNAL_FILE;
+ }
+
+ // Perform validity check of the MPQ flags
+ if(nError == ERROR_SUCCESS)
+ {
+ // Mask all unsupported flags out
+ dwFlags &= MPQ_FILE_VALID_FLAGS;
+
+ // Check for valid flag combinations
+ if((dwFlags & (MPQ_FILE_IMPLODE | MPQ_FILE_COMPRESS)) == (MPQ_FILE_IMPLODE | MPQ_FILE_COMPRESS))
+ nError = ERROR_INVALID_PARAMETER;
+ }
+
+ // Initiate the add file operation
+ if(nError == ERROR_SUCCESS)
+ nError = SFileAddFile_Init(ha, szArchivedName, FileTime, dwFileSize, lcLocale, dwFlags, (TMPQFile **)phFile);
+
+ // Deal with the errors
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+bool WINAPI SFileWriteFile(
+ HANDLE hFile,
+ const void * pvData,
+ DWORD dwSize,
+ DWORD dwCompression)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+ int nError = ERROR_SUCCESS;
+
+ // Check the proper parameters
+ if(!IsValidFileHandle(hFile))
+ nError = ERROR_INVALID_HANDLE;
+ if(hf->bIsWriteHandle == false)
+ nError = ERROR_INVALID_HANDLE;
+
+ // Special checks for single unit files
+ if(nError == ERROR_SUCCESS && (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT))
+ {
+ //
+ // Note: Blizzard doesn't support single unit files
+ // that are stored as encrypted or imploded. We will allow them here,
+ // the calling application must ensure that such flag combination doesn't get here
+ //
+
+// if(dwFlags & MPQ_FILE_IMPLODE)
+// nError = ERROR_INVALID_PARAMETER;
+//
+// if(dwFlags & MPQ_FILE_ENCRYPTED)
+// nError = ERROR_INVALID_PARAMETER;
+
+ // Lossy compression is not allowed on single unit files
+ if(dwCompression & MPQ_LOSSY_COMPRESSION_MASK)
+ nError = ERROR_INVALID_PARAMETER;
+ }
+
+
+ // Write the data to the file
+ if(nError == ERROR_SUCCESS)
+ nError = SFileAddFile_Write(hf, pvData, dwSize, dwCompression);
+
+ // Deal with errors
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+bool WINAPI SFileFinishFile(HANDLE hFile)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+ int nError = ERROR_SUCCESS;
+
+ // Check the proper parameters
+ if(!IsValidFileHandle(hFile))
+ nError = ERROR_INVALID_HANDLE;
+ if(hf->bIsWriteHandle == false)
+ nError = ERROR_INVALID_HANDLE;
+
+ // Finish the file
+ if(nError == ERROR_SUCCESS)
+ nError = SFileAddFile_Finish(hf);
+
+ // Deal with errors
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// Adds a file to the archive
+
+bool WINAPI SFileAddFileEx(
+ HANDLE hMpq,
+ const TCHAR * szFileName,
+ const char * szArchivedName,
+ DWORD dwFlags,
+ DWORD dwCompression, // Compression of the first sector
+ DWORD dwCompressionNext) // Compression of next sectors
+{
+ ULONGLONG FileSize = 0;
+ ULONGLONG FileTime = 0;
+ TFileStream * pStream = NULL;
+ HANDLE hMpqFile = NULL;
+ LPBYTE pbFileData = NULL;
+ DWORD dwBytesRemaining = 0;
+ DWORD dwBytesToRead;
+ DWORD dwSectorSize = 0x1000;
+ DWORD dwChannels = 0;
+ bool bIsAdpcmCompression = false;
+ bool bIsFirstSector = true;
+ int nError = ERROR_SUCCESS;
+
+ // Check parameters
+ if(hMpq == NULL || szFileName == NULL || *szFileName == 0)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // Open added file
+ pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
+ if(pStream == NULL)
+ return false;
+
+ // Files bigger than 4GB cannot be added to MPQ
+ FileStream_GetTime(pStream, &FileTime);
+ FileStream_GetSize(pStream, &FileSize);
+ if(FileSize >> 32)
+ nError = ERROR_DISK_FULL;
+
+ // Allocate data buffer for reading from the source file
+ if(nError == ERROR_SUCCESS)
+ {
+ dwBytesRemaining = (DWORD)FileSize;
+ pbFileData = STORM_ALLOC(BYTE, dwSectorSize);
+ if(pbFileData == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Deal with various combination of compressions
+ if(nError == ERROR_SUCCESS)
+ {
+ // When the compression for next blocks is set to default,
+ // we will copy the compression for the first sector
+ 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_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO))
+ {
+ // The compression of the first file sector must not be ADPCM
+ // in order not to corrupt the headers
+ if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO))
+ dwCompression = MPQ_COMPRESSION_PKWARE;
+
+ // Remove both flag mono and stereo flags.
+ // They will be re-added according to WAVE type
+ dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO);
+ bIsAdpcmCompression = true;
+ }
+
+ // Initiate adding file to the MPQ
+ if(!SFileCreateFile(hMpq, szArchivedName, FileTime, (DWORD)FileSize, lcFileLocale, dwFlags, &hMpqFile))
+ nError = GetLastError();
+ }
+
+ // Write the file data to the MPQ
+ while(nError == ERROR_SUCCESS && dwBytesRemaining != 0)
+ {
+ // Get the number of bytes remaining in the source file
+ dwBytesToRead = dwBytesRemaining;
+ if(dwBytesToRead > dwSectorSize)
+ dwBytesToRead = dwSectorSize;
+
+ // Read data from the local file
+ if(!FileStream_Read(pStream, NULL, pbFileData, dwBytesToRead))
+ {
+ nError = GetLastError();
+ 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 with at least 16 bits per sample,
+ // otherwise the ADPCM compression will corrupt it
+ if(IsWaveFile_16BitsPerAdpcmSample(pbFileData, dwBytesToRead, &dwChannels))
+ {
+ // Setup the compression of next sectors according to number of channels
+ dwCompressionNext |= (dwChannels == 1) ? MPQ_COMPRESSION_ADPCM_MONO : MPQ_COMPRESSION_ADPCM_STEREO;
+ }
+ else
+ {
+ // Setup the compression of next sectors to a lossless compression
+ dwCompressionNext = (dwCompression & MPQ_LOSSY_COMPRESSION_MASK) ? MPQ_COMPRESSION_PKWARE : dwCompression;
+ }
+
+ bIsFirstSector = false;
+ }
+
+ // Add the file sectors to the MPQ
+ if(!SFileWriteFile(hMpqFile, pbFileData, dwBytesToRead, dwCompression))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Set the next data compression
+ dwBytesRemaining -= dwBytesToRead;
+ dwCompression = dwCompressionNext;
+ }
+
+ // Finish the file writing
+ if(hMpqFile != NULL)
+ {
+ if(!SFileFinishFile(hMpqFile))
+ nError = GetLastError();
+ }
+
+ // Cleanup and exit
+ if(pbFileData != NULL)
+ STORM_FREE(pbFileData);
+ if(pStream != NULL)
+ FileStream_Close(pStream);
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+// Adds a data file into the archive
+bool WINAPI SFileAddFile(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags)
+{
+ return SFileAddFileEx(hMpq,
+ szFileName,
+ szArchivedName,
+ dwFlags,
+ DefaultDataCompression,
+ DefaultDataCompression);
+}
+
+// Adds a WAVE file into the archive
+bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwQuality)
+{
+ DWORD dwCompression = 0;
+
+ //
+ // Note to wave compression level:
+ // The following conversion table applied:
+ // High quality: WaveCompressionLevel = -1
+ // Medium quality: WaveCompressionLevel = 4
+ // Low quality: WaveCompressionLevel = 2
+ //
+ // Starcraft files are packed as Mono (0x41) on medium quality.
+ // Because this compression is not used anymore, our compression functions
+ // will default to WaveCompressionLevel = 4 when using ADPCM compression
+ //
+
+ // Convert quality to data compression
+ switch(dwQuality)
+ {
+ case MPQ_WAVE_QUALITY_HIGH:
+// WaveCompressionLevel = -1;
+ dwCompression = MPQ_COMPRESSION_PKWARE;
+ break;
+
+ case MPQ_WAVE_QUALITY_MEDIUM:
+// WaveCompressionLevel = 4;
+ dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN;
+ break;
+
+ case MPQ_WAVE_QUALITY_LOW:
+// WaveCompressionLevel = 2;
+ dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN;
+ break;
+ }
+
+ return SFileAddFileEx(hMpq,
+ szFileName,
+ szArchivedName,
+ dwFlags,
+ MPQ_COMPRESSION_PKWARE, // First sector should be compressed as data
+ dwCompression); // Next sectors should be compressed as WAVE
+}
+
+//-----------------------------------------------------------------------------
+// bool SFileRemoveFile(HANDLE hMpq, char * szFileName)
+//
+// This function removes a file from the archive.
+//
+
+bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope)
+{
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
+ TMPQFile * hf = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Keep compiler happy
+ dwSearchScope = dwSearchScope;
+
+ // Check the parameters
+ if(ha == NULL)
+ nError = ERROR_INVALID_HANDLE;
+ if(szFileName == NULL || *szFileName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+ if(IsInternalMpqFileName(szFileName))
+ nError = ERROR_INTERNAL_FILE;
+
+ // Do not allow to remove files from read-only or patched MPQs
+ if(nError == ERROR_SUCCESS)
+ {
+ if((ha->dwFlags & MPQ_FLAG_READ_ONLY) || (ha->haPatch != NULL))
+ nError = ERROR_ACCESS_DENIED;
+ }
+
+ // If all checks have passed, we can delete the file from the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ // Open the file from the MPQ
+ if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
+ {
+ // Delete the file entry
+ nError = DeleteFileEntry(ha, hf);
+ FreeFileHandle(hf);
+ }
+ else
+ nError = GetLastError();
+ }
+
+ // If the file has been deleted, we need to invalidate
+ // the internal files and recreate HET table
+ if(nError == ERROR_SUCCESS)
+ {
+ // Invalidate the entries for internal files
+ // After we are done with MPQ changes, we need to re-create them anyway
+ InvalidateInternalFiles(ha);
+
+ //
+ // Don't rebuild HET table now; the file's flags indicate
+ // that it's been deleted, which is enough
+ //
+ }
+
+ // Resolve error and exit
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+// Renames the file within the archive.
+bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName)
+{
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
+ TMPQFile * hf;
+ int nError = ERROR_SUCCESS;
+
+ // Test the valid parameters
+ if(ha == NULL)
+ nError = ERROR_INVALID_HANDLE;
+ if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+ if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName))
+ nError = ERROR_INTERNAL_FILE;
+
+ // Do not allow to rename files in MPQ open for read only
+ if(nError == ERROR_SUCCESS)
+ {
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ nError = ERROR_ACCESS_DENIED;
+ }
+
+ // Open the new file. If exists, we don't allow rename operation
+ if(nError == ERROR_SUCCESS)
+ {
+ if(GetFileEntryLocale(ha, szNewFileName, lcFileLocale) != NULL)
+ nError = ERROR_ALREADY_EXISTS;
+ }
+
+ // Open the file from the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ // Attempt to open the file
+ if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
+ {
+ ULONGLONG RawDataOffs;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+
+ // Invalidate the entries for internal files
+ InvalidateInternalFiles(ha);
+
+ // Rename the file entry in the table
+ nError = RenameFileEntry(ha, hf, szNewFileName);
+
+ // If the file is encrypted, we have to re-crypt the file content
+ // with the new decryption key
+ if((nError == ERROR_SUCCESS) && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED))
+ {
+ // Recrypt the file data in the MPQ
+ nError = RecryptFileData(ha, hf, szFileName, szNewFileName);
+
+ // Update the MD5 of the raw block
+ if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0)
+ {
+ RawDataOffs = ha->MpqPos + pFileEntry->ByteOffset;
+ WriteMpqDataMD5(ha->pStream,
+ RawDataOffs,
+ pFileEntry->dwCmpSize,
+ ha->pHeader->dwRawChunkSize);
+ }
+ }
+
+ // Free the file handle
+ FreeFileHandle(hf);
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+ }
+
+ // We also need to rebuild the HET table, if present
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ nError = RebuildHetTable(ha);
+
+ // Resolve error and exit
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// Sets default data compression for SFileAddFile
+
+bool WINAPI SFileSetDataCompression(DWORD DataCompression)
+{
+ unsigned int uValidMask = (MPQ_COMPRESSION_ZLIB | MPQ_COMPRESSION_PKWARE | MPQ_COMPRESSION_BZIP2 | MPQ_COMPRESSION_SPARSE);
+
+ if((DataCompression & uValidMask) != DataCompression)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ DefaultDataCompression = DataCompression;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Changes locale ID of a file
+
+bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale)
+{
+ TMPQArchive * ha;
+ TFileEntry * pFileEntry;
+ TMPQFile * hf = IsValidFileHandle(hFile);
+
+ // Invalid handle => do nothing
+ if(hf == NULL)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ // Do not allow to rename files in MPQ open for read only
+ ha = hf->ha;
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // Do not allow unnamed access
+ if(hf->pFileEntry->szFileName == NULL)
+ {
+ SetLastError(ERROR_CAN_NOT_COMPLETE);
+ return false;
+ }
+
+ // Do not allow to change locale of any internal file
+ if(IsInternalMpqFileName(hf->pFileEntry->szFileName))
+ {
+ SetLastError(ERROR_INTERNAL_FILE);
+ return false;
+ }
+
+ // Do not allow changing file locales if there is no hash table
+ if(hf->pHashEntry == NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return false;
+ }
+
+ // We have to check if the file+locale is not already there
+ pFileEntry = GetFileEntryExact(ha, hf->pFileEntry->szFileName, lcNewLocale, NULL);
+ if(pFileEntry != NULL)
+ {
+ SetLastError(ERROR_ALREADY_EXISTS);
+ return false;
+ }
+
+ // Update the locale in the hash table entry
+ hf->pHashEntry->lcLocale = (USHORT)lcNewLocale;
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Sets add file callback
+
+bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileCB, void * pvUserData)
+{
+ TMPQArchive * ha = (TMPQArchive *) hMpq;
+
+ if(!IsValidMpqHandle(hMpq))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ ha->pvAddFileUserData = pvUserData;
+ ha->pfnAddFileCB = AddFileCB;
+ return true;
+}
diff --git a/src/SFileAttributes.cpp b/src/SFileAttributes.cpp index dc13b2c..d58426c 100644 --- a/src/SFileAttributes.cpp +++ b/src/SFileAttributes.cpp @@ -1,570 +1,570 @@ -/*****************************************************************************/ -/* SAttrFile.cpp Copyright (c) Ladislav Zezula 2007 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 12.06.04 1.00 Lad The first version of SAttrFile.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Local structures - -typedef struct _MPQ_ATTRIBUTES_HEADER -{ - DWORD dwVersion; // Version of the (attributes) file. Must be 100 (0x64) - DWORD dwFlags; // See MPQ_ATTRIBUTE_XXXX - - // 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 - - // Note: The MD5 in (attributes), if present, is a hash of the entire file. - // In case the file is an incremental patch, it contains MD5 of the file - // after being patched. - -} MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER; - -//----------------------------------------------------------------------------- -// Local functions - -static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwBlockTableSize) -{ - DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER); - - // Calculate size of the (attributes) file - if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32) - cbAttrFile += dwBlockTableSize * sizeof(DWORD); - if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) - cbAttrFile += dwBlockTableSize * sizeof(ULONGLONG); - if(dwAttrFlags & MPQ_ATTRIBUTE_MD5) - cbAttrFile += dwBlockTableSize * MD5_DIGEST_SIZE; - - // The bit array has been created without the last bit belonging to (attributes) - // When the number of files is a multiplier of 8 plus one, then the size of (attributes) - // if 1 byte less than expected. - // Example: wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes - if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) - cbAttrFile += (dwBlockTableSize + 6) / 8; - - return cbAttrFile; -} - -static DWORD CheckSizeOfAttributesFile(DWORD cbAttrFile, DWORD dwAttrFlags, DWORD dwBlockTableSize) -{ - DWORD cbHeaderSize = sizeof(MPQ_ATTRIBUTES_HEADER); - DWORD cbChecksumSize1 = 0; - DWORD cbChecksumSize2 = 0; - DWORD cbFileTimeSize1 = 0; - DWORD cbFileTimeSize2 = 0; - DWORD cbFileHashSize1 = 0; - DWORD cbFileHashSize2 = 0; - DWORD cbPatchBitSize1 = 0; - DWORD cbPatchBitSize2 = 0; - DWORD cbPatchBitSize3 = 0; - - // - // Various variants with the patch bit - // - // interface.MPQ.part from WoW build 10958 has - // the MPQ_ATTRIBUTE_PATCH_BIT set, but there's an array of DWORDs instead. - // The array is filled with zeros, so we don't know what it should contain - // - // Zenith.SC2MAP has the MPQ_ATTRIBUTE_PATCH_BIT set, but the bit array is missing - // - // Elimination Tournament 2.w3x's (attributes) have one entry less - // - // There may be two variants: Either the (attributes) file has full - // number of entries, or has one entry less - // - - // Get the expected size of CRC32 array - if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32) - { - cbChecksumSize1 += dwBlockTableSize * sizeof(DWORD); - cbChecksumSize2 += cbChecksumSize1 - sizeof(DWORD); - } - - // Get the expected size of FILETIME array - if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) - { - cbFileTimeSize1 += dwBlockTableSize * sizeof(ULONGLONG); - cbFileTimeSize2 += cbFileTimeSize1 - sizeof(ULONGLONG); - } - - // Get the expected size of MD5 array - if(dwAttrFlags & MPQ_ATTRIBUTE_MD5) - { - cbFileHashSize1 += dwBlockTableSize * MD5_DIGEST_SIZE; - cbFileHashSize2 += cbFileHashSize1 - MD5_DIGEST_SIZE; - } - - // Get the expected size of patch bit array - if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) - { - cbPatchBitSize1 = - cbPatchBitSize2 = ((dwBlockTableSize + 6) / 8); - cbPatchBitSize3 = dwBlockTableSize * sizeof(DWORD); - } - - // Check if the (attributes) file entry count is equal to our file table size - if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1 + cbPatchBitSize1)) - return dwBlockTableSize; - - // Check if the (attributes) file entry count is equal to our file table size minus one - if(cbAttrFile == (cbHeaderSize + cbChecksumSize2 + cbFileTimeSize2 + cbFileHashSize2 + cbPatchBitSize2)) - return dwBlockTableSize - 1; - - // Zenith.SC2MAP has the MPQ_ATTRIBUTE_PATCH_BIT set, but the bit array is missing - if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1)) - return dwBlockTableSize; - - // interface.MPQ.part (WoW build 10958) has the MPQ_ATTRIBUTE_PATCH_BIT set - // but there's an array of DWORDs (filled with zeros) instead of array of bits - if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1 + cbPatchBitSize3)) - return dwBlockTableSize; - -#ifdef __STORMLIB_TEST__ - // Invalid size of the (attributes) file - // Note that many MPQs, especially Warcraft III maps have the size of (attributes) invalid. - // We only perform this check if this is the STORMLIB testprogram itself -// assert(false); -#endif - - return 0; -} - -static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile) -{ - LPBYTE pbAttrFileEnd = pbAttrFile + cbAttrFile; - LPBYTE pbAttrPtr = pbAttrFile; - DWORD dwAttributesEntries = 0; - DWORD i; - - // Load and verify the header - if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd) - { - PMPQ_ATTRIBUTES_HEADER pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr; - - // Verify the header version - BSWAP_ARRAY32_UNSIGNED(pAttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER)); - if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1) - return ERROR_BAD_FORMAT; - - // Verify the flags - if(pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL) - return ERROR_BAD_FORMAT; - - // Verify whether file size of (attributes) is expected - dwAttributesEntries = CheckSizeOfAttributesFile(cbAttrFile, pAttrHeader->dwFlags, ha->pHeader->dwBlockTableSize); - if(dwAttributesEntries == 0) - return ERROR_BAD_FORMAT; - - ha->dwAttrFlags = pAttrHeader->dwFlags; - pbAttrPtr = (LPBYTE)(pAttrHeader + 1); - } - - // Load the CRC32 (if present) - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32) - { - LPDWORD ArrayCRC32 = (LPDWORD)pbAttrPtr; - DWORD cbArraySize = dwAttributesEntries * sizeof(DWORD); - - // Verify if there's enough data - if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) - return ERROR_FILE_CORRUPT; - - BSWAP_ARRAY32_UNSIGNED(ArrayCRC32, cbCRC32Size); - for(i = 0; i < dwAttributesEntries; i++) - ha->pFileTable[i].dwCrc32 = ArrayCRC32[i]; - pbAttrPtr += cbArraySize; - } - - // Load the FILETIME (if present) - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) - { - ULONGLONG * ArrayFileTime = (ULONGLONG *)pbAttrPtr; - DWORD cbArraySize = dwAttributesEntries * sizeof(ULONGLONG); - - // Verify if there's enough data - if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) - return ERROR_FILE_CORRUPT; - - BSWAP_ARRAY64_UNSIGNED(ArrayFileTime, cbFileTimeSize); - for(i = 0; i < dwAttributesEntries; i++) - ha->pFileTable[i].FileTime = ArrayFileTime[i]; - pbAttrPtr += cbArraySize; - } - - // Load the MD5 (if present) - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) - { - LPBYTE ArrayMd5 = pbAttrPtr; - DWORD cbArraySize = dwAttributesEntries * MD5_DIGEST_SIZE; - - // Verify if there's enough data - if((pbAttrPtr + cbArraySize) > pbAttrFileEnd) - return ERROR_FILE_CORRUPT; - - for(i = 0; i < dwAttributesEntries; i++) - { - memcpy(ha->pFileTable[i].md5, ArrayMd5, MD5_DIGEST_SIZE); - ArrayMd5 += MD5_DIGEST_SIZE; - } - pbAttrPtr += cbArraySize; - } - - // Read the patch bit for each file (if present) - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) - { - LPBYTE pbBitArray = pbAttrPtr; - DWORD cbArraySize = (dwAttributesEntries + 7) / 8; - DWORD dwByteIndex = 0; - DWORD dwBitMask = 0x80; - - // Verify if there's enough data - if((pbAttrPtr + cbArraySize) == pbAttrFileEnd) - { - for(i = 0; i < dwAttributesEntries; i++) - { - ha->pFileTable[i].dwFlags |= (pbBitArray[dwByteIndex] & dwBitMask) ? MPQ_FILE_PATCH_FILE : 0; - dwByteIndex += (dwBitMask & 0x01); - dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01); - } - } - } - - return ERROR_SUCCESS; -} - -static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile) -{ - PMPQ_ATTRIBUTES_HEADER pAttrHeader; - TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize; - TFileEntry * pFileEntry; - LPBYTE pbAttrFile; - LPBYTE pbAttrPtr; - size_t cbAttrFile; - - // Check if we need patch bits in the (attributes) file - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) - { - ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT; - break; - } - } - - // Allocate the buffer for holding the entire (attributes) - // Allocate 1 byte more (See GetSizeOfAttributesFile for more info) - cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, ha->pHeader->dwBlockTableSize); - pbAttrFile = pbAttrPtr = STORM_ALLOC(BYTE, cbAttrFile + 1); - if(pbAttrFile != NULL) - { - // Make sure it's all zeroed - memset(pbAttrFile, 0, cbAttrFile + 1); - - // Write the header of the (attributes) file - pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr; - pAttrHeader->dwVersion = BSWAP_INT32_UNSIGNED(100); - pAttrHeader->dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL)); - pbAttrPtr = (LPBYTE)(pAttrHeader + 1); - - // Write the array of CRC32, if present - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32) - { - LPDWORD pArrayCRC32 = (LPDWORD)pbAttrPtr; - - // Copy from file table - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - *pArrayCRC32++ = BSWAP_INT32_UNSIGNED(pFileEntry->dwCrc32); - - // Update pointer - pbAttrPtr = (LPBYTE)pArrayCRC32; - } - - // Write the array of file time - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME) - { - ULONGLONG * pArrayFileTime = (ULONGLONG *)pbAttrPtr; - - // Copy from file table - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - *pArrayFileTime++ = BSWAP_INT64_UNSIGNED(pFileEntry->FileTime); - - // Update pointer - pbAttrPtr = (LPBYTE)pArrayFileTime; - } - - // Write the array of MD5s - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5) - { - LPBYTE pbArrayMD5 = pbAttrPtr; - - // Copy from file table - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - memcpy(pbArrayMD5, pFileEntry->md5, MD5_DIGEST_SIZE); - pbArrayMD5 += MD5_DIGEST_SIZE; - } - - // Update pointer - pbAttrPtr = pbArrayMD5; - } - - // Write the array of patch bits - if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT) - { - LPBYTE pbBitArray = pbAttrPtr; - DWORD dwByteIndex = 0; - BYTE dwBitMask = 0x80; - - // Copy from file table - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Set the bit, if needed - if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) - pbBitArray[dwByteIndex] |= dwBitMask; - - // Update bit index and bit mask - dwByteIndex += (dwBitMask & 0x01); - dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01); - } - - // Move past the bit array - pbAttrPtr += (ha->pHeader->dwBlockTableSize + 6) / 8; - } - - // Now we expect that current position matches the estimated size - // Note that if there is 1 extra bit above the byte size, - // the table is actually 1 byte shorter in Blizzard MPQs. See GetSizeOfAttributesFile - assert((size_t)(pbAttrPtr - pbAttrFile) == cbAttrFile); - } - - // Give away the attributes file - if(pcbAttrFile != NULL) - *pcbAttrFile = (DWORD)cbAttrFile; - return pbAttrFile; -} - -//----------------------------------------------------------------------------- -// Public functions (internal use by StormLib) - -int SAttrLoadAttributes(TMPQArchive * ha) -{ - HANDLE hFile = NULL; - LPBYTE pbAttrFile; - DWORD dwBytesRead; - DWORD cbAttrFile = 0; - int nError = ERROR_FILE_CORRUPT; - - // File table must be initialized - assert(ha->pFileTable != NULL); - assert((ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0); - - // Don't load the attributes file from malformed Warcraft III maps - if(ha->dwFlags & MPQ_FLAG_MALFORMED) - return ERROR_FILE_CORRUPT; - - // Attempt to open the "(attributes)" file. - if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile)) - { - // Retrieve and check size of the (attributes) file - cbAttrFile = SFileGetFileSize(hFile, NULL); - - // Integer overflow check - if((cbAttrFile + 1) > cbAttrFile) - { - // Size of the (attributes) might be 1 byte less than expected - // See GetSizeOfAttributesFile for more info - pbAttrFile = STORM_ALLOC(BYTE, cbAttrFile + 1); - if(pbAttrFile != NULL) - { - // Set the last byte to 0 in case the size should be 1 byte greater - pbAttrFile[cbAttrFile] = 0; - - // Load the entire file to memory - SFileReadFile(hFile, pbAttrFile, cbAttrFile, &dwBytesRead, NULL); - if(dwBytesRead == cbAttrFile) - nError = LoadAttributesFile(ha, pbAttrFile, cbAttrFile); - - // Free the buffer - STORM_FREE(pbAttrFile); - } - } - - // Close the attributes file - SFileCloseFile(hFile); - } - - return nError; -} - -// Saves the (attributes) to the MPQ -int SAttrFileSaveToMpq(TMPQArchive * ha) -{ - TMPQFile * hf = NULL; - LPBYTE pbAttrFile; - DWORD cbAttrFile = 0; - int nError = ERROR_SUCCESS; - - // Only save the attributes if we should do so - if(ha->dwFileFlags2 != 0) - { - // At this point, we expect to have at least one reserved entry in the file table - assert(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW); - assert(ha->dwReservedFiles > 0); - - // Create the raw data that is to be written to (attributes) - // Note: Blizzard MPQs have entries for (listfile) and (attributes), - // but they are filled empty - pbAttrFile = CreateAttributesFile(ha, &cbAttrFile); - if(pbAttrFile != NULL) - { - // Determine the real flags for (attributes) - if(ha->dwFileFlags2 == MPQ_FILE_EXISTS) - ha->dwFileFlags2 = GetDefaultSpecialFileFlags(cbAttrFile, ha->pHeader->wFormatVersion); - - // Create the attributes file in the MPQ - nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME, - 0, - cbAttrFile, - LANG_NEUTRAL, - ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING, - &hf); - - // Write the attributes file raw data to it - if(nError == ERROR_SUCCESS) - { - // Write the content of the attributes file to the MPQ - nError = SFileAddFile_Write(hf, pbAttrFile, cbAttrFile, MPQ_COMPRESSION_ZLIB); - SFileAddFile_Finish(hf); - } - - // Clear the number of reserved files - ha->dwFlags &= ~(MPQ_FLAG_ATTRIBUTES_NEW | MPQ_FLAG_ATTRIBUTES_NONE); - ha->dwReservedFiles--; - - // Free the attributes buffer - STORM_FREE(pbAttrFile); - } - else - { - // If the (attributes) file would be empty, its OK - nError = (cbAttrFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; - } - } - - return nError; -} - -//----------------------------------------------------------------------------- -// Public functions - -DWORD WINAPI SFileGetAttributes(HANDLE hMpq) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - // Verify the parameters - if(!IsValidMpqHandle(hMpq)) - { - SetLastError(ERROR_INVALID_PARAMETER); - return SFILE_INVALID_ATTRIBUTES; - } - - return ha->dwAttrFlags; -} - -bool WINAPI SFileSetAttributes(HANDLE hMpq, DWORD dwFlags) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - // Verify the parameters - if(!IsValidMpqHandle(hMpq)) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // Not allowed when the archive is read-only - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - // Set the attributes - InvalidateInternalFiles(ha); - ha->dwAttrFlags = (dwFlags & MPQ_ATTRIBUTE_ALL); - return true; -} - -bool WINAPI SFileUpdateFileAttributes(HANDLE hMpq, const char * szFileName) -{ - hash_state md5_state; - TMPQArchive * ha = (TMPQArchive *)hMpq; - TMPQFile * hf; - BYTE Buffer[0x1000]; - HANDLE hFile = NULL; - DWORD dwTotalBytes = 0; - DWORD dwBytesRead; - DWORD dwCrc32; - - // Verify the parameters - if(!IsValidMpqHandle(ha)) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // Not allowed when the archive is read-only - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - // Attempt to open the file - if(!SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) - return false; - - // Get the file size - hf = (TMPQFile *)hFile; - dwTotalBytes = hf->pFileEntry->dwFileSize; - - // Initialize the CRC32 and MD5 contexts - md5_init(&md5_state); - dwCrc32 = crc32(0, Z_NULL, 0); - - // Go through entire file and calculate both CRC32 and MD5 - while(dwTotalBytes != 0) - { - // Read data from file - SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL); - if(dwBytesRead == 0) - break; - - // Update CRC32 and MD5 - dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead); - md5_process(&md5_state, Buffer, dwBytesRead); - - // Decrement the total size - dwTotalBytes -= dwBytesRead; - } - - // Update both CRC32 and MD5 - hf->pFileEntry->dwCrc32 = dwCrc32; - md5_done(&md5_state, hf->pFileEntry->md5); - - // Remember that we need to save the MPQ tables - InvalidateInternalFiles(ha); - SFileCloseFile(hFile); - return true; -} +/*****************************************************************************/
+/* SAttrFile.cpp Copyright (c) Ladislav Zezula 2007 */
+/*---------------------------------------------------------------------------*/
+/* Description: */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 12.06.04 1.00 Lad The first version of SAttrFile.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+typedef struct _MPQ_ATTRIBUTES_HEADER
+{
+ DWORD dwVersion; // Version of the (attributes) file. Must be 100 (0x64)
+ DWORD dwFlags; // See MPQ_ATTRIBUTE_XXXX
+
+ // 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
+
+ // Note: The MD5 in (attributes), if present, is a hash of the entire file.
+ // In case the file is an incremental patch, it contains MD5 of the file
+ // after being patched.
+
+} MPQ_ATTRIBUTES_HEADER, *PMPQ_ATTRIBUTES_HEADER;
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static DWORD GetSizeOfAttributesFile(DWORD dwAttrFlags, DWORD dwBlockTableSize)
+{
+ DWORD cbAttrFile = sizeof(MPQ_ATTRIBUTES_HEADER);
+
+ // Calculate size of the (attributes) file
+ if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
+ cbAttrFile += dwBlockTableSize * sizeof(DWORD);
+ if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ cbAttrFile += dwBlockTableSize * sizeof(ULONGLONG);
+ if(dwAttrFlags & MPQ_ATTRIBUTE_MD5)
+ cbAttrFile += dwBlockTableSize * MD5_DIGEST_SIZE;
+
+ // The bit array has been created without the last bit belonging to (attributes)
+ // When the number of files is a multiplier of 8 plus one, then the size of (attributes)
+ // if 1 byte less than expected.
+ // Example: wow-update-13164.MPQ: BlockTableSize = 0x62E1, but there's only 0xC5C bytes
+ if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ cbAttrFile += (dwBlockTableSize + 6) / 8;
+
+ return cbAttrFile;
+}
+
+static DWORD CheckSizeOfAttributesFile(DWORD cbAttrFile, DWORD dwAttrFlags, DWORD dwBlockTableSize)
+{
+ DWORD cbHeaderSize = sizeof(MPQ_ATTRIBUTES_HEADER);
+ DWORD cbChecksumSize1 = 0;
+ DWORD cbChecksumSize2 = 0;
+ DWORD cbFileTimeSize1 = 0;
+ DWORD cbFileTimeSize2 = 0;
+ DWORD cbFileHashSize1 = 0;
+ DWORD cbFileHashSize2 = 0;
+ DWORD cbPatchBitSize1 = 0;
+ DWORD cbPatchBitSize2 = 0;
+ DWORD cbPatchBitSize3 = 0;
+
+ //
+ // Various variants with the patch bit
+ //
+ // interface.MPQ.part from WoW build 10958 has
+ // the MPQ_ATTRIBUTE_PATCH_BIT set, but there's an array of DWORDs instead.
+ // The array is filled with zeros, so we don't know what it should contain
+ //
+ // Zenith.SC2MAP has the MPQ_ATTRIBUTE_PATCH_BIT set, but the bit array is missing
+ //
+ // Elimination Tournament 2.w3x's (attributes) have one entry less
+ //
+ // There may be two variants: Either the (attributes) file has full
+ // number of entries, or has one entry less
+ //
+
+ // Get the expected size of CRC32 array
+ if(dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
+ {
+ cbChecksumSize1 += dwBlockTableSize * sizeof(DWORD);
+ cbChecksumSize2 += cbChecksumSize1 - sizeof(DWORD);
+ }
+
+ // Get the expected size of FILETIME array
+ if(dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ {
+ cbFileTimeSize1 += dwBlockTableSize * sizeof(ULONGLONG);
+ cbFileTimeSize2 += cbFileTimeSize1 - sizeof(ULONGLONG);
+ }
+
+ // Get the expected size of MD5 array
+ if(dwAttrFlags & MPQ_ATTRIBUTE_MD5)
+ {
+ cbFileHashSize1 += dwBlockTableSize * MD5_DIGEST_SIZE;
+ cbFileHashSize2 += cbFileHashSize1 - MD5_DIGEST_SIZE;
+ }
+
+ // Get the expected size of patch bit array
+ if(dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ {
+ cbPatchBitSize1 =
+ cbPatchBitSize2 = ((dwBlockTableSize + 6) / 8);
+ cbPatchBitSize3 = dwBlockTableSize * sizeof(DWORD);
+ }
+
+ // Check if the (attributes) file entry count is equal to our file table size
+ if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1 + cbPatchBitSize1))
+ return dwBlockTableSize;
+
+ // Check if the (attributes) file entry count is equal to our file table size minus one
+ if(cbAttrFile == (cbHeaderSize + cbChecksumSize2 + cbFileTimeSize2 + cbFileHashSize2 + cbPatchBitSize2))
+ return dwBlockTableSize - 1;
+
+ // Zenith.SC2MAP has the MPQ_ATTRIBUTE_PATCH_BIT set, but the bit array is missing
+ if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1))
+ return dwBlockTableSize;
+
+ // interface.MPQ.part (WoW build 10958) has the MPQ_ATTRIBUTE_PATCH_BIT set
+ // but there's an array of DWORDs (filled with zeros) instead of array of bits
+ if(cbAttrFile == (cbHeaderSize + cbChecksumSize1 + cbFileTimeSize1 + cbFileHashSize1 + cbPatchBitSize3))
+ return dwBlockTableSize;
+
+#ifdef __STORMLIB_TEST__
+ // Invalid size of the (attributes) file
+ // Note that many MPQs, especially Warcraft III maps have the size of (attributes) invalid.
+ // We only perform this check if this is the STORMLIB testprogram itself
+// assert(false);
+#endif
+
+ return 0;
+}
+
+static int LoadAttributesFile(TMPQArchive * ha, LPBYTE pbAttrFile, DWORD cbAttrFile)
+{
+ LPBYTE pbAttrFileEnd = pbAttrFile + cbAttrFile;
+ LPBYTE pbAttrPtr = pbAttrFile;
+ DWORD dwAttributesEntries = 0;
+ DWORD i;
+
+ // Load and verify the header
+ if((pbAttrPtr + sizeof(MPQ_ATTRIBUTES_HEADER)) <= pbAttrFileEnd)
+ {
+ PMPQ_ATTRIBUTES_HEADER pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr;
+
+ // Verify the header version
+ BSWAP_ARRAY32_UNSIGNED(pAttrHeader, sizeof(MPQ_ATTRIBUTES_HEADER));
+ if(pAttrHeader->dwVersion != MPQ_ATTRIBUTES_V1)
+ return ERROR_BAD_FORMAT;
+
+ // Verify the flags
+ if(pAttrHeader->dwFlags & ~MPQ_ATTRIBUTE_ALL)
+ return ERROR_BAD_FORMAT;
+
+ // Verify whether file size of (attributes) is expected
+ dwAttributesEntries = CheckSizeOfAttributesFile(cbAttrFile, pAttrHeader->dwFlags, ha->pHeader->dwBlockTableSize);
+ if(dwAttributesEntries == 0)
+ return ERROR_BAD_FORMAT;
+
+ ha->dwAttrFlags = pAttrHeader->dwFlags;
+ pbAttrPtr = (LPBYTE)(pAttrHeader + 1);
+ }
+
+ // Load the CRC32 (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
+ {
+ LPDWORD ArrayCRC32 = (LPDWORD)pbAttrPtr;
+ DWORD cbArraySize = dwAttributesEntries * sizeof(DWORD);
+
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
+
+ BSWAP_ARRAY32_UNSIGNED(ArrayCRC32, cbCRC32Size);
+ for(i = 0; i < dwAttributesEntries; i++)
+ ha->pFileTable[i].dwCrc32 = ArrayCRC32[i];
+ pbAttrPtr += cbArraySize;
+ }
+
+ // Load the FILETIME (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ {
+ ULONGLONG * ArrayFileTime = (ULONGLONG *)pbAttrPtr;
+ DWORD cbArraySize = dwAttributesEntries * sizeof(ULONGLONG);
+
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
+
+ BSWAP_ARRAY64_UNSIGNED(ArrayFileTime, cbFileTimeSize);
+ for(i = 0; i < dwAttributesEntries; i++)
+ ha->pFileTable[i].FileTime = ArrayFileTime[i];
+ pbAttrPtr += cbArraySize;
+ }
+
+ // Load the MD5 (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)
+ {
+ LPBYTE ArrayMd5 = pbAttrPtr;
+ DWORD cbArraySize = dwAttributesEntries * MD5_DIGEST_SIZE;
+
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) > pbAttrFileEnd)
+ return ERROR_FILE_CORRUPT;
+
+ for(i = 0; i < dwAttributesEntries; i++)
+ {
+ memcpy(ha->pFileTable[i].md5, ArrayMd5, MD5_DIGEST_SIZE);
+ ArrayMd5 += MD5_DIGEST_SIZE;
+ }
+ pbAttrPtr += cbArraySize;
+ }
+
+ // Read the patch bit for each file (if present)
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ {
+ LPBYTE pbBitArray = pbAttrPtr;
+ DWORD cbArraySize = (dwAttributesEntries + 7) / 8;
+ DWORD dwByteIndex = 0;
+ DWORD dwBitMask = 0x80;
+
+ // Verify if there's enough data
+ if((pbAttrPtr + cbArraySize) == pbAttrFileEnd)
+ {
+ for(i = 0; i < dwAttributesEntries; i++)
+ {
+ ha->pFileTable[i].dwFlags |= (pbBitArray[dwByteIndex] & dwBitMask) ? MPQ_FILE_PATCH_FILE : 0;
+ dwByteIndex += (dwBitMask & 0x01);
+ dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01);
+ }
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static LPBYTE CreateAttributesFile(TMPQArchive * ha, DWORD * pcbAttrFile)
+{
+ PMPQ_ATTRIBUTES_HEADER pAttrHeader;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize;
+ TFileEntry * pFileEntry;
+ LPBYTE pbAttrFile;
+ LPBYTE pbAttrPtr;
+ size_t cbAttrFile;
+
+ // Check if we need patch bits in the (attributes) file
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
+ {
+ ha->dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT;
+ break;
+ }
+ }
+
+ // Allocate the buffer for holding the entire (attributes)
+ // Allocate 1 byte more (See GetSizeOfAttributesFile for more info)
+ cbAttrFile = GetSizeOfAttributesFile(ha->dwAttrFlags, ha->pHeader->dwBlockTableSize);
+ pbAttrFile = pbAttrPtr = STORM_ALLOC(BYTE, cbAttrFile + 1);
+ if(pbAttrFile != NULL)
+ {
+ // Make sure it's all zeroed
+ memset(pbAttrFile, 0, cbAttrFile + 1);
+
+ // Write the header of the (attributes) file
+ pAttrHeader = (PMPQ_ATTRIBUTES_HEADER)pbAttrPtr;
+ pAttrHeader->dwVersion = BSWAP_INT32_UNSIGNED(100);
+ pAttrHeader->dwFlags = BSWAP_INT32_UNSIGNED((ha->dwAttrFlags & MPQ_ATTRIBUTE_ALL));
+ pbAttrPtr = (LPBYTE)(pAttrHeader + 1);
+
+ // Write the array of CRC32, if present
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_CRC32)
+ {
+ LPDWORD pArrayCRC32 = (LPDWORD)pbAttrPtr;
+
+ // Copy from file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ *pArrayCRC32++ = BSWAP_INT32_UNSIGNED(pFileEntry->dwCrc32);
+
+ // Update pointer
+ pbAttrPtr = (LPBYTE)pArrayCRC32;
+ }
+
+ // Write the array of file time
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_FILETIME)
+ {
+ ULONGLONG * pArrayFileTime = (ULONGLONG *)pbAttrPtr;
+
+ // Copy from file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ *pArrayFileTime++ = BSWAP_INT64_UNSIGNED(pFileEntry->FileTime);
+
+ // Update pointer
+ pbAttrPtr = (LPBYTE)pArrayFileTime;
+ }
+
+ // Write the array of MD5s
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_MD5)
+ {
+ LPBYTE pbArrayMD5 = pbAttrPtr;
+
+ // Copy from file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ memcpy(pbArrayMD5, pFileEntry->md5, MD5_DIGEST_SIZE);
+ pbArrayMD5 += MD5_DIGEST_SIZE;
+ }
+
+ // Update pointer
+ pbAttrPtr = pbArrayMD5;
+ }
+
+ // Write the array of patch bits
+ if(ha->dwAttrFlags & MPQ_ATTRIBUTE_PATCH_BIT)
+ {
+ LPBYTE pbBitArray = pbAttrPtr;
+ DWORD dwByteIndex = 0;
+ BYTE dwBitMask = 0x80;
+
+ // Copy from file table
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Set the bit, if needed
+ if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
+ pbBitArray[dwByteIndex] |= dwBitMask;
+
+ // Update bit index and bit mask
+ dwByteIndex += (dwBitMask & 0x01);
+ dwBitMask = (dwBitMask << 0x07) | (dwBitMask >> 0x01);
+ }
+
+ // Move past the bit array
+ pbAttrPtr += (ha->pHeader->dwBlockTableSize + 6) / 8;
+ }
+
+ // Now we expect that current position matches the estimated size
+ // Note that if there is 1 extra bit above the byte size,
+ // the table is actually 1 byte shorter in Blizzard MPQs. See GetSizeOfAttributesFile
+ assert((size_t)(pbAttrPtr - pbAttrFile) == cbAttrFile);
+ }
+
+ // Give away the attributes file
+ if(pcbAttrFile != NULL)
+ *pcbAttrFile = (DWORD)cbAttrFile;
+ return pbAttrFile;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions (internal use by StormLib)
+
+int SAttrLoadAttributes(TMPQArchive * ha)
+{
+ HANDLE hFile = NULL;
+ LPBYTE pbAttrFile;
+ DWORD dwBytesRead;
+ DWORD cbAttrFile = 0;
+ int nError = ERROR_FILE_CORRUPT;
+
+ // File table must be initialized
+ assert(ha->pFileTable != NULL);
+ assert((ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0);
+
+ // Don't load the attributes file from malformed Warcraft III maps
+ if(ha->dwFlags & MPQ_FLAG_MALFORMED)
+ return ERROR_FILE_CORRUPT;
+
+ // Attempt to open the "(attributes)" file.
+ if(SFileOpenFileEx((HANDLE)ha, ATTRIBUTES_NAME, SFILE_OPEN_ANY_LOCALE, &hFile))
+ {
+ // Retrieve and check size of the (attributes) file
+ cbAttrFile = SFileGetFileSize(hFile, NULL);
+
+ // Integer overflow check
+ if((cbAttrFile + 1) > cbAttrFile)
+ {
+ // Size of the (attributes) might be 1 byte less than expected
+ // See GetSizeOfAttributesFile for more info
+ pbAttrFile = STORM_ALLOC(BYTE, cbAttrFile + 1);
+ if(pbAttrFile != NULL)
+ {
+ // Set the last byte to 0 in case the size should be 1 byte greater
+ pbAttrFile[cbAttrFile] = 0;
+
+ // Load the entire file to memory
+ SFileReadFile(hFile, pbAttrFile, cbAttrFile, &dwBytesRead, NULL);
+ if(dwBytesRead == cbAttrFile)
+ nError = LoadAttributesFile(ha, pbAttrFile, cbAttrFile);
+
+ // Free the buffer
+ STORM_FREE(pbAttrFile);
+ }
+ }
+
+ // Close the attributes file
+ SFileCloseFile(hFile);
+ }
+
+ return nError;
+}
+
+// Saves the (attributes) to the MPQ
+int SAttrFileSaveToMpq(TMPQArchive * ha)
+{
+ TMPQFile * hf = NULL;
+ LPBYTE pbAttrFile;
+ DWORD cbAttrFile = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Only save the attributes if we should do so
+ if(ha->dwFileFlags2 != 0)
+ {
+ // At this point, we expect to have at least one reserved entry in the file table
+ assert(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW);
+ assert(ha->dwReservedFiles > 0);
+
+ // Create the raw data that is to be written to (attributes)
+ // Note: Blizzard MPQs have entries for (listfile) and (attributes),
+ // but they are filled empty
+ pbAttrFile = CreateAttributesFile(ha, &cbAttrFile);
+ if(pbAttrFile != NULL)
+ {
+ // Determine the real flags for (attributes)
+ if(ha->dwFileFlags2 == MPQ_FILE_EXISTS)
+ ha->dwFileFlags2 = GetDefaultSpecialFileFlags(cbAttrFile, ha->pHeader->wFormatVersion);
+
+ // Create the attributes file in the MPQ
+ nError = SFileAddFile_Init(ha, ATTRIBUTES_NAME,
+ 0,
+ cbAttrFile,
+ LANG_NEUTRAL,
+ ha->dwFileFlags2 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Write the attributes file raw data to it
+ if(nError == ERROR_SUCCESS)
+ {
+ // Write the content of the attributes file to the MPQ
+ nError = SFileAddFile_Write(hf, pbAttrFile, cbAttrFile, MPQ_COMPRESSION_ZLIB);
+ SFileAddFile_Finish(hf);
+ }
+
+ // Clear the number of reserved files
+ ha->dwFlags &= ~(MPQ_FLAG_ATTRIBUTES_NEW | MPQ_FLAG_ATTRIBUTES_NONE);
+ ha->dwReservedFiles--;
+
+ // Free the attributes buffer
+ STORM_FREE(pbAttrFile);
+ }
+ else
+ {
+ // If the (attributes) file would be empty, its OK
+ nError = (cbAttrFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+DWORD WINAPI SFileGetAttributes(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Verify the parameters
+ if(!IsValidMpqHandle(hMpq))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SFILE_INVALID_ATTRIBUTES;
+ }
+
+ return ha->dwAttrFlags;
+}
+
+bool WINAPI SFileSetAttributes(HANDLE hMpq, DWORD dwFlags)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Verify the parameters
+ if(!IsValidMpqHandle(hMpq))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // Not allowed when the archive is read-only
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // Set the attributes
+ InvalidateInternalFiles(ha);
+ ha->dwAttrFlags = (dwFlags & MPQ_ATTRIBUTE_ALL);
+ return true;
+}
+
+bool WINAPI SFileUpdateFileAttributes(HANDLE hMpq, const char * szFileName)
+{
+ hash_state md5_state;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TMPQFile * hf;
+ BYTE Buffer[0x1000];
+ HANDLE hFile = NULL;
+ DWORD dwTotalBytes = 0;
+ DWORD dwBytesRead;
+ DWORD dwCrc32;
+
+ // Verify the parameters
+ if(!IsValidMpqHandle(ha))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // Not allowed when the archive is read-only
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // Attempt to open the file
+ if(!SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
+ return false;
+
+ // Get the file size
+ hf = (TMPQFile *)hFile;
+ dwTotalBytes = hf->pFileEntry->dwFileSize;
+
+ // Initialize the CRC32 and MD5 contexts
+ md5_init(&md5_state);
+ dwCrc32 = crc32(0, Z_NULL, 0);
+
+ // Go through entire file and calculate both CRC32 and MD5
+ while(dwTotalBytes != 0)
+ {
+ // Read data from file
+ SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL);
+ if(dwBytesRead == 0)
+ break;
+
+ // Update CRC32 and MD5
+ dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead);
+ md5_process(&md5_state, Buffer, dwBytesRead);
+
+ // Decrement the total size
+ dwTotalBytes -= dwBytesRead;
+ }
+
+ // Update both CRC32 and MD5
+ hf->pFileEntry->dwCrc32 = dwCrc32;
+ md5_done(&md5_state, hf->pFileEntry->md5);
+
+ // Remember that we need to save the MPQ tables
+ InvalidateInternalFiles(ha);
+ SFileCloseFile(hFile);
+ return true;
+}
diff --git a/src/SFileCompactArchive.cpp b/src/SFileCompactArchive.cpp index 5b6cbe3..57c8839 100644 --- a/src/SFileCompactArchive.cpp +++ b/src/SFileCompactArchive.cpp @@ -1,654 +1,654 @@ -/*****************************************************************************/ -/* SFileCompactArchive.cpp Copyright (c) Ladislav Zezula 2003 */ -/*---------------------------------------------------------------------------*/ -/* Archive compacting function */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 14.04.03 1.00 Lad Splitted from SFileCreateArchiveEx.cpp */ -/* 19.11.03 1.01 Dan Big endian handling */ -/* 21.04.13 1.02 Dea Compact callback now part of TMPQArchive */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -/*****************************************************************************/ -/* Local functions */ -/*****************************************************************************/ - -static int CheckIfAllFilesKnown(TMPQArchive * ha) -{ - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; - DWORD dwBlockIndex = 0; - int nError = ERROR_SUCCESS; - - // Verify the file table - if(nError == ERROR_SUCCESS) - { - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++, dwBlockIndex++) - { - // If there is an existing entry in the file table, check its name - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // The name must be valid and must not be a pseudo-name - if(pFileEntry->szFileName == NULL || IsPseudoFileName(pFileEntry->szFileName, NULL)) - { - nError = ERROR_UNKNOWN_FILE_NAMES; - break; - } - } - } - } - - return nError; -} - -static int CheckIfAllKeysKnown(TMPQArchive * ha, const char * szListFile, LPDWORD pFileKeys) -{ - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; - DWORD dwBlockIndex = 0; - int nError = ERROR_SUCCESS; - - // Add the listfile to the MPQ - if(szListFile != NULL) - { - // Notify the user - if(ha->pfnCompactCB != NULL) - ha->pfnCompactCB(ha->pvCompactUserData, CCB_CHECKING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); - - nError = SFileAddListFile((HANDLE)ha, szListFile); - } - - // Verify the file table - if(nError == ERROR_SUCCESS) - { - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++, dwBlockIndex++) - { - // If the file exists and it's encrypted - if(pFileEntry->dwFlags & MPQ_FILE_EXISTS) - { - // If we know the name, we decrypt the file key from the file name - if(pFileEntry->szFileName != NULL && !IsPseudoFileName(pFileEntry->szFileName, NULL)) - { - // Give the key to the caller - pFileKeys[dwBlockIndex] = DecryptFileKey(pFileEntry->szFileName, - pFileEntry->ByteOffset, - pFileEntry->dwFileSize, - pFileEntry->dwFlags); - continue; - } - - // We don't know the encryption key of this file, - // thus we cannot compact the file - nError = ERROR_UNKNOWN_FILE_NAMES; - break; - } - } - } - - return nError; -} - -static int CopyNonMpqData( - TMPQArchive * ha, - TFileStream * pSrcStream, - TFileStream * pTrgStream, - ULONGLONG & ByteOffset, - ULONGLONG & ByteCount) -{ - ULONGLONG DataSize = ByteCount; - DWORD dwToRead; - char DataBuffer[0x1000]; - int nError = ERROR_SUCCESS; - - // Copy the data - while(DataSize > 0) - { - // Get the proper size of data - dwToRead = sizeof(DataBuffer); - if(DataSize < dwToRead) - dwToRead = (DWORD)DataSize; - - // Read from the source stream - if(!FileStream_Read(pSrcStream, &ByteOffset, DataBuffer, dwToRead)) - { - nError = GetLastError(); - break; - } - - // Write to the target stream - if(!FileStream_Write(pTrgStream, NULL, DataBuffer, dwToRead)) - { - nError = GetLastError(); - break; - } - - // Update the progress - if(ha->pfnCompactCB != NULL) - { - ha->CompactBytesProcessed += dwToRead; - ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes); - } - - // Decrement the number of data to be copied - ByteOffset += dwToRead; - DataSize -= dwToRead; - } - - return nError; -} - -// Copies all file sectors into another archive. -static int CopyMpqFileSectors( - TMPQArchive * ha, - TMPQFile * hf, - TFileStream * pNewStream, - ULONGLONG MpqFilePos) // MPQ file position in the new archive -{ - TFileEntry * pFileEntry = hf->pFileEntry; - ULONGLONG RawFilePos; // Used for calculating sector offset in the old MPQ archive - DWORD dwBytesToCopy = pFileEntry->dwCmpSize; - DWORD dwPatchSize = 0; // Size of patch header - DWORD dwFileKey1 = 0; // File key used for decryption - DWORD dwFileKey2 = 0; // File key used for encryption - DWORD dwCmpSize = 0; // Compressed file size, including patch header - int nError = ERROR_SUCCESS; - - // Resolve decryption keys. Note that the file key given - // in the TMPQFile structure also includes the key adjustment - if(nError == ERROR_SUCCESS && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)) - { - dwFileKey2 = dwFileKey1 = hf->dwFileKey; - if(pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) - { - dwFileKey2 = (dwFileKey1 ^ pFileEntry->dwFileSize) - (DWORD)pFileEntry->ByteOffset; - dwFileKey2 = (dwFileKey2 + (DWORD)MpqFilePos) ^ pFileEntry->dwFileSize; - } - } - - // If we have to save patch header, do it - if(nError == ERROR_SUCCESS && hf->pPatchInfo != NULL) - { - BSWAP_ARRAY32_UNSIGNED(hf->pPatchInfo, sizeof(DWORD) * 3); - if(!FileStream_Write(pNewStream, NULL, hf->pPatchInfo, hf->pPatchInfo->dwLength)) - nError = GetLastError(); - - // Save the size of the patch info - dwPatchSize = hf->pPatchInfo->dwLength; - } - - // If we have to save sector offset table, do it. - if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL) - { - DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD)); - DWORD dwSectorOffsLen = hf->SectorOffsets[0]; - - assert((pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0); - assert(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK); - - if(SectorOffsetsCopy == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - - // Encrypt the secondary sector offset table and write it to the target file - if(nError == ERROR_SUCCESS) - { - memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen); - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwFileKey2 - 1); - - BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen); - if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorOffsLen)) - nError = GetLastError(); - - dwBytesToCopy -= dwSectorOffsLen; - dwCmpSize += dwSectorOffsLen; - } - - // Update compact progress - if(ha->pfnCompactCB != NULL) - { - ha->CompactBytesProcessed += dwSectorOffsLen; - ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); - } - - STORM_FREE(SectorOffsetsCopy); - } - - // Now we have to copy all file sectors. We do it without - // recompression, because recompression is not necessary in this case - if(nError == ERROR_SUCCESS) - { - for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++) - { - DWORD dwRawDataInSector = hf->dwSectorSize; - DWORD dwRawByteOffset = dwSector * hf->dwSectorSize; - - // Fix the raw data length if the file is compressed - if(hf->SectorOffsets != NULL) - { - dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector]; - 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 - RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset); - - // Read the file sector - if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector)) - { - nError = GetLastError(); - break; - } - - // If necessary, re-encrypt the sector - // Note: Recompression is not necessary here. Unlike encryption, - // the compression does not depend on the position of the file in MPQ. - if((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) && dwFileKey1 != dwFileKey2) - { - BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); - DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey1 + dwSector); - EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey2 + dwSector); - BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector); - } - - // Now write the sector back to the file - if(!FileStream_Write(pNewStream, NULL, hf->pbFileSector, dwRawDataInSector)) - { - nError = GetLastError(); - break; - } - - // Update compact progress - if(ha->pfnCompactCB != NULL) - { - ha->CompactBytesProcessed += dwRawDataInSector; - ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); - } - - // Adjust byte counts - dwBytesToCopy -= dwRawDataInSector; - dwCmpSize += dwRawDataInSector; - } - } - - // Copy the sector CRCs, if any - // Sector CRCs are always compressed (not imploded) and unencrypted - if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL && hf->SectorChksums != NULL) - { - DWORD dwCrcLength; - - dwCrcLength = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount]; - if(dwCrcLength != 0) - { - if(!FileStream_Read(ha->pStream, NULL, hf->SectorChksums, dwCrcLength)) - nError = GetLastError(); - - if(!FileStream_Write(pNewStream, NULL, hf->SectorChksums, dwCrcLength)) - nError = GetLastError(); - - // Update compact progress - if(ha->pfnCompactCB != NULL) - { - ha->CompactBytesProcessed += dwCrcLength; - ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes); - } - - // 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; - 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) - { - nError = WriteMpqDataMD5(pNewStream, - ha->MpqPos + MpqFilePos, - pFileEntry->dwCmpSize, - ha->pHeader->dwRawChunkSize); - } - - // Verify the number of bytes written - if(nError == ERROR_SUCCESS) - { - // At this point, number of bytes written should be exactly - // the same like the compressed file size. If it isn't, - // there's something wrong (an unknown archive version, MPQ malformation, ...) - // - // Note: Diablo savegames have very weird layout, and the file "hero" - // seems to have improper compressed size. Instead of real compressed size, - // the "dwCmpSize" member of the block table entry contains - // uncompressed size of file data + size of the sector table. - // If we compact the archive, Diablo will refuse to load the game - // - // Note: Some patch files in WOW patches don't count the patch header - // into compressed size - // - - if(!(dwCmpSize <= pFileEntry->dwCmpSize && pFileEntry->dwCmpSize <= dwCmpSize + dwPatchSize)) - { - nError = ERROR_FILE_CORRUPT; - assert(false); - } - } - - return nError; -} - -static int CopyMpqFiles(TMPQArchive * ha, LPDWORD pFileKeys, TFileStream * pNewStream) -{ - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; - TMPQFile * hf = NULL; - ULONGLONG MpqFilePos; - int nError = ERROR_SUCCESS; - - // Walk through all files and write them to the destination MPQ archive - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Copy all the file sectors - // Only do that when the file has nonzero size - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS)) - { - // Query the position where the destination file will be - FileStream_GetPos(pNewStream, &MpqFilePos); - MpqFilePos = MpqFilePos - ha->MpqPos; - - // Perform file copy ONLY if the file has nonzero size - if(pFileEntry->dwFileSize != 0) - { - // Allocate structure for the MPQ file - hf = CreateFileHandle(ha, pFileEntry); - if(hf == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Set the file decryption key - hf->dwFileKey = pFileKeys[pFileEntry - ha->pFileTable]; - - // If the file is a patch file, load the patch header - if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) - { - nError = AllocatePatchInfo(hf, true); - if(nError != ERROR_SUCCESS) - break; - } - - // Allocate buffers for file sector and sector offset table - nError = AllocateSectorBuffer(hf); - if(nError != ERROR_SUCCESS) - break; - - // Also allocate sector offset table and sector checksum table - nError = AllocateSectorOffsets(hf, true); - if(nError != ERROR_SUCCESS) - break; - - // Also load sector checksums, if any - if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) - { - nError = AllocateSectorChecksums(hf, false); - if(nError != ERROR_SUCCESS) - break; - } - - // Copy all file sectors - nError = CopyMpqFileSectors(ha, hf, pNewStream, MpqFilePos); - if(nError != ERROR_SUCCESS) - break; - - // Free buffers. This also sets "hf" to NULL. - FreeFileHandle(hf); - } - - // Note: DO NOT update the compressed size in the file entry, no matter how bad it is. - pFileEntry->ByteOffset = MpqFilePos; - } - } - - // Cleanup and exit - if(hf != NULL) - FreeFileHandle(hf); - return nError; -} - -/*****************************************************************************/ -/* Public functions */ -/*****************************************************************************/ - -//----------------------------------------------------------------------------- -// Changing hash table size - -DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - return ha->dwMaxFileCount; -} - -bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - DWORD dwNewHashTableSize = 0; - int nError = ERROR_SUCCESS; - - // Test the valid parameters - if(!IsValidMpqHandle(hMpq)) - nError = ERROR_INVALID_HANDLE; - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - nError = ERROR_ACCESS_DENIED; - if(dwMaxFileCount < ha->dwFileTableSize) - nError = ERROR_DISK_FULL; - - // ALL file names must be known in order to be able to rebuild hash table - if(nError == ERROR_SUCCESS && ha->pHashTable != NULL) - { - nError = CheckIfAllFilesKnown(ha); - if(nError == ERROR_SUCCESS) - { - // Calculate the hash table size for the new file limit - dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount); - - // Rebuild both file tables - nError = RebuildFileTable(ha, dwNewHashTableSize); - } - } - - // We always have to rebuild the (attributes) file due to file table change - if(nError == ERROR_SUCCESS) - { - // Invalidate (listfile) and (attributes) - InvalidateInternalFiles(ha); - - // Rebuild the HET table, if we have any - if(ha->pHetTable != NULL) - nError = RebuildHetTable(ha); - } - - // Return the error - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// Archive compacting - -bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK pfnCompactCB, void * pvUserData) -{ - TMPQArchive * ha = (TMPQArchive *) hMpq; - - if (!IsValidMpqHandle(hMpq)) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - ha->pfnCompactCB = pfnCompactCB; - ha->pvCompactUserData = pvUserData; - return true; -} - -bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bReserved */) -{ - TFileStream * pTempStream = NULL; - TMPQArchive * ha = (TMPQArchive *)hMpq; - ULONGLONG ByteOffset; - ULONGLONG ByteCount; - LPDWORD pFileKeys = NULL; - TCHAR szTempFile[MAX_PATH+1] = _T(""); - int nError = ERROR_SUCCESS; - - // Test the valid parameters - if(!IsValidMpqHandle(hMpq)) - nError = ERROR_INVALID_HANDLE; - if(ha->dwFlags & MPQ_FLAG_READ_ONLY) - nError = ERROR_ACCESS_DENIED; - - // If the MPQ is changed at this moment, we have to flush the archive - if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_CHANGED)) - { - SFileFlushArchive(hMpq); - } - - // Create the table with file keys - if(nError == ERROR_SUCCESS) - { - if((pFileKeys = STORM_ALLOC(DWORD, ha->dwFileTableSize)) != NULL) - memset(pFileKeys, 0, sizeof(DWORD) * ha->dwFileTableSize); - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // First of all, we have to check of we are able to decrypt all files. - // If not, sorry, but the archive cannot be compacted. - if(nError == ERROR_SUCCESS) - { - // Initialize the progress variables for compact callback - FileStream_GetSize(ha->pStream, &(ha->CompactTotalBytes)); - ha->CompactBytesProcessed = 0; - nError = CheckIfAllKeysKnown(ha, szListFile, pFileKeys); - } - - // Get the temporary file name and create it - if(nError == ERROR_SUCCESS) - { - // Create temporary file name. Prevent buffer overflow - StringCopyT(szTempFile, FileStream_GetFileName(ha->pStream), MAX_PATH); - StringCatT(szTempFile, _T(".tmp"), MAX_PATH); - - // Create temporary file - pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); - if(pTempStream == NULL) - nError = GetLastError(); - } - - // Write the data before MPQ user data (if any) - if(nError == ERROR_SUCCESS && ha->UserDataPos != 0) - { - // Inform the application about the progress - if(ha->pfnCompactCB != NULL) - ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes); - - ByteOffset = 0; - ByteCount = ha->UserDataPos; - nError = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount); - } - - // Write the MPQ user data (if any) - if(nError == ERROR_SUCCESS && ha->MpqPos > ha->UserDataPos) - { - // At this point, we assume that the user data size is equal - // to pUserData->dwHeaderOffs. - // If this assumption doesn't work, then we have an unknown version of MPQ - ByteOffset = ha->UserDataPos; - ByteCount = ha->MpqPos - ha->UserDataPos; - - assert(ha->pUserData != NULL); - assert(ha->pUserData->dwHeaderOffs == ByteCount); - nError = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount); - } - - // Write the MPQ header - if(nError == ERROR_SUCCESS) - { - TMPQHeader SaveMpqHeader; - - // Write the MPQ header to the file - memcpy(&SaveMpqHeader, ha->pHeader, ha->pHeader->dwHeaderSize); - BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1); - BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2); - BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3); - BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4); - if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize)) - nError = GetLastError(); - - // Update the progress - ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize; - } - - // Now copy all files - if(nError == ERROR_SUCCESS) - nError = CopyMpqFiles(ha, pFileKeys, pTempStream); - - // If succeeded, switch the streams - if(nError == ERROR_SUCCESS) - { - ha->dwFlags |= MPQ_FLAG_CHANGED; - if(FileStream_Replace(ha->pStream, pTempStream)) - pTempStream = NULL; - else - nError = ERROR_CAN_NOT_COMPLETE; - } - - // Final user notification - if(nError == ERROR_SUCCESS && ha->pfnCompactCB != NULL) - { - ha->CompactBytesProcessed += (ha->pHeader->dwHashTableSize * sizeof(TMPQHash)); - ha->CompactBytesProcessed += (ha->dwFileTableSize * sizeof(TMPQBlock)); - ha->pfnCompactCB(ha->pvCompactUserData, CCB_CLOSING_ARCHIVE, ha->CompactBytesProcessed, ha->CompactTotalBytes); - } - - // Cleanup and return - if(pTempStream != NULL) - FileStream_Close(pTempStream); - if(pFileKeys != NULL) - STORM_FREE(pFileKeys); - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} +/*****************************************************************************/
+/* SFileCompactArchive.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Archive compacting function */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 14.04.03 1.00 Lad Splitted from SFileCreateArchiveEx.cpp */
+/* 19.11.03 1.01 Dan Big endian handling */
+/* 21.04.13 1.02 Dea Compact callback now part of TMPQArchive */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+/*****************************************************************************/
+/* Local functions */
+/*****************************************************************************/
+
+static int CheckIfAllFilesKnown(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ DWORD dwBlockIndex = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Verify the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++, dwBlockIndex++)
+ {
+ // If there is an existing entry in the file table, check its name
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // The name must be valid and must not be a pseudo-name
+ if(pFileEntry->szFileName == NULL || IsPseudoFileName(pFileEntry->szFileName, NULL))
+ {
+ nError = ERROR_UNKNOWN_FILE_NAMES;
+ break;
+ }
+ }
+ }
+ }
+
+ return nError;
+}
+
+static int CheckIfAllKeysKnown(TMPQArchive * ha, const char * szListFile, LPDWORD pFileKeys)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ DWORD dwBlockIndex = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Add the listfile to the MPQ
+ if(szListFile != NULL)
+ {
+ // Notify the user
+ if(ha->pfnCompactCB != NULL)
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_CHECKING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+
+ nError = SFileAddListFile((HANDLE)ha, szListFile);
+ }
+
+ // Verify the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++, dwBlockIndex++)
+ {
+ // If the file exists and it's encrypted
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // If we know the name, we decrypt the file key from the file name
+ if(pFileEntry->szFileName != NULL && !IsPseudoFileName(pFileEntry->szFileName, NULL))
+ {
+ // Give the key to the caller
+ pFileKeys[dwBlockIndex] = DecryptFileKey(pFileEntry->szFileName,
+ pFileEntry->ByteOffset,
+ pFileEntry->dwFileSize,
+ pFileEntry->dwFlags);
+ continue;
+ }
+
+ // We don't know the encryption key of this file,
+ // thus we cannot compact the file
+ nError = ERROR_UNKNOWN_FILE_NAMES;
+ break;
+ }
+ }
+ }
+
+ return nError;
+}
+
+static int CopyNonMpqData(
+ TMPQArchive * ha,
+ TFileStream * pSrcStream,
+ TFileStream * pTrgStream,
+ ULONGLONG & ByteOffset,
+ ULONGLONG & ByteCount)
+{
+ ULONGLONG DataSize = ByteCount;
+ DWORD dwToRead;
+ char DataBuffer[0x1000];
+ int nError = ERROR_SUCCESS;
+
+ // Copy the data
+ while(DataSize > 0)
+ {
+ // Get the proper size of data
+ dwToRead = sizeof(DataBuffer);
+ if(DataSize < dwToRead)
+ dwToRead = (DWORD)DataSize;
+
+ // Read from the source stream
+ if(!FileStream_Read(pSrcStream, &ByteOffset, DataBuffer, dwToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Write to the target stream
+ if(!FileStream_Write(pTrgStream, NULL, DataBuffer, dwToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Update the progress
+ if(ha->pfnCompactCB != NULL)
+ {
+ ha->CompactBytesProcessed += dwToRead;
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+ }
+
+ // Decrement the number of data to be copied
+ ByteOffset += dwToRead;
+ DataSize -= dwToRead;
+ }
+
+ return nError;
+}
+
+// Copies all file sectors into another archive.
+static int CopyMpqFileSectors(
+ TMPQArchive * ha,
+ TMPQFile * hf,
+ TFileStream * pNewStream,
+ ULONGLONG MpqFilePos) // MPQ file position in the new archive
+{
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ ULONGLONG RawFilePos; // Used for calculating sector offset in the old MPQ archive
+ DWORD dwBytesToCopy = pFileEntry->dwCmpSize;
+ DWORD dwPatchSize = 0; // Size of patch header
+ DWORD dwFileKey1 = 0; // File key used for decryption
+ DWORD dwFileKey2 = 0; // File key used for encryption
+ DWORD dwCmpSize = 0; // Compressed file size, including patch header
+ int nError = ERROR_SUCCESS;
+
+ // Resolve decryption keys. Note that the file key given
+ // in the TMPQFile structure also includes the key adjustment
+ if(nError == ERROR_SUCCESS && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED))
+ {
+ dwFileKey2 = dwFileKey1 = hf->dwFileKey;
+ if(pFileEntry->dwFlags & MPQ_FILE_FIX_KEY)
+ {
+ dwFileKey2 = (dwFileKey1 ^ pFileEntry->dwFileSize) - (DWORD)pFileEntry->ByteOffset;
+ dwFileKey2 = (dwFileKey2 + (DWORD)MpqFilePos) ^ pFileEntry->dwFileSize;
+ }
+ }
+
+ // If we have to save patch header, do it
+ if(nError == ERROR_SUCCESS && hf->pPatchInfo != NULL)
+ {
+ BSWAP_ARRAY32_UNSIGNED(hf->pPatchInfo, sizeof(DWORD) * 3);
+ if(!FileStream_Write(pNewStream, NULL, hf->pPatchInfo, hf->pPatchInfo->dwLength))
+ nError = GetLastError();
+
+ // Save the size of the patch info
+ dwPatchSize = hf->pPatchInfo->dwLength;
+ }
+
+ // If we have to save sector offset table, do it.
+ if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL)
+ {
+ DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD));
+ DWORD dwSectorOffsLen = hf->SectorOffsets[0];
+
+ assert((pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) == 0);
+ assert(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
+
+ if(SectorOffsetsCopy == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Encrypt the secondary sector offset table and write it to the target file
+ if(nError == ERROR_SUCCESS)
+ {
+ memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen);
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwFileKey2 - 1);
+
+ BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen);
+ if(!FileStream_Write(pNewStream, NULL, SectorOffsetsCopy, dwSectorOffsLen))
+ nError = GetLastError();
+
+ dwBytesToCopy -= dwSectorOffsLen;
+ dwCmpSize += dwSectorOffsLen;
+ }
+
+ // Update compact progress
+ if(ha->pfnCompactCB != NULL)
+ {
+ ha->CompactBytesProcessed += dwSectorOffsLen;
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+ }
+
+ STORM_FREE(SectorOffsetsCopy);
+ }
+
+ // Now we have to copy all file sectors. We do it without
+ // recompression, because recompression is not necessary in this case
+ if(nError == ERROR_SUCCESS)
+ {
+ for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++)
+ {
+ DWORD dwRawDataInSector = hf->dwSectorSize;
+ DWORD dwRawByteOffset = dwSector * hf->dwSectorSize;
+
+ // Fix the raw data length if the file is compressed
+ if(hf->SectorOffsets != NULL)
+ {
+ dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector];
+ 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
+ RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset);
+
+ // Read the file sector
+ if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // If necessary, re-encrypt the sector
+ // Note: Recompression is not necessary here. Unlike encryption,
+ // the compression does not depend on the position of the file in MPQ.
+ if((pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) && dwFileKey1 != dwFileKey2)
+ {
+ BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
+ DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey1 + dwSector);
+ EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwFileKey2 + dwSector);
+ BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
+ }
+
+ // Now write the sector back to the file
+ if(!FileStream_Write(pNewStream, NULL, hf->pbFileSector, dwRawDataInSector))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Update compact progress
+ if(ha->pfnCompactCB != NULL)
+ {
+ ha->CompactBytesProcessed += dwRawDataInSector;
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+ }
+
+ // Adjust byte counts
+ dwBytesToCopy -= dwRawDataInSector;
+ dwCmpSize += dwRawDataInSector;
+ }
+ }
+
+ // Copy the sector CRCs, if any
+ // Sector CRCs are always compressed (not imploded) and unencrypted
+ if(nError == ERROR_SUCCESS && hf->SectorOffsets != NULL && hf->SectorChksums != NULL)
+ {
+ DWORD dwCrcLength;
+
+ dwCrcLength = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
+ if(dwCrcLength != 0)
+ {
+ if(!FileStream_Read(ha->pStream, NULL, hf->SectorChksums, dwCrcLength))
+ nError = GetLastError();
+
+ if(!FileStream_Write(pNewStream, NULL, hf->SectorChksums, dwCrcLength))
+ nError = GetLastError();
+
+ // Update compact progress
+ if(ha->pfnCompactCB != NULL)
+ {
+ ha->CompactBytesProcessed += dwCrcLength;
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_COMPACTING_FILES, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+ }
+
+ // 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;
+ 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)
+ {
+ nError = WriteMpqDataMD5(pNewStream,
+ ha->MpqPos + MpqFilePos,
+ pFileEntry->dwCmpSize,
+ ha->pHeader->dwRawChunkSize);
+ }
+
+ // Verify the number of bytes written
+ if(nError == ERROR_SUCCESS)
+ {
+ // At this point, number of bytes written should be exactly
+ // the same like the compressed file size. If it isn't,
+ // there's something wrong (an unknown archive version, MPQ malformation, ...)
+ //
+ // Note: Diablo savegames have very weird layout, and the file "hero"
+ // seems to have improper compressed size. Instead of real compressed size,
+ // the "dwCmpSize" member of the block table entry contains
+ // uncompressed size of file data + size of the sector table.
+ // If we compact the archive, Diablo will refuse to load the game
+ //
+ // Note: Some patch files in WOW patches don't count the patch header
+ // into compressed size
+ //
+
+ if(!(dwCmpSize <= pFileEntry->dwCmpSize && pFileEntry->dwCmpSize <= dwCmpSize + dwPatchSize))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ assert(false);
+ }
+ }
+
+ return nError;
+}
+
+static int CopyMpqFiles(TMPQArchive * ha, LPDWORD pFileKeys, TFileStream * pNewStream)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ TMPQFile * hf = NULL;
+ ULONGLONG MpqFilePos;
+ int nError = ERROR_SUCCESS;
+
+ // Walk through all files and write them to the destination MPQ archive
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Copy all the file sectors
+ // Only do that when the file has nonzero size
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS))
+ {
+ // Query the position where the destination file will be
+ FileStream_GetPos(pNewStream, &MpqFilePos);
+ MpqFilePos = MpqFilePos - ha->MpqPos;
+
+ // Perform file copy ONLY if the file has nonzero size
+ if(pFileEntry->dwFileSize != 0)
+ {
+ // Allocate structure for the MPQ file
+ hf = CreateFileHandle(ha, pFileEntry);
+ if(hf == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Set the file decryption key
+ hf->dwFileKey = pFileKeys[pFileEntry - ha->pFileTable];
+
+ // If the file is a patch file, load the patch header
+ if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
+ {
+ nError = AllocatePatchInfo(hf, true);
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+
+ // Allocate buffers for file sector and sector offset table
+ nError = AllocateSectorBuffer(hf);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Also allocate sector offset table and sector checksum table
+ nError = AllocateSectorOffsets(hf, true);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Also load sector checksums, if any
+ if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
+ {
+ nError = AllocateSectorChecksums(hf, false);
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+
+ // Copy all file sectors
+ nError = CopyMpqFileSectors(ha, hf, pNewStream, MpqFilePos);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Free buffers. This also sets "hf" to NULL.
+ FreeFileHandle(hf);
+ }
+
+ // Note: DO NOT update the compressed size in the file entry, no matter how bad it is.
+ pFileEntry->ByteOffset = MpqFilePos;
+ }
+ }
+
+ // Cleanup and exit
+ if(hf != NULL)
+ FreeFileHandle(hf);
+ return nError;
+}
+
+/*****************************************************************************/
+/* Public functions */
+/*****************************************************************************/
+
+//-----------------------------------------------------------------------------
+// Changing hash table size
+
+DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ return ha->dwMaxFileCount;
+}
+
+bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ DWORD dwNewHashTableSize = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Test the valid parameters
+ if(!IsValidMpqHandle(hMpq))
+ nError = ERROR_INVALID_HANDLE;
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ nError = ERROR_ACCESS_DENIED;
+ if(dwMaxFileCount < ha->dwFileTableSize)
+ nError = ERROR_DISK_FULL;
+
+ // ALL file names must be known in order to be able to rebuild hash table
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ {
+ nError = CheckIfAllFilesKnown(ha);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Calculate the hash table size for the new file limit
+ dwNewHashTableSize = GetHashTableSizeForFileCount(dwMaxFileCount);
+
+ // Rebuild both file tables
+ nError = RebuildFileTable(ha, dwNewHashTableSize);
+ }
+ }
+
+ // We always have to rebuild the (attributes) file due to file table change
+ if(nError == ERROR_SUCCESS)
+ {
+ // Invalidate (listfile) and (attributes)
+ InvalidateInternalFiles(ha);
+
+ // Rebuild the HET table, if we have any
+ if(ha->pHetTable != NULL)
+ nError = RebuildHetTable(ha);
+ }
+
+ // Return the error
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// Archive compacting
+
+bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK pfnCompactCB, void * pvUserData)
+{
+ TMPQArchive * ha = (TMPQArchive *) hMpq;
+
+ if (!IsValidMpqHandle(hMpq))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ ha->pfnCompactCB = pfnCompactCB;
+ ha->pvCompactUserData = pvUserData;
+ return true;
+}
+
+bool WINAPI SFileCompactArchive(HANDLE hMpq, const char * szListFile, bool /* bReserved */)
+{
+ TFileStream * pTempStream = NULL;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ ULONGLONG ByteOffset;
+ ULONGLONG ByteCount;
+ LPDWORD pFileKeys = NULL;
+ TCHAR szTempFile[MAX_PATH+1] = _T("");
+ int nError = ERROR_SUCCESS;
+
+ // Test the valid parameters
+ if(!IsValidMpqHandle(hMpq))
+ nError = ERROR_INVALID_HANDLE;
+ if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
+ nError = ERROR_ACCESS_DENIED;
+
+ // If the MPQ is changed at this moment, we have to flush the archive
+ if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_CHANGED))
+ {
+ SFileFlushArchive(hMpq);
+ }
+
+ // Create the table with file keys
+ if(nError == ERROR_SUCCESS)
+ {
+ if((pFileKeys = STORM_ALLOC(DWORD, ha->dwFileTableSize)) != NULL)
+ memset(pFileKeys, 0, sizeof(DWORD) * ha->dwFileTableSize);
+ else
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // First of all, we have to check of we are able to decrypt all files.
+ // If not, sorry, but the archive cannot be compacted.
+ if(nError == ERROR_SUCCESS)
+ {
+ // Initialize the progress variables for compact callback
+ FileStream_GetSize(ha->pStream, &(ha->CompactTotalBytes));
+ ha->CompactBytesProcessed = 0;
+ nError = CheckIfAllKeysKnown(ha, szListFile, pFileKeys);
+ }
+
+ // Get the temporary file name and create it
+ if(nError == ERROR_SUCCESS)
+ {
+ // Create temporary file name. Prevent buffer overflow
+ StringCopyT(szTempFile, FileStream_GetFileName(ha->pStream), MAX_PATH);
+ StringCatT(szTempFile, _T(".tmp"), MAX_PATH);
+
+ // Create temporary file
+ pTempStream = FileStream_CreateFile(szTempFile, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
+ if(pTempStream == NULL)
+ nError = GetLastError();
+ }
+
+ // Write the data before MPQ user data (if any)
+ if(nError == ERROR_SUCCESS && ha->UserDataPos != 0)
+ {
+ // Inform the application about the progress
+ if(ha->pfnCompactCB != NULL)
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_COPYING_NON_MPQ_DATA, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+
+ ByteOffset = 0;
+ ByteCount = ha->UserDataPos;
+ nError = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount);
+ }
+
+ // Write the MPQ user data (if any)
+ if(nError == ERROR_SUCCESS && ha->MpqPos > ha->UserDataPos)
+ {
+ // At this point, we assume that the user data size is equal
+ // to pUserData->dwHeaderOffs.
+ // If this assumption doesn't work, then we have an unknown version of MPQ
+ ByteOffset = ha->UserDataPos;
+ ByteCount = ha->MpqPos - ha->UserDataPos;
+
+ assert(ha->pUserData != NULL);
+ assert(ha->pUserData->dwHeaderOffs == ByteCount);
+ nError = CopyNonMpqData(ha, ha->pStream, pTempStream, ByteOffset, ByteCount);
+ }
+
+ // Write the MPQ header
+ if(nError == ERROR_SUCCESS)
+ {
+ TMPQHeader SaveMpqHeader;
+
+ // Write the MPQ header to the file
+ memcpy(&SaveMpqHeader, ha->pHeader, ha->pHeader->dwHeaderSize);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_2);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_3);
+ BSWAP_TMPQHEADER(&SaveMpqHeader, MPQ_FORMAT_VERSION_4);
+ if(!FileStream_Write(pTempStream, NULL, &SaveMpqHeader, ha->pHeader->dwHeaderSize))
+ nError = GetLastError();
+
+ // Update the progress
+ ha->CompactBytesProcessed += ha->pHeader->dwHeaderSize;
+ }
+
+ // Now copy all files
+ if(nError == ERROR_SUCCESS)
+ nError = CopyMpqFiles(ha, pFileKeys, pTempStream);
+
+ // If succeeded, switch the streams
+ if(nError == ERROR_SUCCESS)
+ {
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+ if(FileStream_Replace(ha->pStream, pTempStream))
+ pTempStream = NULL;
+ else
+ nError = ERROR_CAN_NOT_COMPLETE;
+ }
+
+ // Final user notification
+ if(nError == ERROR_SUCCESS && ha->pfnCompactCB != NULL)
+ {
+ ha->CompactBytesProcessed += (ha->pHeader->dwHashTableSize * sizeof(TMPQHash));
+ ha->CompactBytesProcessed += (ha->dwFileTableSize * sizeof(TMPQBlock));
+ ha->pfnCompactCB(ha->pvCompactUserData, CCB_CLOSING_ARCHIVE, ha->CompactBytesProcessed, ha->CompactTotalBytes);
+ }
+
+ // Cleanup and return
+ if(pTempStream != NULL)
+ FileStream_Close(pTempStream);
+ if(pFileKeys != NULL)
+ STORM_FREE(pFileKeys);
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
diff --git a/src/SFileFindFile.cpp b/src/SFileFindFile.cpp index 3e6854b..aacba12 100644 --- a/src/SFileFindFile.cpp +++ b/src/SFileFindFile.cpp @@ -1,484 +1,476 @@ -/*****************************************************************************/ -/* SFileFindFile.cpp Copyright (c) Ladislav Zezula 2003 */ -/*---------------------------------------------------------------------------*/ -/* A module for file searching within MPQs */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 25.03.03 1.00 Lad The first version of SFileFindFile.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Defines - -#define LISTFILE_CACHE_SIZE 0x1000 - -//----------------------------------------------------------------------------- -// Private structure used for file search (search handle) - -struct TMPQSearch; -typedef int (*MPQSEARCH)(TMPQSearch *, SFILE_FIND_DATA *); - -// Used by searching in MPQ archives -struct TMPQSearch -{ - TMPQArchive * ha; // Handle to MPQ, where the search runs - TFileEntry ** pSearchTable; // Table for files that have been already found - 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[1]; // Search mask (variable length) -}; - -//----------------------------------------------------------------------------- -// Local functions - -static TMPQSearch * IsValidSearchHandle(HANDLE hFind) -{ - TMPQSearch * hs = (TMPQSearch *)hFind; - - if(hs != NULL && IsValidMpqHandle(hs->ha)) - return hs; - - return NULL; -} - -bool CheckWildCard(const char * szString, const char * szWildCard) -{ - const char * szWildCardPtr; - - for(;;) - { - // If there is '?' in the wildcard, we skip one char - while(szWildCard[0] == '?') - { - if(szString[0] == 0) - return false; - - szWildCard++; - szString++; - } - - // Handle '*' - szWildCardPtr = szWildCard; - if(szWildCardPtr[0] != 0) - { - if(szWildCardPtr[0] == '*') - { - szWildCardPtr++; - - if(szWildCardPtr[0] == '*') - continue; - - if(szWildCardPtr[0] == 0) - return true; - - if(AsciiToUpperTable[szWildCardPtr[0]] == AsciiToUpperTable[szString[0]]) - { - if(CheckWildCard(szString, szWildCardPtr)) - return true; - } - } - else - { - if(AsciiToUpperTable[szWildCardPtr[0]] != AsciiToUpperTable[szString[0]]) - return false; - - szWildCard = szWildCardPtr + 1; - } - - if(szString[0] == 0) - return false; - szString++; - } - else - { - return (szString[0] == 0) ? true : false; - } - } -} - -static DWORD GetSearchTableItems(TMPQArchive * ha) -{ - DWORD dwMergeItems = 0; - - // Loop over all patches - while(ha != NULL) - { - // Append the number of files - dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount - : ha->pHeader->dwBlockTableSize; - // Move to the patched archive - ha = ha->haPatch; - } - - // Return the double size of number of items - return (dwMergeItems | 1); -} - -static bool FileWasFoundBefore( - TMPQArchive * ha, - TMPQSearch * hs, - TFileEntry * pFileEntry) -{ - TFileEntry * pEntry; - char * szRealFileName = pFileEntry->szFileName; - DWORD dwStartIndex; - DWORD dwNameHash; - DWORD dwIndex; - - if(hs->pSearchTable != NULL && szRealFileName != NULL) - { - // If we are in patch MPQ, we check if patch prefix matches - // and then trim the patch prefix - if(ha->pPatchPrefix != NULL) - { - // If the patch prefix doesn't fit, we pretend that the file - // was there before and it will be skipped - if(_strnicmp(szRealFileName, ha->pPatchPrefix->szPatchPrefix, ha->pPatchPrefix->nLength)) - return true; - - szRealFileName += ha->pPatchPrefix->nLength; - } - - // Calculate the hash to the table - dwNameHash = HashString(szRealFileName, MPQ_HASH_NAME_A); - dwStartIndex = dwIndex = (dwNameHash % hs->dwSearchTableItems); - - // The file might have been found before - // only if this is not the first MPQ being searched - if(ha->haBase != NULL) - { - // Enumerate all entries in the search table - for(;;) - { - // Get the file entry at that position - pEntry = hs->pSearchTable[dwIndex]; - if(pEntry == NULL) - break; - - if(pEntry->szFileName != NULL) - { - // Does the name match? - if(!_stricmp(pEntry->szFileName, szRealFileName)) - return true; - } - - // Move to the next entry - dwIndex = (dwIndex + 1) % hs->dwSearchTableItems; - if(dwIndex == dwStartIndex) - break; - } - } - - // Put the entry to the table for later use - hs->pSearchTable[dwIndex] = pFileEntry; - } - return false; -} - -static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry) -{ - TFileEntry * pPatchEntry = NULL; - TFileEntry * pTempEntry; - char szFileName[MAX_PATH+1]; - - // Go while there are patches - while(ha->haPatch != NULL) - { - // Move to the patch archive - ha = ha->haPatch; - szFileName[0] = 0; - - // Prepare the prefix for the file name - if(ha->pPatchPrefix != NULL) - StringCopyA(szFileName, ha->pPatchPrefix->szPatchPrefix, MAX_PATH); - StringCatA(szFileName, pFileEntry->szFileName, MAX_PATH); - - // Try to find the file there - pTempEntry = GetFileEntryExact(ha, szFileName, 0, NULL); - if(pTempEntry != NULL) - pPatchEntry = pTempEntry; - } - - // Return the found patch entry - return pPatchEntry; -} - -static bool DoMPQSearch_FileEntry( - TMPQSearch * hs, - SFILE_FIND_DATA * lpFindFileData, - TMPQArchive * ha, - TMPQHash * pHashEntry, - TFileEntry * pFileEntry) -{ - TFileEntry * pPatchEntry; - HANDLE hFile = NULL; - const char * szFileName; - size_t nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0; - DWORD dwBlockIndex; - char szNameBuff[MAX_PATH]; - - // Is it a file but not a patch file? - if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS) - { - // Now we have to check if this file was not enumerated before - if(!FileWasFoundBefore(ha, hs, pFileEntry)) - { -// if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy")) -// DebugBreak(); - - // Find a patch to this file - pPatchEntry = FindPatchEntry(ha, pFileEntry); - if(pPatchEntry == NULL) - pPatchEntry = pFileEntry; - - // Prepare the block index - dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); - - // Get the file name. If it's not known, we will create pseudo-name - szFileName = pFileEntry->szFileName; - if(szFileName == NULL) - { - // Open the file by its pseudo-name. - sprintf(szNameBuff, "File%08u.xxx", (unsigned int)dwBlockIndex); - if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile)) - { - SFileGetFileName(hFile, szNameBuff); - szFileName = szNameBuff; - SFileCloseFile(hFile); - } - } - - // If the file name is still NULL, we cannot include the file to search results - if(szFileName != NULL) - { - // Check the file name against the wildcard - if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask)) - { - // Fill the found entry. hash entry and block index are taken from the base MPQ - lpFindFileData->dwHashIndex = HASH_ENTRY_FREE; - lpFindFileData->dwBlockIndex = dwBlockIndex; - lpFindFileData->dwFileSize = pPatchEntry->dwFileSize; - lpFindFileData->dwFileFlags = pPatchEntry->dwFlags; - lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize; - lpFindFileData->lcLocale = 0; // pPatchEntry->lcLocale; - - // Fill the filetime - lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32); - lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime); - - // Fill-in the entries from hash table entry, if given - if(pHashEntry != NULL) - { - lpFindFileData->dwHashIndex = (DWORD)(pHashEntry - ha->pHashTable); - lpFindFileData->lcLocale = pHashEntry->lcLocale; - } - - // Fill the file name and plain file name - StringCopyA(lpFindFileData->cFileName, szFileName + nPrefixLength, MAX_PATH-1); - lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName); - return true; - } - } - } - } - - // Either not a valid item or was found before - return false; -} - -static int DoMPQSearch_HashTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha) -{ - TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - TMPQHash * pHash; - - // Parse the file table - for(pHash = ha->pHashTable + hs->dwNextIndex; pHash < pHashTableEnd; pHash++) - { - // Increment the next index for subsequent search - hs->dwNextIndex++; - - // Does this hash table entry point to a proper block table entry? - if(IsValidHashEntry(ha, pHash)) - { - // Check if this file entry should be included in the search result - if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + pHash->dwBlockIndex)) - return ERROR_SUCCESS; - } - } - - // No more files - return ERROR_NO_MORE_FILES; -} - -static int DoMPQSearch_FileTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha) -{ - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; - - // Parse the file table - for(pFileEntry = ha->pFileTable + hs->dwNextIndex; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Increment the next index for subsequent search - hs->dwNextIndex++; - - // Check if this file entry should be included in the search result - if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, NULL, pFileEntry)) - return ERROR_SUCCESS; - } - - // No more files - return ERROR_NO_MORE_FILES; -} - -// Performs one MPQ search -static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData) -{ - TMPQArchive * ha = hs->ha; - int nError; - - // Start searching with base MPQ - while(ha != NULL) - { - // If the archive has hash table, we need to use hash table - // in order to catch hash table index and file locale. - // Note: If multiple hash table entries, point to the same block entry, - // we need, to report them all - nError = (ha->pHashTable != NULL) ? DoMPQSearch_HashTable(hs, lpFindFileData, ha) - : DoMPQSearch_FileTable(hs, lpFindFileData, ha); - if(nError == ERROR_SUCCESS) - return nError; - - // If there is no more patches in the chain, stop it. - // This also keeps hs->ha non-NULL, which is required - // for freeing the handle later - if(ha->haPatch == NULL) - break; - - // Move to the next patch in the patch chain - hs->ha = ha = ha->haPatch; - hs->dwNextIndex = 0; - } - - // No more files found, return error - return ERROR_NO_MORE_FILES; -} - -static void FreeMPQSearch(TMPQSearch *& hs) -{ - if(hs != NULL) - { - if(hs->pSearchTable != NULL) - STORM_FREE(hs->pSearchTable); - STORM_FREE(hs); - hs = NULL; - } -} - -//----------------------------------------------------------------------------- -// Public functions - -HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DATA * lpFindFileData, const char * szListFile) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - TMPQSearch * hs = NULL; - size_t nSize = 0; - int nError = ERROR_SUCCESS; - - // Check for the valid parameters - if(!IsValidMpqHandle(hMpq)) - nError = ERROR_INVALID_HANDLE; - if(szMask == NULL || lpFindFileData == NULL) - nError = ERROR_INVALID_PARAMETER; - - // Include the listfile into the MPQ's internal listfile - // Note that if the listfile name is NULL, do nothing because the - // internal listfile is always included. - if(nError == ERROR_SUCCESS && szListFile != NULL && *szListFile != 0) - nError = SFileAddListFile((HANDLE)ha, szListFile); - - // Allocate the structure for MPQ search - if(nError == ERROR_SUCCESS) - { - nSize = sizeof(TMPQSearch) + strlen(szMask) + 1; - if((hs = (TMPQSearch *)STORM_ALLOC(char, nSize)) == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } - - // Perform the first search - if(nError == ERROR_SUCCESS) - { - memset(hs, 0, sizeof(TMPQSearch)); - strcpy(hs->szSearchMask, szMask); - hs->dwFlagMask = MPQ_FILE_EXISTS; - hs->ha = ha; - - // If the archive is patched archive, we have to create a merge table - // to prevent files being repeated - if(ha->haPatch != NULL) - { - hs->dwSearchTableItems = GetSearchTableItems(ha); - hs->pSearchTable = STORM_ALLOC(TFileEntry *, hs->dwSearchTableItems); - hs->dwFlagMask = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE; - if(hs->pSearchTable != NULL) - memset(hs->pSearchTable, 0, hs->dwSearchTableItems * sizeof(TFileEntry *)); - else - nError = ERROR_NOT_ENOUGH_MEMORY; - } - } - - // Perform first item searching - if(nError == ERROR_SUCCESS) - { - nError = DoMPQSearch(hs, lpFindFileData); - } - - // Cleanup - if(nError != ERROR_SUCCESS) - { - FreeMPQSearch(hs); - SetLastError(nError); - } - - // Return the result value - return (HANDLE)hs; -} - -bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) -{ - TMPQSearch * hs = IsValidSearchHandle(hFind); - int nError = ERROR_SUCCESS; - - // Check the parameters - if(hs == NULL) - nError = ERROR_INVALID_HANDLE; - if(lpFindFileData == NULL) - nError = ERROR_INVALID_PARAMETER; - - if(nError == ERROR_SUCCESS) - nError = DoMPQSearch(hs, lpFindFileData); - - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -bool WINAPI SFileFindClose(HANDLE hFind) -{ - TMPQSearch * hs = IsValidSearchHandle(hFind); - - // Check the parameters - if(hs == NULL) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - FreeMPQSearch(hs); - return true; -} +/*****************************************************************************/
+/* SFileFindFile.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* A module for file searching within MPQs */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 25.03.03 1.00 Lad The first version of SFileFindFile.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Private structure used for file search (search handle)
+
+// Used by searching in MPQ archives
+struct TMPQSearch
+{
+ TMPQArchive * ha; // Handle to MPQ, where the search runs
+ TFileEntry ** pSearchTable; // Table for files that have been already found
+ 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[1]; // Search mask (variable length)
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static TMPQSearch * IsValidSearchHandle(HANDLE hFind)
+{
+ TMPQSearch * hs = (TMPQSearch *)hFind;
+
+ if(hs != NULL && IsValidMpqHandle(hs->ha))
+ return hs;
+
+ return NULL;
+}
+
+bool CheckWildCard(const char * szString, const char * szWildCard)
+{
+ const char * szWildCardPtr;
+
+ for(;;)
+ {
+ // If there is '?' in the wildcard, we skip one char
+ while(szWildCard[0] == '?')
+ {
+ if(szString[0] == 0)
+ return false;
+
+ szWildCard++;
+ szString++;
+ }
+
+ // Handle '*'
+ szWildCardPtr = szWildCard;
+ if(szWildCardPtr[0] != 0)
+ {
+ if(szWildCardPtr[0] == '*')
+ {
+ szWildCardPtr++;
+
+ if(szWildCardPtr[0] == '*')
+ continue;
+
+ if(szWildCardPtr[0] == 0)
+ return true;
+
+ if(AsciiToUpperTable[szWildCardPtr[0]] == AsciiToUpperTable[szString[0]])
+ {
+ if(CheckWildCard(szString, szWildCardPtr))
+ return true;
+ }
+ }
+ else
+ {
+ if(AsciiToUpperTable[szWildCardPtr[0]] != AsciiToUpperTable[szString[0]])
+ return false;
+
+ szWildCard = szWildCardPtr + 1;
+ }
+
+ if(szString[0] == 0)
+ return false;
+ szString++;
+ }
+ else
+ {
+ return (szString[0] == 0) ? true : false;
+ }
+ }
+}
+
+static DWORD GetSearchTableItems(TMPQArchive * ha)
+{
+ DWORD dwMergeItems = 0;
+
+ // Loop over all patches
+ while(ha != NULL)
+ {
+ // Append the number of files
+ dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount
+ : ha->pHeader->dwBlockTableSize;
+ // Move to the patched archive
+ ha = ha->haPatch;
+ }
+
+ // Return the double size of number of items
+ return (dwMergeItems | 1);
+}
+
+static bool FileWasFoundBefore(
+ TMPQArchive * ha,
+ TMPQSearch * hs,
+ TFileEntry * pFileEntry)
+{
+ TFileEntry * pEntry;
+ char * szRealFileName = pFileEntry->szFileName;
+ DWORD dwStartIndex;
+ DWORD dwNameHash;
+ DWORD dwIndex;
+
+ if(hs->pSearchTable != NULL && szRealFileName != NULL)
+ {
+ // If we are in patch MPQ, we check if patch prefix matches
+ // and then trim the patch prefix
+ if(ha->pPatchPrefix != NULL)
+ {
+ // If the patch prefix doesn't fit, we pretend that the file
+ // was there before and it will be skipped
+ if(_strnicmp(szRealFileName, ha->pPatchPrefix->szPatchPrefix, ha->pPatchPrefix->nLength))
+ return true;
+
+ szRealFileName += ha->pPatchPrefix->nLength;
+ }
+
+ // Calculate the hash to the table
+ dwNameHash = HashString(szRealFileName, MPQ_HASH_NAME_A);
+ dwStartIndex = dwIndex = (dwNameHash % hs->dwSearchTableItems);
+
+ // The file might have been found before
+ // only if this is not the first MPQ being searched
+ if(ha->haBase != NULL)
+ {
+ // Enumerate all entries in the search table
+ for(;;)
+ {
+ // Get the file entry at that position
+ pEntry = hs->pSearchTable[dwIndex];
+ if(pEntry == NULL)
+ break;
+
+ if(pEntry->szFileName != NULL)
+ {
+ // Does the name match?
+ if(!_stricmp(pEntry->szFileName, szRealFileName))
+ return true;
+ }
+
+ // Move to the next entry
+ dwIndex = (dwIndex + 1) % hs->dwSearchTableItems;
+ if(dwIndex == dwStartIndex)
+ break;
+ }
+ }
+
+ // Put the entry to the table for later use
+ hs->pSearchTable[dwIndex] = pFileEntry;
+ }
+ return false;
+}
+
+static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
+{
+ TFileEntry * pPatchEntry = NULL;
+ TFileEntry * pTempEntry;
+ char szFileName[MAX_PATH+1];
+
+ // Go while there are patches
+ while(ha->haPatch != NULL)
+ {
+ // Move to the patch archive
+ ha = ha->haPatch;
+ szFileName[0] = 0;
+
+ // Prepare the prefix for the file name
+ if(ha->pPatchPrefix != NULL)
+ StringCopyA(szFileName, ha->pPatchPrefix->szPatchPrefix, MAX_PATH);
+ StringCatA(szFileName, pFileEntry->szFileName, MAX_PATH);
+
+ // Try to find the file there
+ pTempEntry = GetFileEntryExact(ha, szFileName, 0, NULL);
+ if(pTempEntry != NULL)
+ pPatchEntry = pTempEntry;
+ }
+
+ // Return the found patch entry
+ return pPatchEntry;
+}
+
+static bool DoMPQSearch_FileEntry(
+ TMPQSearch * hs,
+ SFILE_FIND_DATA * lpFindFileData,
+ TMPQArchive * ha,
+ TMPQHash * pHashEntry,
+ TFileEntry * pFileEntry)
+{
+ TFileEntry * pPatchEntry;
+ HANDLE hFile = NULL;
+ const char * szFileName;
+ size_t nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0;
+ DWORD dwBlockIndex;
+ char szNameBuff[MAX_PATH];
+
+ // Is it a file but not a patch file?
+ if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS)
+ {
+ // Now we have to check if this file was not enumerated before
+ if(!FileWasFoundBefore(ha, hs, pFileEntry))
+ {
+// if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy"))
+// DebugBreak();
+
+ // Find a patch to this file
+ pPatchEntry = FindPatchEntry(ha, pFileEntry);
+ if(pPatchEntry == NULL)
+ pPatchEntry = pFileEntry;
+
+ // Prepare the block index
+ dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
+
+ // Get the file name. If it's not known, we will create pseudo-name
+ szFileName = pFileEntry->szFileName;
+ if(szFileName == NULL)
+ {
+ // Open the file by its pseudo-name.
+ sprintf(szNameBuff, "File%08u.xxx", (unsigned int)dwBlockIndex);
+ if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile))
+ {
+ SFileGetFileName(hFile, szNameBuff);
+ szFileName = szNameBuff;
+ SFileCloseFile(hFile);
+ }
+ }
+
+ // If the file name is still NULL, we cannot include the file to search results
+ if(szFileName != NULL)
+ {
+ // Check the file name against the wildcard
+ if(CheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
+ {
+ // Fill the found entry. hash entry and block index are taken from the base MPQ
+ lpFindFileData->dwHashIndex = HASH_ENTRY_FREE;
+ lpFindFileData->dwBlockIndex = dwBlockIndex;
+ lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
+ lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
+ lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
+ lpFindFileData->lcLocale = 0; // pPatchEntry->lcLocale;
+
+ // Fill the filetime
+ lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
+ lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
+
+ // Fill-in the entries from hash table entry, if given
+ if(pHashEntry != NULL)
+ {
+ lpFindFileData->dwHashIndex = (DWORD)(pHashEntry - ha->pHashTable);
+ lpFindFileData->lcLocale = pHashEntry->lcLocale;
+ }
+
+ // Fill the file name and plain file name
+ StringCopyA(lpFindFileData->cFileName, szFileName + nPrefixLength, MAX_PATH-1);
+ lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName);
+ return true;
+ }
+ }
+ }
+ }
+
+ // Either not a valid item or was found before
+ return false;
+}
+
+static int DoMPQSearch_HashTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha)
+{
+ TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ TMPQHash * pHash;
+
+ // Parse the file table
+ for(pHash = ha->pHashTable + hs->dwNextIndex; pHash < pHashTableEnd; pHash++)
+ {
+ // Increment the next index for subsequent search
+ hs->dwNextIndex++;
+
+ // Does this hash table entry point to a proper block table entry?
+ if(IsValidHashEntry(ha, pHash))
+ {
+ // Check if this file entry should be included in the search result
+ if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + pHash->dwBlockIndex))
+ return ERROR_SUCCESS;
+ }
+ }
+
+ // No more files
+ return ERROR_NO_MORE_FILES;
+}
+
+static int DoMPQSearch_FileTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+
+ // Parse the file table
+ for(pFileEntry = ha->pFileTable + hs->dwNextIndex; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Increment the next index for subsequent search
+ hs->dwNextIndex++;
+
+ // Check if this file entry should be included in the search result
+ if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, NULL, pFileEntry))
+ return ERROR_SUCCESS;
+ }
+
+ // No more files
+ return ERROR_NO_MORE_FILES;
+}
+
+// Performs one MPQ search
+static int DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
+{
+ TMPQArchive * ha = hs->ha;
+ int nError;
+
+ // Start searching with base MPQ
+ while(ha != NULL)
+ {
+ // If the archive has hash table, we need to use hash table
+ // in order to catch hash table index and file locale.
+ // Note: If multiple hash table entries, point to the same block entry,
+ // we need, to report them all
+ nError = (ha->pHashTable != NULL) ? DoMPQSearch_HashTable(hs, lpFindFileData, ha)
+ : DoMPQSearch_FileTable(hs, lpFindFileData, ha);
+ if(nError == ERROR_SUCCESS)
+ return nError;
+
+ // If there is no more patches in the chain, stop it.
+ // This also keeps hs->ha non-NULL, which is required
+ // for freeing the handle later
+ if(ha->haPatch == NULL)
+ break;
+
+ // Move to the next patch in the patch chain
+ hs->ha = ha = ha->haPatch;
+ hs->dwNextIndex = 0;
+ }
+
+ // No more files found, return error
+ return ERROR_NO_MORE_FILES;
+}
+
+static void FreeMPQSearch(TMPQSearch *& hs)
+{
+ if(hs != NULL)
+ {
+ if(hs->pSearchTable != NULL)
+ STORM_FREE(hs->pSearchTable);
+ STORM_FREE(hs);
+ hs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DATA * lpFindFileData, const char * szListFile)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TMPQSearch * hs = NULL;
+ size_t nSize = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Check for the valid parameters
+ if(!IsValidMpqHandle(hMpq))
+ nError = ERROR_INVALID_HANDLE;
+ if(szMask == NULL || lpFindFileData == NULL)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // Include the listfile into the MPQ's internal listfile
+ // Note that if the listfile name is NULL, do nothing because the
+ // internal listfile is always included.
+ if(nError == ERROR_SUCCESS && szListFile != NULL && *szListFile != 0)
+ nError = SFileAddListFile((HANDLE)ha, szListFile);
+
+ // Allocate the structure for MPQ search
+ if(nError == ERROR_SUCCESS)
+ {
+ nSize = sizeof(TMPQSearch) + strlen(szMask) + 1;
+ if((hs = (TMPQSearch *)STORM_ALLOC(char, nSize)) == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Perform the first search
+ if(nError == ERROR_SUCCESS)
+ {
+ memset(hs, 0, sizeof(TMPQSearch));
+ strcpy(hs->szSearchMask, szMask);
+ hs->dwFlagMask = MPQ_FILE_EXISTS;
+ hs->ha = ha;
+
+ // If the archive is patched archive, we have to create a merge table
+ // to prevent files being repeated
+ if(ha->haPatch != NULL)
+ {
+ hs->dwSearchTableItems = GetSearchTableItems(ha);
+ hs->pSearchTable = STORM_ALLOC(TFileEntry *, hs->dwSearchTableItems);
+ hs->dwFlagMask = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE;
+ if(hs->pSearchTable != NULL)
+ memset(hs->pSearchTable, 0, hs->dwSearchTableItems * sizeof(TFileEntry *));
+ else
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Perform first item searching
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = DoMPQSearch(hs, lpFindFileData);
+ }
+
+ // Cleanup
+ if(nError != ERROR_SUCCESS)
+ {
+ FreeMPQSearch(hs);
+ SetLastError(nError);
+ }
+
+ // Return the result value
+ return (HANDLE)hs;
+}
+
+bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
+{
+ TMPQSearch * hs = IsValidSearchHandle(hFind);
+ int nError = ERROR_SUCCESS;
+
+ // Check the parameters
+ if(hs == NULL)
+ nError = ERROR_INVALID_HANDLE;
+ if(lpFindFileData == NULL)
+ nError = ERROR_INVALID_PARAMETER;
+
+ if(nError == ERROR_SUCCESS)
+ nError = DoMPQSearch(hs, lpFindFileData);
+
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+bool WINAPI SFileFindClose(HANDLE hFind)
+{
+ TMPQSearch * hs = IsValidSearchHandle(hFind);
+
+ // Check the parameters
+ if(hs == NULL)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ FreeMPQSearch(hs);
+ return true;
+}
diff --git a/src/SFileGetFileInfo.cpp b/src/SFileGetFileInfo.cpp index bf6273d..9874793 100644 --- a/src/SFileGetFileInfo.cpp +++ b/src/SFileGetFileInfo.cpp @@ -1,992 +1,992 @@ -/*****************************************************************************/ -/* SFileGetFileInfo.cpp Copyright (c) Ladislav Zezula 2013 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 30.11.13 1.00 Lad The first version of SFileGetFileInfo.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Local defines - -// Information types for SFileGetFileInfo -#define SFILE_INFO_TYPE_INVALID_HANDLE 0 -#define SFILE_INFO_TYPE_NOT_FOUND 1 -#define SFILE_INFO_TYPE_DIRECT_POINTER 2 -#define SFILE_INFO_TYPE_ALLOCATED 3 -#define SFILE_INFO_TYPE_READ_FROM_FILE 4 -#define SFILE_INFO_TYPE_TABLE_POINTER 5 -#define SFILE_INFO_TYPE_FILE_ENTRY 6 - -//----------------------------------------------------------------------------- -// Local functions - -static void ConvertFileEntryToSelfRelative(TFileEntry * pFileEntry, TFileEntry * pSrcFileEntry) -{ - // Copy the file entry itself - memcpy(pFileEntry, pSrcFileEntry, sizeof(TFileEntry)); - - // If source is NULL, leave it NULL - if(pSrcFileEntry->szFileName != NULL) - { - // Set the file name pointer after the file entry - pFileEntry->szFileName = (char *)(pFileEntry + 1); - strcpy(pFileEntry->szFileName, pSrcFileEntry->szFileName); - } -} - - -static DWORD GetMpqFileCount(TMPQArchive * ha) -{ - TFileEntry * pFileTableEnd; - TFileEntry * pFileEntry; - DWORD dwFileCount = 0; - - // Go through all open MPQs, including patches - while(ha != NULL) - { - // Only count files that are not patch files - pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // If the file is patch file and this is not primary archive, skip it - // BUGBUG: This errorneously counts non-patch files that are in both - // base MPQ and in patches, and increases the number of files by cca 50% - if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS) - dwFileCount++; - } - - // Move to the next patch archive - ha = ha->haPatch; - } - - return dwFileCount; -} - -static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, DWORD * pcbLengthNeeded) -{ - TMPQFile * hfTemp; - TCHAR * szFileInfo = (TCHAR *)pvFileInfo; - size_t cchCharsNeeded = 1; - size_t cchFileInfo = (cbFileInfo / sizeof(TCHAR)); - size_t nLength; - - // Patch chain is only supported on MPQ files. - if(hf->pStream != NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // Calculate the necessary length of the multi-string - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch) - cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1; - - // Give the caller the needed length - if(pcbLengthNeeded != NULL) - pcbLengthNeeded[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); - - // If the caller gave both buffer pointer and data length, - // try to copy the patch chain - if(szFileInfo != NULL && cchFileInfo != 0) - { - // If there is enough space in the buffer, copy the patch chain - if(cchCharsNeeded > cchFileInfo) - { - SetLastError(ERROR_INSUFFICIENT_BUFFER); - return false; - } - - // Copy each patch - for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch) - { - // Get the file name and its length - const TCHAR * szFileName = FileStream_GetFileName(hfTemp->ha->pStream); - nLength = _tcslen(szFileName) + 1; - - // Copy the file name - memcpy(szFileInfo, szFileName, nLength * sizeof(TCHAR)); - szFileInfo += nLength; - } - - // Make it multi-string - szFileInfo[0] = 0; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Retrieves an information about an archive or about a file within the archive -// -// hMpqOrFile - Handle to an MPQ archive or to a file -// InfoClass - Information to obtain -// pvFileInfo - Pointer to buffer to store the information -// cbFileInfo - Size of the buffer pointed by pvFileInfo -// pcbLengthNeeded - Receives number of bytes necessary to store the information - -bool WINAPI SFileGetFileInfo( - HANDLE hMpqOrFile, - SFileInfoClass InfoClass, - void * pvFileInfo, - DWORD cbFileInfo, - LPDWORD pcbLengthNeeded) -{ - MPQ_SIGNATURE_INFO SignatureInfo; - TMPQArchive * ha = NULL; - TFileEntry * pFileEntry = NULL; - ULONGLONG Int64Value = 0; - ULONGLONG ByteOffset = 0; - TMPQFile * hf = NULL; - void * pvSrcFileInfo = NULL; - DWORD cbSrcFileInfo = 0; - DWORD dwInt32Value = 0; - int nInfoType = SFILE_INFO_TYPE_INVALID_HANDLE; - int nError = ERROR_SUCCESS; - - switch(InfoClass) - { - case SFileMpqFileName: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = (void *)FileStream_GetFileName(ha->pStream); - cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqStreamBitmap: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - return FileStream_GetBitmap(ha->pStream, pvFileInfo, cbFileInfo, pcbLengthNeeded); - break; - - case SFileMpqUserDataOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pUserData != NULL) - { - pvSrcFileInfo = &ha->UserDataPos; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqUserDataHeader: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pUserData != NULL) - { - ByteOffset = ha->UserDataPos; - cbSrcFileInfo = sizeof(TMPQUserData); - nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; - } - } - break; - - case SFileMpqUserData: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pUserData != NULL) - { - ByteOffset = ha->UserDataPos + sizeof(TMPQUserData); - cbSrcFileInfo = ha->pUserData->dwHeaderOffs - sizeof(TMPQUserData); - nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; - } - } - break; - - case SFileMpqHeaderOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->MpqPos; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHeaderSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->dwHeaderSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHeader: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - ByteOffset = ha->MpqPos; - cbSrcFileInfo = ha->pHeader->dwHeaderSize; - nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE; - } - break; - - case SFileMpqHetTableOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->HetTablePos64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHetTableSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->HetTableSize64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHetHeader: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->HetTablePos64, (size_t)ha->pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE); - if(pvSrcFileInfo != NULL) - { - cbSrcFileInfo = sizeof(TMPQHetHeader); - nInfoType = SFILE_INFO_TYPE_ALLOCATED; - } - } - break; - - case SFileMpqHetTable: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - pvSrcFileInfo = LoadHetTable(ha); - if(pvSrcFileInfo != NULL) - { - cbSrcFileInfo = sizeof(void *); - nInfoType = SFILE_INFO_TYPE_TABLE_POINTER; - } - } - break; - - case SFileMpqBetTableOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->BetTablePos64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqBetTableSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->BetTableSize64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqBetHeader: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->BetTablePos64, (size_t)ha->pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE); - if(pvSrcFileInfo != NULL) - { - // It is allowed for the caller to only require BET header. - cbSrcFileInfo = sizeof(TMPQBetHeader) + ((TMPQBetHeader *)pvSrcFileInfo)->dwFlagCount * sizeof(DWORD); - if(cbFileInfo == sizeof(TMPQBetHeader)) - cbSrcFileInfo = sizeof(TMPQBetHeader); - nInfoType = SFILE_INFO_TYPE_ALLOCATED; - } - } - break; - - case SFileMpqBetTable: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - pvSrcFileInfo = LoadBetTable(ha); - if(pvSrcFileInfo != NULL) - { - cbSrcFileInfo = sizeof(void *); - nInfoType = SFILE_INFO_TYPE_TABLE_POINTER; - } - } - break; - - case SFileMpqHashTableOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - Int64Value = MAKE_OFFSET64(ha->pHeader->wHashTablePosHi, ha->pHeader->dwHashTablePos); - pvSrcFileInfo = &Int64Value; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHashTableSize64: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->HashTableSize64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHashTableSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->dwHashTableSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHashTable: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL && ha->pHashTable != NULL) - { - pvSrcFileInfo = ha->pHashTable; - cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqBlockTableOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - Int64Value = MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos); - pvSrcFileInfo = &Int64Value; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqBlockTableSize64: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->BlockTableSize64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqBlockTableSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->dwBlockTableSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqBlockTable: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos) != 0) - { - cbSrcFileInfo = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock); - if(cbFileInfo >= cbSrcFileInfo) - pvSrcFileInfo = LoadBlockTable(ha, true); - nInfoType = SFILE_INFO_TYPE_ALLOCATED; - } - } - break; - - case SFileMpqHiBlockTableOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->HiBlockTablePos64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHiBlockTableSize64: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->HiBlockTableSize64; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqHiBlockTable: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pHeader->HiBlockTablePos64 && ha->pHeader->HiBlockTableSize64) - { - assert(false); - } - } - break; - - case SFileMpqSignatures: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo)) - { - pvSrcFileInfo = &SignatureInfo.SignatureTypes; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqStrongSignatureOffset: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG)) - { - pvSrcFileInfo = &SignatureInfo.EndMpqData; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqStrongSignatureSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG)) - { - dwInt32Value = MPQ_STRONG_SIGNATURE_SIZE + 4; - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqStrongSignature: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG)) - { - pvSrcFileInfo = SignatureInfo.Signature; - cbSrcFileInfo = MPQ_STRONG_SIGNATURE_SIZE + 4; - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqArchiveSize64: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->ArchiveSize64; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqArchiveSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->pHeader->dwArchiveSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqMaxFileCount: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->dwMaxFileCount; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqFileTableSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->dwFileTableSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqSectorSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &ha->dwSectorSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqNumberOfFiles: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - dwInt32Value = GetMpqFileCount(ha); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqRawChunkSize: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - nInfoType = SFILE_INFO_TYPE_NOT_FOUND; - if(ha->pHeader->dwRawChunkSize != 0) - { - pvSrcFileInfo = &ha->pHeader->dwRawChunkSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - } - break; - - case SFileMpqStreamFlags: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - FileStream_GetFlags(ha->pStream, &dwInt32Value); - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileMpqFlags: - ha = IsValidMpqHandle(hMpqOrFile); - if(ha != NULL) - { - dwInt32Value = ha->dwFlags; - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoPatchChain: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL) - return GetFilePatchChain(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded); - break; - - case SFileInfoFileEntry: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = pFileEntry = hf->pFileEntry; - cbSrcFileInfo = sizeof(TFileEntry); - if(pFileEntry->szFileName != NULL) - cbSrcFileInfo += (DWORD)strlen(pFileEntry->szFileName) + 1; - nInfoType = SFILE_INFO_TYPE_FILE_ENTRY; - } - break; - - case SFileInfoHashEntry: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pHashEntry != NULL) - { - pvSrcFileInfo = hf->pHashEntry; - cbSrcFileInfo = sizeof(TMPQHash); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoHashIndex: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pHashEntry != NULL) - { - pvSrcFileInfo = &hf->dwHashIndex; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoNameHash1: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pHashEntry != NULL) - { - dwInt32Value = hf->pHashEntry->dwName1; - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoNameHash2: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pHashEntry != NULL) - { - dwInt32Value = hf->pHashEntry->dwName2; - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoNameHash3: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->FileNameHash; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoLocale: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pHashEntry != NULL) - { - dwInt32Value = hf->pHashEntry->lcLocale; - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoFileIndex: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->ha != NULL && hf->pFileEntry != NULL) - { - dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable); - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoByteOffset: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->ByteOffset; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoFileTime: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->FileTime; - cbSrcFileInfo = sizeof(ULONGLONG); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoFileSize: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->dwFileSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoCompressedSize: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->dwCmpSize; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoFlags: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - pvSrcFileInfo = &hf->pFileEntry->dwFlags; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoEncryptionKey: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL) - { - pvSrcFileInfo = &hf->dwFileKey; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - case SFileInfoEncryptionKeyRaw: - hf = IsValidFileHandle(hMpqOrFile); - if(hf != NULL && hf->pFileEntry != NULL) - { - dwInt32Value = hf->dwFileKey; - if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY) - dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos; - pvSrcFileInfo = &dwInt32Value; - cbSrcFileInfo = sizeof(DWORD); - nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER; - } - break; - - default: // Invalid info class - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // If we validated the handle and info class, give as much info as possible - if(nInfoType >= SFILE_INFO_TYPE_DIRECT_POINTER) - { - // Give the length needed, if wanted - if(pcbLengthNeeded != NULL) - pcbLengthNeeded[0] = cbSrcFileInfo; - - // If the caller entered an output buffer, the output size must also be entered - if(pvFileInfo != NULL && cbFileInfo != 0) - { - // Check if there is enough space in the output buffer - if(cbSrcFileInfo <= cbFileInfo) - { - switch(nInfoType) - { - case SFILE_INFO_TYPE_DIRECT_POINTER: - case SFILE_INFO_TYPE_ALLOCATED: - assert(pvSrcFileInfo != NULL); - memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo); - break; - - case SFILE_INFO_TYPE_READ_FROM_FILE: - if(!FileStream_Read(ha->pStream, &ByteOffset, pvFileInfo, cbSrcFileInfo)) - nError = GetLastError(); - break; - - case SFILE_INFO_TYPE_TABLE_POINTER: - assert(pvSrcFileInfo != NULL); - *(void **)pvFileInfo = pvSrcFileInfo; - pvSrcFileInfo = NULL; - break; - - case SFILE_INFO_TYPE_FILE_ENTRY: - assert(pFileEntry != NULL); - ConvertFileEntryToSelfRelative((TFileEntry *)pvFileInfo, pFileEntry); - break; - } - } - else - { - nError = ERROR_INSUFFICIENT_BUFFER; - } - } - - // Free the file info if needed - if(nInfoType == SFILE_INFO_TYPE_ALLOCATED && pvSrcFileInfo != NULL) - STORM_FREE(pvSrcFileInfo); - if(nInfoType == SFILE_INFO_TYPE_TABLE_POINTER && pvSrcFileInfo != NULL) - SFileFreeFileInfo(pvSrcFileInfo, InfoClass); - } - else - { - // Handle error cases - if(nInfoType == SFILE_INFO_TYPE_INVALID_HANDLE) - nError = ERROR_INVALID_HANDLE; - if(nInfoType == SFILE_INFO_TYPE_NOT_FOUND) - nError = ERROR_FILE_NOT_FOUND; - } - - // Set the last error value, if needed - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass) -{ - switch(InfoClass) - { - case SFileMpqHetTable: - FreeHetTable((TMPQHetTable *)pvFileInfo); - return true; - - case SFileMpqBetTable: - FreeBetTable((TMPQBetTable *)pvFileInfo); - return true; - - default: - break; - } - - SetLastError(ERROR_INVALID_PARAMETER); - return false; -} - -//----------------------------------------------------------------------------- -// Tries to retrieve the file name - -struct TFileHeader2Ext -{ - DWORD dwOffset00Data; // Required data at offset 00 (32-bits) - DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored - DWORD dwOffset04Data; // Required data at offset 04 (32-bits) - DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored - const char * szExt; // Supplied extension, if the condition is true -}; - -static TFileHeader2Ext data2ext[] = -{ - {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files - {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files - {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A') - {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF' - {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2' - {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new) - {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I - {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II - {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm' - {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM - {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files - {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures - {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2) - {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files - {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps - {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8' - {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2 - {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc - {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders - {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image - {0x503B4449, 0xFFFFFFFF, 0x3B4C5857, 0xFFFFFFFF, "slk"}, // SLK file (usually starts with "ID;PWXL;N;E") - {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension - {0, 0, 0, 0, NULL} // Terminator -}; - -static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file - DWORD dwBytesRead = 0; - DWORD dwFilePos; // Saved file position - - // Read the first 2 DWORDs bytes from the file - dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT); - SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL); - SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN); - - // If we read at least 8 bytes - if(dwBytesRead == sizeof(FirstBytes)) - { - // Make sure that the array is properly BSWAP-ed - BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes)); - - // Try to guess file extension from those 2 DWORDs - for(size_t i = 0; data2ext[i].szExt != NULL; i++) - { - if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data && - (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data) - { - char szPseudoName[20] = ""; - - // Format the pseudo-name - sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt); - - // Save the pseudo-name in the file entry as well - AllocateFileName(hf->ha, pFileEntry, szPseudoName); - - // If the caller wants to copy the file name, do it - if(szFileName != NULL) - strcpy(szFileName, szPseudoName); - return ERROR_SUCCESS; - } - } - } - - return ERROR_CAN_NOT_COMPLETE; -} - -bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName) -{ - TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle - int nError = ERROR_INVALID_HANDLE; - - // Check valid parameters - if(IsValidFileHandle(hFile)) - { - TFileEntry * pFileEntry = hf->pFileEntry; - - // For MPQ files, retrieve the file name from the file entry - if(hf->pStream == NULL) - { - if(pFileEntry != NULL) - { - // If the file name is not there yet, create a pseudo name - if(pFileEntry->szFileName == NULL) - nError = CreatePseudoFileName(hFile, pFileEntry, szFileName); - - // Copy the file name to the output buffer, if any - if(pFileEntry->szFileName && szFileName) - { - strcpy(szFileName, pFileEntry->szFileName); - nError = ERROR_SUCCESS; - } - } - } - - // For local files, copy the file name from the stream - else - { - if(szFileName != NULL) - { - const TCHAR * szStreamName = FileStream_GetFileName(hf->pStream); - CopyFileName(szFileName, szStreamName, _tcslen(szStreamName)); - } - nError = ERROR_SUCCESS; - } - } - - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - +/*****************************************************************************/
+/* SFileGetFileInfo.cpp Copyright (c) Ladislav Zezula 2013 */
+/*---------------------------------------------------------------------------*/
+/* Description: */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 30.11.13 1.00 Lad The first version of SFileGetFileInfo.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local defines
+
+// Information types for SFileGetFileInfo
+#define SFILE_INFO_TYPE_INVALID_HANDLE 0
+#define SFILE_INFO_TYPE_NOT_FOUND 1
+#define SFILE_INFO_TYPE_DIRECT_POINTER 2
+#define SFILE_INFO_TYPE_ALLOCATED 3
+#define SFILE_INFO_TYPE_READ_FROM_FILE 4
+#define SFILE_INFO_TYPE_TABLE_POINTER 5
+#define SFILE_INFO_TYPE_FILE_ENTRY 6
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static void ConvertFileEntryToSelfRelative(TFileEntry * pFileEntry, TFileEntry * pSrcFileEntry)
+{
+ // Copy the file entry itself
+ memcpy(pFileEntry, pSrcFileEntry, sizeof(TFileEntry));
+
+ // If source is NULL, leave it NULL
+ if(pSrcFileEntry->szFileName != NULL)
+ {
+ // Set the file name pointer after the file entry
+ pFileEntry->szFileName = (char *)(pFileEntry + 1);
+ strcpy(pFileEntry->szFileName, pSrcFileEntry->szFileName);
+ }
+}
+
+
+static DWORD GetMpqFileCount(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd;
+ TFileEntry * pFileEntry;
+ DWORD dwFileCount = 0;
+
+ // Go through all open MPQs, including patches
+ while(ha != NULL)
+ {
+ // Only count files that are not patch files
+ pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // If the file is patch file and this is not primary archive, skip it
+ // BUGBUG: This errorneously counts non-patch files that are in both
+ // base MPQ and in patches, and increases the number of files by cca 50%
+ if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS)
+ dwFileCount++;
+ }
+
+ // Move to the next patch archive
+ ha = ha->haPatch;
+ }
+
+ return dwFileCount;
+}
+
+static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, DWORD * pcbLengthNeeded)
+{
+ TMPQFile * hfTemp;
+ TCHAR * szFileInfo = (TCHAR *)pvFileInfo;
+ size_t cchCharsNeeded = 1;
+ size_t cchFileInfo = (cbFileInfo / sizeof(TCHAR));
+ size_t nLength;
+
+ // Patch chain is only supported on MPQ files.
+ if(hf->pStream != NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // Calculate the necessary length of the multi-string
+ for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch)
+ cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1;
+
+ // Give the caller the needed length
+ if(pcbLengthNeeded != NULL)
+ pcbLengthNeeded[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR));
+
+ // If the caller gave both buffer pointer and data length,
+ // try to copy the patch chain
+ if(szFileInfo != NULL && cchFileInfo != 0)
+ {
+ // If there is enough space in the buffer, copy the patch chain
+ if(cchCharsNeeded > cchFileInfo)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return false;
+ }
+
+ // Copy each patch
+ for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch)
+ {
+ // Get the file name and its length
+ const TCHAR * szFileName = FileStream_GetFileName(hfTemp->ha->pStream);
+ nLength = _tcslen(szFileName) + 1;
+
+ // Copy the file name
+ memcpy(szFileInfo, szFileName, nLength * sizeof(TCHAR));
+ szFileInfo += nLength;
+ }
+
+ // Make it multi-string
+ szFileInfo[0] = 0;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Retrieves an information about an archive or about a file within the archive
+//
+// hMpqOrFile - Handle to an MPQ archive or to a file
+// InfoClass - Information to obtain
+// pvFileInfo - Pointer to buffer to store the information
+// cbFileInfo - Size of the buffer pointed by pvFileInfo
+// pcbLengthNeeded - Receives number of bytes necessary to store the information
+
+bool WINAPI SFileGetFileInfo(
+ HANDLE hMpqOrFile,
+ SFileInfoClass InfoClass,
+ void * pvFileInfo,
+ DWORD cbFileInfo,
+ LPDWORD pcbLengthNeeded)
+{
+ MPQ_SIGNATURE_INFO SignatureInfo;
+ TMPQArchive * ha = NULL;
+ TFileEntry * pFileEntry = NULL;
+ ULONGLONG Int64Value = 0;
+ ULONGLONG ByteOffset = 0;
+ TMPQFile * hf = NULL;
+ void * pvSrcFileInfo = NULL;
+ DWORD cbSrcFileInfo = 0;
+ DWORD dwInt32Value = 0;
+ int nInfoType = SFILE_INFO_TYPE_INVALID_HANDLE;
+ int nError = ERROR_SUCCESS;
+
+ switch(InfoClass)
+ {
+ case SFileMpqFileName:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = (void *)FileStream_GetFileName(ha->pStream);
+ cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqStreamBitmap:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ return FileStream_GetBitmap(ha->pStream, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+ break;
+
+ case SFileMpqUserDataOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(ha->pUserData != NULL)
+ {
+ pvSrcFileInfo = &ha->UserDataPos;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqUserDataHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(ha->pUserData != NULL)
+ {
+ ByteOffset = ha->UserDataPos;
+ cbSrcFileInfo = sizeof(TMPQUserData);
+ nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
+ }
+ }
+ break;
+
+ case SFileMpqUserData:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(ha->pUserData != NULL)
+ {
+ ByteOffset = ha->UserDataPos + sizeof(TMPQUserData);
+ cbSrcFileInfo = ha->pUserData->dwHeaderOffs - sizeof(TMPQUserData);
+ nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
+ }
+ }
+ break;
+
+ case SFileMpqHeaderOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->MpqPos;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHeaderSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwHeaderSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ ByteOffset = ha->MpqPos;
+ cbSrcFileInfo = ha->pHeader->dwHeaderSize;
+ nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
+ }
+ break;
+
+ case SFileMpqHetTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HetTablePos64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHetTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HetTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHetHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->HetTablePos64, (size_t)ha->pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
+ if(pvSrcFileInfo != NULL)
+ {
+ cbSrcFileInfo = sizeof(TMPQHetHeader);
+ nInfoType = SFILE_INFO_TYPE_ALLOCATED;
+ }
+ }
+ break;
+
+ case SFileMpqHetTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ pvSrcFileInfo = LoadHetTable(ha);
+ if(pvSrcFileInfo != NULL)
+ {
+ cbSrcFileInfo = sizeof(void *);
+ nInfoType = SFILE_INFO_TYPE_TABLE_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqBetTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->BetTablePos64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqBetTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->BetTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqBetHeader:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->BetTablePos64, (size_t)ha->pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE);
+ if(pvSrcFileInfo != NULL)
+ {
+ // It is allowed for the caller to only require BET header.
+ cbSrcFileInfo = sizeof(TMPQBetHeader) + ((TMPQBetHeader *)pvSrcFileInfo)->dwFlagCount * sizeof(DWORD);
+ if(cbFileInfo == sizeof(TMPQBetHeader))
+ cbSrcFileInfo = sizeof(TMPQBetHeader);
+ nInfoType = SFILE_INFO_TYPE_ALLOCATED;
+ }
+ }
+ break;
+
+ case SFileMpqBetTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ pvSrcFileInfo = LoadBetTable(ha);
+ if(pvSrcFileInfo != NULL)
+ {
+ cbSrcFileInfo = sizeof(void *);
+ nInfoType = SFILE_INFO_TYPE_TABLE_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqHashTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ Int64Value = MAKE_OFFSET64(ha->pHeader->wHashTablePosHi, ha->pHeader->dwHashTablePos);
+ pvSrcFileInfo = &Int64Value;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHashTableSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HashTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHashTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwHashTableSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHashTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && ha->pHashTable != NULL)
+ {
+ pvSrcFileInfo = ha->pHashTable;
+ cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqBlockTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ Int64Value = MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos);
+ pvSrcFileInfo = &Int64Value;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqBlockTableSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->BlockTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqBlockTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwBlockTableSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqBlockTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos) != 0)
+ {
+ cbSrcFileInfo = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ if(cbFileInfo >= cbSrcFileInfo)
+ pvSrcFileInfo = LoadBlockTable(ha, true);
+ nInfoType = SFILE_INFO_TYPE_ALLOCATED;
+ }
+ }
+ break;
+
+ case SFileMpqHiBlockTableOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HiBlockTablePos64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHiBlockTableSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->HiBlockTableSize64;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqHiBlockTable:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(ha->pHeader->HiBlockTablePos64 && ha->pHeader->HiBlockTableSize64)
+ {
+ assert(false);
+ }
+ }
+ break;
+
+ case SFileMpqSignatures:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo))
+ {
+ pvSrcFileInfo = &SignatureInfo.SignatureTypes;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqStrongSignatureOffset:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG))
+ {
+ pvSrcFileInfo = &SignatureInfo.EndMpqData;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqStrongSignatureSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG))
+ {
+ dwInt32Value = MPQ_STRONG_SIGNATURE_SIZE + 4;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqStrongSignature:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG))
+ {
+ pvSrcFileInfo = SignatureInfo.Signature;
+ cbSrcFileInfo = MPQ_STRONG_SIGNATURE_SIZE + 4;
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqArchiveSize64:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->ArchiveSize64;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqArchiveSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwArchiveSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqMaxFileCount:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->dwMaxFileCount;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqFileTableSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->dwFileTableSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqSectorSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &ha->dwSectorSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqNumberOfFiles:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ dwInt32Value = GetMpqFileCount(ha);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqRawChunkSize:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
+ if(ha->pHeader->dwRawChunkSize != 0)
+ {
+ pvSrcFileInfo = &ha->pHeader->dwRawChunkSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ }
+ break;
+
+ case SFileMpqStreamFlags:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ FileStream_GetFlags(ha->pStream, &dwInt32Value);
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileMpqFlags:
+ ha = IsValidMpqHandle(hMpqOrFile);
+ if(ha != NULL)
+ {
+ dwInt32Value = ha->dwFlags;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoPatchChain:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL)
+ return GetFilePatchChain(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+ break;
+
+ case SFileInfoFileEntry:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = pFileEntry = hf->pFileEntry;
+ cbSrcFileInfo = sizeof(TFileEntry);
+ if(pFileEntry->szFileName != NULL)
+ cbSrcFileInfo += (DWORD)strlen(pFileEntry->szFileName) + 1;
+ nInfoType = SFILE_INFO_TYPE_FILE_ENTRY;
+ }
+ break;
+
+ case SFileInfoHashEntry:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pHashEntry != NULL)
+ {
+ pvSrcFileInfo = hf->pHashEntry;
+ cbSrcFileInfo = sizeof(TMPQHash);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoHashIndex:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pHashEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->dwHashIndex;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoNameHash1:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pHashEntry != NULL)
+ {
+ dwInt32Value = hf->pHashEntry->dwName1;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoNameHash2:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pHashEntry != NULL)
+ {
+ dwInt32Value = hf->pHashEntry->dwName2;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoNameHash3:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->FileNameHash;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoLocale:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pHashEntry != NULL)
+ {
+ dwInt32Value = hf->pHashEntry->lcLocale;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoFileIndex:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->ha != NULL && hf->pFileEntry != NULL)
+ {
+ dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable);
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoByteOffset:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->ByteOffset;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoFileTime:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->FileTime;
+ cbSrcFileInfo = sizeof(ULONGLONG);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoFileSize:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->dwFileSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoCompressedSize:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->dwCmpSize;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoFlags:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ pvSrcFileInfo = &hf->pFileEntry->dwFlags;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoEncryptionKey:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL)
+ {
+ pvSrcFileInfo = &hf->dwFileKey;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ case SFileInfoEncryptionKeyRaw:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ dwInt32Value = hf->dwFileKey;
+ if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY)
+ dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos;
+ pvSrcFileInfo = &dwInt32Value;
+ cbSrcFileInfo = sizeof(DWORD);
+ nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
+ }
+ break;
+
+ default: // Invalid info class
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // If we validated the handle and info class, give as much info as possible
+ if(nInfoType >= SFILE_INFO_TYPE_DIRECT_POINTER)
+ {
+ // Give the length needed, if wanted
+ if(pcbLengthNeeded != NULL)
+ pcbLengthNeeded[0] = cbSrcFileInfo;
+
+ // If the caller entered an output buffer, the output size must also be entered
+ if(pvFileInfo != NULL && cbFileInfo != 0)
+ {
+ // Check if there is enough space in the output buffer
+ if(cbSrcFileInfo <= cbFileInfo)
+ {
+ switch(nInfoType)
+ {
+ case SFILE_INFO_TYPE_DIRECT_POINTER:
+ case SFILE_INFO_TYPE_ALLOCATED:
+ assert(pvSrcFileInfo != NULL);
+ memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo);
+ break;
+
+ case SFILE_INFO_TYPE_READ_FROM_FILE:
+ if(!FileStream_Read(ha->pStream, &ByteOffset, pvFileInfo, cbSrcFileInfo))
+ nError = GetLastError();
+ break;
+
+ case SFILE_INFO_TYPE_TABLE_POINTER:
+ assert(pvSrcFileInfo != NULL);
+ *(void **)pvFileInfo = pvSrcFileInfo;
+ pvSrcFileInfo = NULL;
+ break;
+
+ case SFILE_INFO_TYPE_FILE_ENTRY:
+ assert(pFileEntry != NULL);
+ ConvertFileEntryToSelfRelative((TFileEntry *)pvFileInfo, pFileEntry);
+ break;
+ }
+ }
+ else
+ {
+ nError = ERROR_INSUFFICIENT_BUFFER;
+ }
+ }
+
+ // Free the file info if needed
+ if(nInfoType == SFILE_INFO_TYPE_ALLOCATED && pvSrcFileInfo != NULL)
+ STORM_FREE(pvSrcFileInfo);
+ if(nInfoType == SFILE_INFO_TYPE_TABLE_POINTER && pvSrcFileInfo != NULL)
+ SFileFreeFileInfo(pvSrcFileInfo, InfoClass);
+ }
+ else
+ {
+ // Handle error cases
+ if(nInfoType == SFILE_INFO_TYPE_INVALID_HANDLE)
+ nError = ERROR_INVALID_HANDLE;
+ if(nInfoType == SFILE_INFO_TYPE_NOT_FOUND)
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // Set the last error value, if needed
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass)
+{
+ switch(InfoClass)
+ {
+ case SFileMpqHetTable:
+ FreeHetTable((TMPQHetTable *)pvFileInfo);
+ return true;
+
+ case SFileMpqBetTable:
+ FreeBetTable((TMPQBetTable *)pvFileInfo);
+ return true;
+
+ default:
+ break;
+ }
+
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Tries to retrieve the file name
+
+struct TFileHeader2Ext
+{
+ DWORD dwOffset00Data; // Required data at offset 00 (32-bits)
+ DWORD dwOffset00Mask; // Mask for data at offset 00 (32 bits). 0 = data are ignored
+ DWORD dwOffset04Data; // Required data at offset 04 (32-bits)
+ DWORD dwOffset04Mask; // Mask for data at offset 04 (32 bits). 0 = data are ignored
+ const char * szExt; // Supplied extension, if the condition is true
+};
+
+static TFileHeader2Ext data2ext[] =
+{
+ {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"}, // EXE files
+ {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"}, // EXE files
+ {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"}, // MPQ archive header ID ('MPQ\x1A')
+ {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"}, // WAVE header 'RIFF'
+ {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"}, // Old "Smacker Video" files 'SMK2'
+ {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"}, // Bink video files (new)
+ {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"}, // PCX images used in Diablo I
+ {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"}, // Font files used in Diablo II
+ {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<htm'
+ {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"}, // HTML '<HTM
+ {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"}, // Table files
+ {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures
+ {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"}, // BLP textures (v2)
+ {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"}, // MDX files
+ {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"}, // Warcraft II maps
+ {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"}, // GIF images 'GIF8'
+ {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"}, // WoW ??? .m2
+ {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"}, // ??? .dbc
+ {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"}, // WoW pixel shaders
+ {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"}, // JPEG image
+ {0x503B4449, 0xFFFFFFFF, 0x3B4C5857, 0xFFFFFFFF, "slk"}, // SLK file (usually starts with "ID;PWXL;N;E")
+ {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"}, // Default extension
+ {0, 0, 0, 0, NULL} // Terminator
+};
+
+static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName)
+{
+ TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
+ DWORD FirstBytes[2] = {0, 0}; // The first 4 bytes of the file
+ DWORD dwBytesRead = 0;
+ DWORD dwFilePos; // Saved file position
+
+ // Read the first 2 DWORDs bytes from the file
+ dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT);
+ SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL);
+ SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN);
+
+ // If we read at least 8 bytes
+ if(dwBytesRead == sizeof(FirstBytes))
+ {
+ // Make sure that the array is properly BSWAP-ed
+ BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));
+
+ // Try to guess file extension from those 2 DWORDs
+ for(size_t i = 0; data2ext[i].szExt != NULL; i++)
+ {
+ if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data &&
+ (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data)
+ {
+ char szPseudoName[20] = "";
+
+ // Format the pseudo-name
+ sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt);
+
+ // Save the pseudo-name in the file entry as well
+ AllocateFileName(hf->ha, pFileEntry, szPseudoName);
+
+ // If the caller wants to copy the file name, do it
+ if(szFileName != NULL)
+ strcpy(szFileName, szPseudoName);
+ return ERROR_SUCCESS;
+ }
+ }
+ }
+
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
+{
+ TMPQFile * hf = (TMPQFile *)hFile; // MPQ File handle
+ int nError = ERROR_INVALID_HANDLE;
+
+ // Check valid parameters
+ if(IsValidFileHandle(hFile))
+ {
+ TFileEntry * pFileEntry = hf->pFileEntry;
+
+ // For MPQ files, retrieve the file name from the file entry
+ if(hf->pStream == NULL)
+ {
+ if(pFileEntry != NULL)
+ {
+ // If the file name is not there yet, create a pseudo name
+ if(pFileEntry->szFileName == NULL)
+ nError = CreatePseudoFileName(hFile, pFileEntry, szFileName);
+
+ // Copy the file name to the output buffer, if any
+ if(pFileEntry->szFileName && szFileName)
+ {
+ strcpy(szFileName, pFileEntry->szFileName);
+ nError = ERROR_SUCCESS;
+ }
+ }
+ }
+
+ // For local files, copy the file name from the stream
+ else
+ {
+ if(szFileName != NULL)
+ {
+ const TCHAR * szStreamName = FileStream_GetFileName(hf->pStream);
+ CopyFileName(szFileName, szStreamName, _tcslen(szStreamName));
+ }
+ nError = ERROR_SUCCESS;
+ }
+ }
+
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp index e47a9ea..62b8f11 100644 --- a/src/SFileListFile.cpp +++ b/src/SFileListFile.cpp @@ -1,642 +1,642 @@ -/*****************************************************************************/ -/* SListFile.cpp Copyright (c) Ladislav Zezula 2004 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 12.06.04 1.00 Lad The first version of SListFile.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" -#include <assert.h> - -//----------------------------------------------------------------------------- -// Listfile entry structure - -#define CACHE_BUFFER_SIZE 0x1000 // Size of the cache buffer -#define MAX_LISTFILE_SIZE 0x04000000 // Maximum accepted listfile size is about 68 MB - -struct TListFileCache -{ - char * szWildCard; // Self-relative pointer to file mask - LPBYTE pBegin; // The begin of the listfile cache - LPBYTE pPos; // Current position in the cache - LPBYTE pEnd; // The last character in the file cache - -// char szWildCard[wildcard_length]; // Followed by the name mask (if any) -// char szListFile[listfile_length]; // Followed by the listfile (if any) -}; - -//----------------------------------------------------------------------------- -// Local functions (cache) - -static char * CopyListLine(char * szListLine, const char * szFileName) -{ - // Copy the string - while(szFileName[0] != 0) - *szListLine++ = *szFileName++; - - // Append the end-of-line - *szListLine++ = 0x0D; - *szListLine++ = 0x0A; - return szListLine; -} - -static bool FreeListFileCache(TListFileCache * pCache) -{ - // Valid parameter check - if(pCache != NULL) - STORM_FREE(pCache); - return true; -} - -static TListFileCache * CreateListFileCache(HANDLE hListFile, const char * szWildCard) -{ - TListFileCache * pCache = NULL; - size_t cchWildCard = 0; - DWORD dwBytesRead = 0; - DWORD dwFileSize; - - // Get the amount of bytes that need to be allocated - dwFileSize = SFileGetFileSize(hListFile, NULL); - if(dwFileSize == 0 || dwFileSize > MAX_LISTFILE_SIZE) - return NULL; - - // Append buffer for name mask, if any - if(szWildCard != NULL) - cchWildCard = strlen(szWildCard) + 1; - - // Allocate cache for one file block - pCache = (TListFileCache *)STORM_ALLOC(BYTE, sizeof(TListFileCache) + cchWildCard + dwFileSize + 1); - if(pCache != NULL) - { - // Clear the entire structure - memset(pCache, 0, sizeof(TListFileCache) + cchWildCard); - - // Shall we copy the mask? - if(cchWildCard != 0) - { - pCache->szWildCard = (char *)(pCache + 1); - memcpy(pCache->szWildCard, szWildCard, cchWildCard); - } - - // Fill-in the rest of the cache pointers - pCache->pBegin = (LPBYTE)(pCache + 1) + cchWildCard; - - // Load the entire listfile to the cache - SFileReadFile(hListFile, pCache->pBegin, dwFileSize, &dwBytesRead, NULL); - if(dwBytesRead != 0) - { - // Allocate pointers - pCache->pPos = pCache->pBegin; - pCache->pEnd = pCache->pBegin + dwBytesRead; - } - else - { - FreeListFileCache(pCache); - pCache = NULL; - } - } - - // Return the cache - return pCache; -} - -#ifdef _DEBUG -/* -TMPQNameCache * CreateNameCache(HANDLE hListFile, const char * szSearchMask) -{ - TMPQNameCache * pNameCache; - char * szCachePointer; - size_t cbToAllocate; - size_t nMaskLength = 1; - DWORD dwBytesRead = 0; - DWORD dwFileSize; - - // Get the size of the listfile. Ignore zero or too long ones - dwFileSize = SFileGetFileSize(hListFile, NULL); - if(dwFileSize == 0 || dwFileSize > MAX_LISTFILE_SIZE) - return NULL; - - // Get the length of the search mask - if(szSearchMask == NULL) - szSearchMask = "*"; - nMaskLength = strlen(szSearchMask) + 1; - - // Allocate the name cache - cbToAllocate = sizeof(TMPQNameCache) + nMaskLength + dwFileSize + 1; - pNameCache = (TMPQNameCache *)STORM_ALLOC(BYTE, cbToAllocate); - if(pNameCache != NULL) - { - // Initialize the name cache - memset(pNameCache, 0, sizeof(TMPQNameCache)); - pNameCache->TotalCacheSize = (DWORD)(nMaskLength + dwFileSize + 1); - szCachePointer = (char *)(pNameCache + 1); - - // Copy the search mask, if any - memcpy(szCachePointer, szSearchMask, nMaskLength); - pNameCache->FirstNameOffset = (DWORD)nMaskLength; - pNameCache->FreeSpaceOffset = (DWORD)nMaskLength; - - // Read the listfile itself - SFileSetFilePointer(hListFile, 0, NULL, FILE_BEGIN); - SFileReadFile(hListFile, szCachePointer + nMaskLength, dwFileSize, &dwBytesRead, NULL); - - // If nothing has been read from the listfile, clear the cache - if(dwBytesRead == 0) - { - STORM_FREE(pNameCache); - return NULL; - } - - // Move the free space offset - pNameCache->FreeSpaceOffset = pNameCache->FirstNameOffset + dwBytesRead + 1; - szCachePointer[nMaskLength + dwBytesRead] = 0; - } - - return pNameCache; -} - -static void FreeNameCache(TMPQNameCache * pNameCache) -{ - if(pNameCache != NULL) - STORM_FREE(pNameCache); - pNameCache = NULL; -} -*/ -#endif // _DEBUG - -static char * ReadListFileLine(TListFileCache * pCache, size_t * PtrLength) -{ - LPBYTE pbLineBegin; - LPBYTE pbLineEnd; - LPBYTE pbExtraString = NULL; - - // Skip newlines, spaces, tabs and another non-printable stuff - while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20) - pCache->pPos++; - - // Set the line begin and end - if(pCache->pPos >= pCache->pEnd) - return NULL; - pbLineBegin = pbLineEnd = pCache->pPos; - - // Copy the remaining characters - while(pCache->pPos < pCache->pEnd && pCache->pPos[0] != 0x0A && pCache->pPos[0] != 0x0D) - { - // Blizzard listfiles can also contain information about patch: - // Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326) - if(pCache->pPos[0] == '~') - pbExtraString = pCache->pPos; - - // Copy the character - pCache->pPos++; - } - - // If there was extra string after the file name, clear it - if(pbExtraString != NULL) - { - if(pbExtraString[0] == '~' && pbExtraString[1] == 'P') - { - pbLineEnd = pbExtraString; - pbLineEnd[0] = 0; - } - } - else - { - pbLineEnd = pCache->pPos++; - pbLineEnd[0] = 0; - } - - // Give the line to the caller - if(PtrLength != NULL) - PtrLength[0] = (size_t)(pbLineEnd - pbLineBegin); - return (char *)pbLineBegin; -} - -static int CompareFileNodes(const void * p1, const void * p2) -{ - char * szFileName1 = *(char **)p1; - char * szFileName2 = *(char **)p2; - - return _stricmp(szFileName1, szFileName2); -} - -static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile) -{ - TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; - TFileEntry * pFileEntry; - char ** SortTable = NULL; - char * szListFile = NULL; - char * szListLine; - size_t nFileNodes = 0; - size_t cbListFile = 0; - size_t nIndex0; - size_t nIndex1; - - // Allocate the table for sorting listfile - SortTable = STORM_ALLOC(char*, ha->dwFileTableSize); - if(SortTable == NULL) - return NULL; - - // Construct the sort table - // Note: in MPQs with multiple locale versions of the same file, - // this code causes adding multiple listfile entries. - // They will get removed after the listfile sorting - for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Only take existing items - if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL) - { - // Ignore pseudo-names and internal names - if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName)) - { - SortTable[nFileNodes++] = pFileEntry->szFileName; - } - } - } - - // Remove duplicities - if(nFileNodes > 0) - { - // Sort the table - qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes); - - // Count the 0-th item - cbListFile += strlen(SortTable[0]) + 2; - - // Walk through the items and only use the ones that are not duplicated - for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++) - { - // If the next file node is different, we will include it to the result listfile - if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0) - { - cbListFile += strlen(SortTable[nIndex1]) + 2; - nIndex0 = nIndex1; - } - } - - // Now allocate buffer for the entire listfile - szListFile = szListLine = STORM_ALLOC(char, cbListFile + 1); - if(szListFile != NULL) - { - // Copy the 0-th item - szListLine = CopyListLine(szListLine, SortTable[0]); - - // Walk through the items and only use the ones that are not duplicated - for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++) - { - // If the next file node is different, we will include it to the result listfile - if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0) - { - // Copy the listfile line - szListLine = CopyListLine(szListLine, SortTable[nIndex1]); - nIndex0 = nIndex1; - } - } - - // Sanity check - does the size match? - assert((size_t)(szListLine - szListFile) == cbListFile); - } - } - else - { - szListFile = STORM_ALLOC(char, 1); - cbListFile = 0; - } - - // Free the sort table - STORM_FREE(SortTable); - - // Give away the listfile - if(pcbListFile != NULL) - *pcbListFile = (DWORD)cbListFile; - return (LPBYTE)szListFile; -} - -//----------------------------------------------------------------------------- -// Local functions (listfile nodes) - -// Adds a name into the list of all names. For each locale in the MPQ, -// one entry will be created -// If the file name is already there, does nothing. -static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFileName) -{ - TFileEntry * pFileEntry; - TMPQHash * pFirstHash; - TMPQHash * pHash; - - // If we have HET table, use that one - if(ha->pHetTable != NULL) - { - pFileEntry = GetFileEntryLocale(ha, szFileName, 0); - if(pFileEntry != NULL) - { - // Allocate file name for the file entry - AllocateFileName(ha, pFileEntry, szFileName); - } - - return ERROR_SUCCESS; - } - - // If we have hash table, we use it - if(ha->pHashTable != NULL) - { - // Go while we found something - pFirstHash = pHash = GetFirstHashEntry(ha, szFileName); - while(pHash != NULL) - { - // Allocate file name for the file entry - AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName); - - // Now find the next language version of the file - pHash = GetNextHashEntry(ha, pFirstHash, pHash); - } - - return ERROR_SUCCESS; - } - - return ERROR_CAN_NOT_COMPLETE; -} - -// Saves the whole listfile to the MPQ -int SListFileSaveToMpq(TMPQArchive * ha) -{ - TMPQFile * hf = NULL; - LPBYTE pbListFile; - DWORD cbListFile = 0; - int nError = ERROR_SUCCESS; - - // Only save the listfile if we should do so - if(ha->dwFileFlags1 != 0) - { - // At this point, we expect to have at least one reserved entry in the file table - assert(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW); - assert(ha->dwReservedFiles > 0); - - // Create the raw data that is to be written to (listfile) - // Note: Creating the raw data before the (listfile) has been created in the MPQ - // causes that the name of the listfile will not be included in the listfile itself. - // That is OK, because (listfile) in Blizzard MPQs does not contain it either. - pbListFile = CreateListFile(ha, &cbListFile); - if(pbListFile != NULL) - { - // Determine the real flags for (listfile) - if(ha->dwFileFlags1 == MPQ_FILE_EXISTS) - ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion); - - // Create the listfile in the MPQ - nError = SFileAddFile_Init(ha, LISTFILE_NAME, - 0, - cbListFile, - LANG_NEUTRAL, - ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING, - &hf); - - // Write the listfile raw data to it - if(nError == ERROR_SUCCESS) - { - // Write the content of the listfile to the MPQ - nError = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB); - SFileAddFile_Finish(hf); - } - - // Clear the listfile flags - ha->dwFlags &= ~(MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_NONE); - ha->dwReservedFiles--; - - // Free the listfile buffer - STORM_FREE(pbListFile); - } - else - { - // If the (listfile) file would be empty, its OK - nError = (cbListFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; - } - } - - return nError; -} - -static int SFileAddArbitraryListFile( - TMPQArchive * ha, - HANDLE hListFile) -{ - TListFileCache * pCache = NULL; - - // Create the listfile cache for that file - pCache = CreateListFileCache(hListFile, NULL); - if(pCache != NULL) - { - char * szFileName; - size_t nLength = 0; - - // Get the next line - while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL) - { - // Add the line to the MPQ - if(nLength != 0) - SListFileCreateNodeForAllLocales(ha, szFileName); - } - - // Delete the cache - FreeListFileCache(pCache); - } - - return (pCache != NULL) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; -} - -static int SFileAddExternalListFile( - TMPQArchive * ha, - HANDLE hMpq, - const char * szListFile) -{ - HANDLE hListFile; - int nError = ERROR_SUCCESS; - - // Open the external list file - if(!SFileOpenFileEx(hMpq, szListFile, SFILE_OPEN_LOCAL_FILE, &hListFile)) - return GetLastError(); - - // Add the data from the listfile to MPQ - nError = SFileAddArbitraryListFile(ha, hListFile); - SFileCloseFile(hListFile); - return nError; -} - -static int SFileAddInternalListFile( - TMPQArchive * ha, - HANDLE hMpq) -{ - TMPQHash * pFirstHash; - TMPQHash * pHash; - HANDLE hListFile; - DWORD dwFileSize; - LCID lcSaveLocale = lcFileLocale; - bool bIgnoreListFile = false; - int nError = ERROR_SUCCESS; - - // If there is hash table, we need to support multiple listfiles - // with different locales (BrooDat.mpq) - if(ha->pHashTable != NULL) - { - pFirstHash = pHash = GetFirstHashEntry(ha, LISTFILE_NAME); - while(nError == ERROR_SUCCESS && pHash != NULL) - { - // Set the prefered locale to that from list file - SFileSetLocale(pHash->lcLocale); - - // Attempt to open the file with that locale - if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hListFile)) - { - // If the archive is a malformed map, ignore too large listfiles - if(ha->dwFlags & MPQ_FLAG_MALFORMED) - { - dwFileSize = SFileGetFileSize(hListFile, NULL); - bIgnoreListFile = (dwFileSize > 0x40000); - } - - // Add the data from the listfile to MPQ - if(bIgnoreListFile == false) - nError = SFileAddArbitraryListFile(ha, hListFile); - SFileCloseFile(hListFile); - } - - // Restore the original locale - SFileSetLocale(lcSaveLocale); - - // Move to the next hash - pHash = GetNextHashEntry(ha, pFirstHash, pHash); - } - } - else - { - // Open the external list file - if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hListFile)) - { - // Add the data from the listfile to MPQ - // The function also closes the listfile handle - nError = SFileAddArbitraryListFile(ha, hListFile); - SFileCloseFile(hListFile); - } - } - - // Return the result of the operation - return nError; -} - -static bool DoListFileSearch(TListFileCache * pCache, SFILE_FIND_DATA * lpFindFileData) -{ - // Check for the valid search handle - if(pCache != NULL) - { - char * szFileName; - size_t nLength = 0; - - // Get the next line - while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL) - { - // Check search mask - if(nLength != 0 && CheckWildCard(szFileName, pCache->szWildCard)) - { - if(nLength >= sizeof(lpFindFileData->cFileName)) - nLength = sizeof(lpFindFileData->cFileName) - 1; - - memcpy(lpFindFileData->cFileName, szFileName, nLength); - lpFindFileData->cFileName[nLength] = 0; - return true; - } - } - } - - // No more files - memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA)); - SetLastError(ERROR_NO_MORE_FILES); - return false; -} - -//----------------------------------------------------------------------------- -// File functions - -// Adds a listfile into the MPQ archive. -int WINAPI SFileAddListFile(HANDLE hMpq, const char * szListFile) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - int nError = ERROR_SUCCESS; - - // Add the listfile for each MPQ in the patch chain - while(ha != NULL) - { - if(szListFile != NULL) - nError = SFileAddExternalListFile(ha, hMpq, szListFile); - else - nError = SFileAddInternalListFile(ha, hMpq); - - // Also, add three special files to the listfile: - // (listfile) itself, (attributes) and (signature) - SListFileCreateNodeForAllLocales(ha, LISTFILE_NAME); - SListFileCreateNodeForAllLocales(ha, SIGNATURE_NAME); - SListFileCreateNodeForAllLocales(ha, ATTRIBUTES_NAME); - - // Move to the next archive in the chain - ha = ha->haPatch; - } - - return nError; -} - -//----------------------------------------------------------------------------- -// Enumerating files in listfile - -HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const char * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData) -{ - TListFileCache * pCache = NULL; - HANDLE hListFile = NULL; - DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE; - - // Initialize the structure with zeros - memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA)); - - // If the szListFile is NULL, it means we have to open internal listfile - if(szListFile == NULL) - { - // Use SFILE_OPEN_ANY_LOCALE for listfile. This will allow us to load - // the listfile even if there is only non-neutral version of the listfile in the MPQ - dwSearchScope = SFILE_OPEN_ANY_LOCALE; - szListFile = LISTFILE_NAME; - } - - // Open the local/internal listfile - if(SFileOpenFileEx(hMpq, szListFile, dwSearchScope, &hListFile)) - { - pCache = CreateListFileCache(hListFile, szMask); - SFileCloseFile(hListFile); - } - - if(!DoListFileSearch(pCache, lpFindFileData)) - { - memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA)); - SetLastError(ERROR_NO_MORE_FILES); - FreeListFileCache(pCache); - pCache = NULL; - } - - // Return the listfile cache as handle - return (HANDLE)pCache; -} - -bool WINAPI SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData) -{ - return DoListFileSearch((TListFileCache *)hFind, lpFindFileData); -} - -bool WINAPI SListFileFindClose(HANDLE hFind) -{ - TListFileCache * pCache = (TListFileCache *)hFind; - - return FreeListFileCache(pCache); -} - +/*****************************************************************************/
+/* SListFile.cpp Copyright (c) Ladislav Zezula 2004 */
+/*---------------------------------------------------------------------------*/
+/* Description: */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 12.06.04 1.00 Lad The first version of SListFile.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+#include <assert.h>
+
+//-----------------------------------------------------------------------------
+// Listfile entry structure
+
+#define CACHE_BUFFER_SIZE 0x1000 // Size of the cache buffer
+#define MAX_LISTFILE_SIZE 0x04000000 // Maximum accepted listfile size is about 68 MB
+
+struct TListFileCache
+{
+ char * szWildCard; // Self-relative pointer to file mask
+ LPBYTE pBegin; // The begin of the listfile cache
+ LPBYTE pPos; // Current position in the cache
+ LPBYTE pEnd; // The last character in the file cache
+
+// char szWildCard[wildcard_length]; // Followed by the name mask (if any)
+// char szListFile[listfile_length]; // Followed by the listfile (if any)
+};
+
+//-----------------------------------------------------------------------------
+// Local functions (cache)
+
+static char * CopyListLine(char * szListLine, const char * szFileName)
+{
+ // Copy the string
+ while(szFileName[0] != 0)
+ *szListLine++ = *szFileName++;
+
+ // Append the end-of-line
+ *szListLine++ = 0x0D;
+ *szListLine++ = 0x0A;
+ return szListLine;
+}
+
+static bool FreeListFileCache(TListFileCache * pCache)
+{
+ // Valid parameter check
+ if(pCache != NULL)
+ STORM_FREE(pCache);
+ return true;
+}
+
+static TListFileCache * CreateListFileCache(HANDLE hListFile, const char * szWildCard)
+{
+ TListFileCache * pCache = NULL;
+ size_t cchWildCard = 0;
+ DWORD dwBytesRead = 0;
+ DWORD dwFileSize;
+
+ // Get the amount of bytes that need to be allocated
+ dwFileSize = SFileGetFileSize(hListFile, NULL);
+ if(dwFileSize == 0 || dwFileSize > MAX_LISTFILE_SIZE)
+ return NULL;
+
+ // Append buffer for name mask, if any
+ if(szWildCard != NULL)
+ cchWildCard = strlen(szWildCard) + 1;
+
+ // Allocate cache for one file block
+ pCache = (TListFileCache *)STORM_ALLOC(BYTE, sizeof(TListFileCache) + cchWildCard + dwFileSize + 1);
+ if(pCache != NULL)
+ {
+ // Clear the entire structure
+ memset(pCache, 0, sizeof(TListFileCache) + cchWildCard);
+
+ // Shall we copy the mask?
+ if(cchWildCard != 0)
+ {
+ pCache->szWildCard = (char *)(pCache + 1);
+ memcpy(pCache->szWildCard, szWildCard, cchWildCard);
+ }
+
+ // Fill-in the rest of the cache pointers
+ pCache->pBegin = (LPBYTE)(pCache + 1) + cchWildCard;
+
+ // Load the entire listfile to the cache
+ SFileReadFile(hListFile, pCache->pBegin, dwFileSize, &dwBytesRead, NULL);
+ if(dwBytesRead != 0)
+ {
+ // Allocate pointers
+ pCache->pPos = pCache->pBegin;
+ pCache->pEnd = pCache->pBegin + dwBytesRead;
+ }
+ else
+ {
+ FreeListFileCache(pCache);
+ pCache = NULL;
+ }
+ }
+
+ // Return the cache
+ return pCache;
+}
+
+#ifdef _DEBUG
+/*
+TMPQNameCache * CreateNameCache(HANDLE hListFile, const char * szSearchMask)
+{
+ TMPQNameCache * pNameCache;
+ char * szCachePointer;
+ size_t cbToAllocate;
+ size_t nMaskLength = 1;
+ DWORD dwBytesRead = 0;
+ DWORD dwFileSize;
+
+ // Get the size of the listfile. Ignore zero or too long ones
+ dwFileSize = SFileGetFileSize(hListFile, NULL);
+ if(dwFileSize == 0 || dwFileSize > MAX_LISTFILE_SIZE)
+ return NULL;
+
+ // Get the length of the search mask
+ if(szSearchMask == NULL)
+ szSearchMask = "*";
+ nMaskLength = strlen(szSearchMask) + 1;
+
+ // Allocate the name cache
+ cbToAllocate = sizeof(TMPQNameCache) + nMaskLength + dwFileSize + 1;
+ pNameCache = (TMPQNameCache *)STORM_ALLOC(BYTE, cbToAllocate);
+ if(pNameCache != NULL)
+ {
+ // Initialize the name cache
+ memset(pNameCache, 0, sizeof(TMPQNameCache));
+ pNameCache->TotalCacheSize = (DWORD)(nMaskLength + dwFileSize + 1);
+ szCachePointer = (char *)(pNameCache + 1);
+
+ // Copy the search mask, if any
+ memcpy(szCachePointer, szSearchMask, nMaskLength);
+ pNameCache->FirstNameOffset = (DWORD)nMaskLength;
+ pNameCache->FreeSpaceOffset = (DWORD)nMaskLength;
+
+ // Read the listfile itself
+ SFileSetFilePointer(hListFile, 0, NULL, FILE_BEGIN);
+ SFileReadFile(hListFile, szCachePointer + nMaskLength, dwFileSize, &dwBytesRead, NULL);
+
+ // If nothing has been read from the listfile, clear the cache
+ if(dwBytesRead == 0)
+ {
+ STORM_FREE(pNameCache);
+ return NULL;
+ }
+
+ // Move the free space offset
+ pNameCache->FreeSpaceOffset = pNameCache->FirstNameOffset + dwBytesRead + 1;
+ szCachePointer[nMaskLength + dwBytesRead] = 0;
+ }
+
+ return pNameCache;
+}
+
+static void FreeNameCache(TMPQNameCache * pNameCache)
+{
+ if(pNameCache != NULL)
+ STORM_FREE(pNameCache);
+ pNameCache = NULL;
+}
+*/
+#endif // _DEBUG
+
+static char * ReadListFileLine(TListFileCache * pCache, size_t * PtrLength)
+{
+ LPBYTE pbLineBegin;
+ LPBYTE pbLineEnd;
+ LPBYTE pbExtraString = NULL;
+
+ // Skip newlines, spaces, tabs and another non-printable stuff
+ while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20)
+ pCache->pPos++;
+
+ // Set the line begin and end
+ if(pCache->pPos >= pCache->pEnd)
+ return NULL;
+ pbLineBegin = pbLineEnd = pCache->pPos;
+
+ // Copy the remaining characters
+ while(pCache->pPos < pCache->pEnd && pCache->pPos[0] != 0x0A && pCache->pPos[0] != 0x0D)
+ {
+ // Blizzard listfiles can also contain information about patch:
+ // Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326)
+ if(pCache->pPos[0] == '~')
+ pbExtraString = pCache->pPos;
+
+ // Copy the character
+ pCache->pPos++;
+ }
+
+ // If there was extra string after the file name, clear it
+ if(pbExtraString != NULL)
+ {
+ if(pbExtraString[0] == '~' && pbExtraString[1] == 'P')
+ {
+ pbLineEnd = pbExtraString;
+ pbLineEnd[0] = 0;
+ }
+ }
+ else
+ {
+ pbLineEnd = pCache->pPos++;
+ pbLineEnd[0] = 0;
+ }
+
+ // Give the line to the caller
+ if(PtrLength != NULL)
+ PtrLength[0] = (size_t)(pbLineEnd - pbLineBegin);
+ return (char *)pbLineBegin;
+}
+
+static int CompareFileNodes(const void * p1, const void * p2)
+{
+ char * szFileName1 = *(char **)p1;
+ char * szFileName2 = *(char **)p2;
+
+ return _stricmp(szFileName1, szFileName2);
+}
+
+static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ char ** SortTable = NULL;
+ char * szListFile = NULL;
+ char * szListLine;
+ size_t nFileNodes = 0;
+ size_t cbListFile = 0;
+ size_t nIndex0;
+ size_t nIndex1;
+
+ // Allocate the table for sorting listfile
+ SortTable = STORM_ALLOC(char*, ha->dwFileTableSize);
+ if(SortTable == NULL)
+ return NULL;
+
+ // Construct the sort table
+ // Note: in MPQs with multiple locale versions of the same file,
+ // this code causes adding multiple listfile entries.
+ // They will get removed after the listfile sorting
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Only take existing items
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL)
+ {
+ // Ignore pseudo-names and internal names
+ if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName))
+ {
+ SortTable[nFileNodes++] = pFileEntry->szFileName;
+ }
+ }
+ }
+
+ // Remove duplicities
+ if(nFileNodes > 0)
+ {
+ // Sort the table
+ qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes);
+
+ // Count the 0-th item
+ cbListFile += strlen(SortTable[0]) + 2;
+
+ // Walk through the items and only use the ones that are not duplicated
+ for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++)
+ {
+ // If the next file node is different, we will include it to the result listfile
+ if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0)
+ {
+ cbListFile += strlen(SortTable[nIndex1]) + 2;
+ nIndex0 = nIndex1;
+ }
+ }
+
+ // Now allocate buffer for the entire listfile
+ szListFile = szListLine = STORM_ALLOC(char, cbListFile + 1);
+ if(szListFile != NULL)
+ {
+ // Copy the 0-th item
+ szListLine = CopyListLine(szListLine, SortTable[0]);
+
+ // Walk through the items and only use the ones that are not duplicated
+ for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++)
+ {
+ // If the next file node is different, we will include it to the result listfile
+ if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0)
+ {
+ // Copy the listfile line
+ szListLine = CopyListLine(szListLine, SortTable[nIndex1]);
+ nIndex0 = nIndex1;
+ }
+ }
+
+ // Sanity check - does the size match?
+ assert((size_t)(szListLine - szListFile) == cbListFile);
+ }
+ }
+ else
+ {
+ szListFile = STORM_ALLOC(char, 1);
+ cbListFile = 0;
+ }
+
+ // Free the sort table
+ STORM_FREE(SortTable);
+
+ // Give away the listfile
+ if(pcbListFile != NULL)
+ *pcbListFile = (DWORD)cbListFile;
+ return (LPBYTE)szListFile;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions (listfile nodes)
+
+// Adds a name into the list of all names. For each locale in the MPQ,
+// one entry will be created
+// If the file name is already there, does nothing.
+static int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFileName)
+{
+ TFileEntry * pFileEntry;
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+
+ // If we have HET table, use that one
+ if(ha->pHetTable != NULL)
+ {
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
+ if(pFileEntry != NULL)
+ {
+ // Allocate file name for the file entry
+ AllocateFileName(ha, pFileEntry, szFileName);
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ // If we have hash table, we use it
+ if(ha->pHashTable != NULL)
+ {
+ // Go while we found something
+ pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
+ while(pHash != NULL)
+ {
+ // Allocate file name for the file entry
+ AllocateFileName(ha, ha->pFileTable + pHash->dwBlockIndex, szFileName);
+
+ // Now find the next language version of the file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+// Saves the whole listfile to the MPQ
+int SListFileSaveToMpq(TMPQArchive * ha)
+{
+ TMPQFile * hf = NULL;
+ LPBYTE pbListFile;
+ DWORD cbListFile = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Only save the listfile if we should do so
+ if(ha->dwFileFlags1 != 0)
+ {
+ // At this point, we expect to have at least one reserved entry in the file table
+ assert(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW);
+ assert(ha->dwReservedFiles > 0);
+
+ // Create the raw data that is to be written to (listfile)
+ // Note: Creating the raw data before the (listfile) has been created in the MPQ
+ // causes that the name of the listfile will not be included in the listfile itself.
+ // That is OK, because (listfile) in Blizzard MPQs does not contain it either.
+ pbListFile = CreateListFile(ha, &cbListFile);
+ if(pbListFile != NULL)
+ {
+ // Determine the real flags for (listfile)
+ if(ha->dwFileFlags1 == MPQ_FILE_EXISTS)
+ ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion);
+
+ // Create the listfile in the MPQ
+ nError = SFileAddFile_Init(ha, LISTFILE_NAME,
+ 0,
+ cbListFile,
+ LANG_NEUTRAL,
+ ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Write the listfile raw data to it
+ if(nError == ERROR_SUCCESS)
+ {
+ // Write the content of the listfile to the MPQ
+ nError = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB);
+ SFileAddFile_Finish(hf);
+ }
+
+ // Clear the listfile flags
+ ha->dwFlags &= ~(MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_NONE);
+ ha->dwReservedFiles--;
+
+ // Free the listfile buffer
+ STORM_FREE(pbListFile);
+ }
+ else
+ {
+ // If the (listfile) file would be empty, its OK
+ nError = (cbListFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return nError;
+}
+
+static int SFileAddArbitraryListFile(
+ TMPQArchive * ha,
+ HANDLE hListFile)
+{
+ TListFileCache * pCache = NULL;
+
+ // Create the listfile cache for that file
+ pCache = CreateListFileCache(hListFile, NULL);
+ if(pCache != NULL)
+ {
+ char * szFileName;
+ size_t nLength = 0;
+
+ // Get the next line
+ while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL)
+ {
+ // Add the line to the MPQ
+ if(nLength != 0)
+ SListFileCreateNodeForAllLocales(ha, szFileName);
+ }
+
+ // Delete the cache
+ FreeListFileCache(pCache);
+ }
+
+ return (pCache != NULL) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
+}
+
+static int SFileAddExternalListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq,
+ const char * szListFile)
+{
+ HANDLE hListFile;
+ int nError = ERROR_SUCCESS;
+
+ // Open the external list file
+ if(!SFileOpenFileEx(hMpq, szListFile, SFILE_OPEN_LOCAL_FILE, &hListFile))
+ return GetLastError();
+
+ // Add the data from the listfile to MPQ
+ nError = SFileAddArbitraryListFile(ha, hListFile);
+ SFileCloseFile(hListFile);
+ return nError;
+}
+
+static int SFileAddInternalListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq)
+{
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+ HANDLE hListFile;
+ DWORD dwFileSize;
+ LCID lcSaveLocale = lcFileLocale;
+ bool bIgnoreListFile = false;
+ int nError = ERROR_SUCCESS;
+
+ // If there is hash table, we need to support multiple listfiles
+ // with different locales (BrooDat.mpq)
+ if(ha->pHashTable != NULL)
+ {
+ pFirstHash = pHash = GetFirstHashEntry(ha, LISTFILE_NAME);
+ while(nError == ERROR_SUCCESS && pHash != NULL)
+ {
+ // Set the prefered locale to that from list file
+ SFileSetLocale(pHash->lcLocale);
+
+ // Attempt to open the file with that locale
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hListFile))
+ {
+ // If the archive is a malformed map, ignore too large listfiles
+ if(ha->dwFlags & MPQ_FLAG_MALFORMED)
+ {
+ dwFileSize = SFileGetFileSize(hListFile, NULL);
+ bIgnoreListFile = (dwFileSize > 0x40000);
+ }
+
+ // Add the data from the listfile to MPQ
+ if(bIgnoreListFile == false)
+ nError = SFileAddArbitraryListFile(ha, hListFile);
+ SFileCloseFile(hListFile);
+ }
+
+ // Restore the original locale
+ SFileSetLocale(lcSaveLocale);
+
+ // Move to the next hash
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+ }
+ else
+ {
+ // Open the external list file
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hListFile))
+ {
+ // Add the data from the listfile to MPQ
+ // The function also closes the listfile handle
+ nError = SFileAddArbitraryListFile(ha, hListFile);
+ SFileCloseFile(hListFile);
+ }
+ }
+
+ // Return the result of the operation
+ return nError;
+}
+
+static bool DoListFileSearch(TListFileCache * pCache, SFILE_FIND_DATA * lpFindFileData)
+{
+ // Check for the valid search handle
+ if(pCache != NULL)
+ {
+ char * szFileName;
+ size_t nLength = 0;
+
+ // Get the next line
+ while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL)
+ {
+ // Check search mask
+ if(nLength != 0 && CheckWildCard(szFileName, pCache->szWildCard))
+ {
+ if(nLength >= sizeof(lpFindFileData->cFileName))
+ nLength = sizeof(lpFindFileData->cFileName) - 1;
+
+ memcpy(lpFindFileData->cFileName, szFileName, nLength);
+ lpFindFileData->cFileName[nLength] = 0;
+ return true;
+ }
+ }
+ }
+
+ // No more files
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+ SetLastError(ERROR_NO_MORE_FILES);
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// File functions
+
+// Adds a listfile into the MPQ archive.
+int WINAPI SFileAddListFile(HANDLE hMpq, const char * szListFile)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ int nError = ERROR_SUCCESS;
+
+ // Add the listfile for each MPQ in the patch chain
+ while(ha != NULL)
+ {
+ if(szListFile != NULL)
+ nError = SFileAddExternalListFile(ha, hMpq, szListFile);
+ else
+ nError = SFileAddInternalListFile(ha, hMpq);
+
+ // Also, add three special files to the listfile:
+ // (listfile) itself, (attributes) and (signature)
+ SListFileCreateNodeForAllLocales(ha, LISTFILE_NAME);
+ SListFileCreateNodeForAllLocales(ha, SIGNATURE_NAME);
+ SListFileCreateNodeForAllLocales(ha, ATTRIBUTES_NAME);
+
+ // Move to the next archive in the chain
+ ha = ha->haPatch;
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Enumerating files in listfile
+
+HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const char * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
+{
+ TListFileCache * pCache = NULL;
+ HANDLE hListFile = NULL;
+ DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
+
+ // Initialize the structure with zeros
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+
+ // If the szListFile is NULL, it means we have to open internal listfile
+ if(szListFile == NULL)
+ {
+ // Use SFILE_OPEN_ANY_LOCALE for listfile. This will allow us to load
+ // the listfile even if there is only non-neutral version of the listfile in the MPQ
+ dwSearchScope = SFILE_OPEN_ANY_LOCALE;
+ szListFile = LISTFILE_NAME;
+ }
+
+ // Open the local/internal listfile
+ if(SFileOpenFileEx(hMpq, szListFile, dwSearchScope, &hListFile))
+ {
+ pCache = CreateListFileCache(hListFile, szMask);
+ SFileCloseFile(hListFile);
+ }
+
+ if(!DoListFileSearch(pCache, lpFindFileData))
+ {
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+ SetLastError(ERROR_NO_MORE_FILES);
+ FreeListFileCache(pCache);
+ pCache = NULL;
+ }
+
+ // Return the listfile cache as handle
+ return (HANDLE)pCache;
+}
+
+bool WINAPI SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
+{
+ return DoListFileSearch((TListFileCache *)hFind, lpFindFileData);
+}
+
+bool WINAPI SListFileFindClose(HANDLE hFind)
+{
+ TListFileCache * pCache = (TListFileCache *)hFind;
+
+ return FreeListFileCache(pCache);
+}
+
diff --git a/src/SFilePatchArchives.cpp b/src/SFilePatchArchives.cpp index 9cb6ecd..7c6515e 100644 --- a/src/SFilePatchArchives.cpp +++ b/src/SFilePatchArchives.cpp @@ -1,937 +1,937 @@ -/*****************************************************************************/ -/* SFilePatchArchives.cpp Copyright (c) Ladislav Zezula 2010 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 18.08.10 1.00 Lad The first version of SFilePatchArchives.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Local structures - -#define PATCH_SIGNATURE_HEADER 0x48435450 -#define PATCH_SIGNATURE_MD5 0x5f35444d -#define PATCH_SIGNATURE_XFRM 0x4d524658 - -#define SIZE_OF_XFRM_HEADER 0x0C - -// Header for incremental patch files -typedef struct _MPQ_PATCH_HEADER -{ - //-- PATCH header ----------------------------------- - DWORD dwSignature; // 'PTCH' - 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 - BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file - BYTE md5_after_patch[0x10]; // MD5 of the patched file - - //-- XFRM block ------------------------------------- - DWORD dwXFRM; // 'XFRM' - DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data - DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY') - - // Followed by the patch data -} MPQ_PATCH_HEADER, *PMPQ_PATCH_HEADER; - -typedef struct _BLIZZARD_BSDIFF40_FILE -{ - ULONGLONG Signature; - ULONGLONG CtrlBlockSize; - ULONGLONG DataBlockSize; - ULONGLONG NewFileSize; -} BLIZZARD_BSDIFF40_FILE, *PBLIZZARD_BSDIFF40_FILE; - -typedef struct _BSDIFF_CTRL_BLOCK -{ - DWORD dwAddDataLength; - DWORD dwMovDataLength; - DWORD dwOldMoveLength; - -} BSDIFF_CTRL_BLOCK, *PBSDIFF_CTRL_BLOCK; - -typedef struct _LOCALIZED_MPQ_INFO -{ - const char * szNameTemplate; // Name template - size_t nLangOffset; // Offset of the language - size_t nLength; // Length of the name template -} LOCALIZED_MPQ_INFO, *PLOCALIZED_MPQ_INFO; - -//----------------------------------------------------------------------------- -// Local variables - -// 4-byte groups for all languages -static const char * LanguageList = "baseteenenUSenGBenCNenTWdeDEesESesMXfrFRitITkoKRptBRptPTruRUzhCNzhTW"; - -// List of localized MPQs for World of Warcraft -static LOCALIZED_MPQ_INFO LocaleMpqs_WoW[] = -{ - {"expansion1-locale-####", 18, 22}, - {"expansion1-speech-####", 18, 22}, - {"expansion2-locale-####", 18, 22}, - {"expansion2-speech-####", 18, 22}, - {"expansion3-locale-####", 18, 22}, - {"expansion3-speech-####", 18, 22}, - {"locale-####", 7, 11}, - {"speech-####", 7, 11}, - {NULL, 0, 0} -}; - -//----------------------------------------------------------------------------- -// Local functions - -static inline bool IsPatchMetadataFile(TFileEntry * pFileEntry) -{ - // The file must ave a name - if(pFileEntry->szFileName != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - { - // The file must be small - if(0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40) - { - // Compare the plain name - return (_stricmp(GetPlainFileName(pFileEntry->szFileName), PATCH_METADATA_NAME) == 0); - } - } - - // Not a patch_metadata - return false; -} - -static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE pbCompressed, DWORD cbCompressed) -{ - LPBYTE pbDecompressedEnd = pbDecompressed + cbDecompressed; - LPBYTE pbCompressedEnd = pbCompressed + cbCompressed; - BYTE RepeatCount; - BYTE OneByte; - - // Cut the initial DWORD from the compressed chunk - pbCompressed += sizeof(DWORD); - - // Pre-fill decompressed buffer with zeros - memset(pbDecompressed, 0, cbDecompressed); - - // Unpack - while(pbCompressed < pbCompressedEnd && pbDecompressed < pbDecompressedEnd) - { - OneByte = *pbCompressed++; - - // Is it a repetition byte ? - if(OneByte & 0x80) - { - RepeatCount = (OneByte & 0x7F) + 1; - for(BYTE i = 0; i < RepeatCount; i++) - { - if(pbDecompressed == pbDecompressedEnd || pbCompressed == pbCompressedEnd) - break; - - *pbDecompressed++ = *pbCompressed++; - } - } - else - { - pbDecompressed += (OneByte + 1); - } - } -} - -static int LoadFilePatch_COPY(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch) -{ - DWORD cbBytesToRead = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER); - DWORD cbBytesRead = 0; - - // Simply load the rest of the patch - SFileReadFile((HANDLE)hf, (pFullPatch + 1), cbBytesToRead, &cbBytesRead, NULL); - return (cbBytesRead == cbBytesToRead) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT; -} - -static int LoadFilePatch_BSD0(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch) -{ - LPBYTE pbDecompressed = (LPBYTE)(pFullPatch + 1); - LPBYTE pbCompressed = NULL; - DWORD cbDecompressed = 0; - DWORD cbCompressed = 0; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Calculate the size of compressed data - cbDecompressed = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER); - cbCompressed = pFullPatch->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER; - - // Is that file compressed? - if(cbCompressed < cbDecompressed) - { - pbCompressed = STORM_ALLOC(BYTE, cbCompressed); - if(pbCompressed == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - - // Read the compressed patch data - if(nError == ERROR_SUCCESS) - { - SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL); - if(dwBytesRead != cbCompressed) - nError = ERROR_FILE_CORRUPT; - } - - // Decompress the data - if(nError == ERROR_SUCCESS) - Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed); - - if(pbCompressed != NULL) - STORM_FREE(pbCompressed); - } - else - { - SFileReadFile((HANDLE)hf, pbDecompressed, cbDecompressed, &dwBytesRead, NULL); - if(dwBytesRead != cbDecompressed) - nError = ERROR_FILE_CORRUPT; - } - - return nError; -} - -static int ApplyFilePatch_COPY( - TMPQPatcher * pPatcher, - PMPQ_PATCH_HEADER pFullPatch, - LPBYTE pbTarget, - LPBYTE pbSource) -{ - // Sanity checks - assert(pPatcher->cbMaxFileData >= pPatcher->cbFileData); - pFullPatch = pFullPatch; - - // Copy the patch data as-is - memcpy(pbTarget, pbSource, pPatcher->cbFileData); - return ERROR_SUCCESS; -} - -static int ApplyFilePatch_BSD0( - TMPQPatcher * pPatcher, - PMPQ_PATCH_HEADER pFullPatch, - LPBYTE pbTarget, - LPBYTE pbSource) -{ - PBLIZZARD_BSDIFF40_FILE pBsdiff; - PBSDIFF_CTRL_BLOCK pCtrlBlock; - LPBYTE pbPatchData = (LPBYTE)(pFullPatch + 1); - LPBYTE pDataBlock; - LPBYTE pExtraBlock; - LPBYTE pbOldData = pbSource; - LPBYTE pbNewData = pbTarget; - DWORD dwCombineSize; - DWORD dwNewOffset = 0; // Current position to patch - DWORD dwOldOffset = 0; // Current source position - DWORD dwNewSize; // Patched file size - DWORD dwOldSize = pPatcher->cbFileData; // File size before patch - - // Get pointer to the patch header - // Format of BSDIFF header corresponds to original BSDIFF, which is: - // 0000 8 bytes signature "BSDIFF40" - // 0008 8 bytes size of the control block - // 0010 8 bytes size of the data block - // 0018 8 bytes new size of the patched file - pBsdiff = (PBLIZZARD_BSDIFF40_FILE)pbPatchData; - pbPatchData += sizeof(BLIZZARD_BSDIFF40_FILE); - - // Get pointer to the 32-bit BSDIFF control block - // The control block follows immediately after the BSDIFF header - // and consists of three 32-bit integers - // 0000 4 bytes Length to copy from the BSDIFF data block the new file - // 0004 4 bytes Length to copy from the BSDIFF extra block - // 0008 4 bytes Size to increment source file offset - pCtrlBlock = (PBSDIFF_CTRL_BLOCK)pbPatchData; - pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->CtrlBlockSize); - - // Get the pointer to the data block - pDataBlock = (LPBYTE)pbPatchData; - pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->DataBlockSize); - - // Get the pointer to the extra block - pExtraBlock = (LPBYTE)pbPatchData; - dwNewSize = (DWORD)BSWAP_INT64_UNSIGNED(pBsdiff->NewFileSize); - - // Now patch the file - while(dwNewOffset < dwNewSize) - { - DWORD dwAddDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwAddDataLength); - DWORD dwMovDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwMovDataLength); - DWORD dwOldMoveLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwOldMoveLength); - DWORD i; - - // Sanity check - if((dwNewOffset + dwAddDataLength) > dwNewSize) - return ERROR_FILE_CORRUPT; - - // Read the diff string to the target buffer - memcpy(pbNewData + dwNewOffset, pDataBlock, dwAddDataLength); - pDataBlock += dwAddDataLength; - - // Get the longest block that we can combine - dwCombineSize = ((dwOldOffset + dwAddDataLength) >= dwOldSize) ? (dwOldSize - dwOldOffset) : dwAddDataLength; - - // Now combine the patch data with the original file - for(i = 0; i < dwCombineSize; i++) - pbNewData[dwNewOffset + i] = pbNewData[dwNewOffset + i] + pbOldData[dwOldOffset + i]; - - // Move the offsets - dwNewOffset += dwAddDataLength; - dwOldOffset += dwAddDataLength; - - // Sanity check - if((dwNewOffset + dwMovDataLength) > dwNewSize) - return ERROR_FILE_CORRUPT; - - // Copy the data from the extra block in BSDIFF patch - memcpy(pbNewData + dwNewOffset, pExtraBlock, dwMovDataLength); - pExtraBlock += dwMovDataLength; - dwNewOffset += dwMovDataLength; - - // Move the old offset - if(dwOldMoveLength & 0x80000000) - dwOldMoveLength = 0x80000000 - dwOldMoveLength; - dwOldOffset += dwOldMoveLength; - pCtrlBlock++; - } - - // The size after patch must match - if(dwNewOffset != pFullPatch->dwSizeAfterPatch) - return ERROR_FILE_CORRUPT; - - // Update the new data size - pPatcher->cbFileData = dwNewOffset; - return ERROR_SUCCESS; -} - -static PMPQ_PATCH_HEADER LoadFullFilePatch(TMPQFile * hf, MPQ_PATCH_HEADER & PatchHeader) -{ - PMPQ_PATCH_HEADER pFullPatch; - int nError = ERROR_SUCCESS; - - // BSWAP the entire header, if needed - BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6); - BSWAP_ARRAY32_UNSIGNED(&PatchHeader.dwXFRM, sizeof(DWORD) * 3); - - // Verify the signatures in the patch header - if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM) - return NULL; - - // Allocate space for patch header and compressed data - pFullPatch = (PMPQ_PATCH_HEADER)STORM_ALLOC(BYTE, PatchHeader.dwSizeOfPatchData); - if(pFullPatch != NULL) - { - // Copy the patch header - memcpy(pFullPatch, &PatchHeader, sizeof(MPQ_PATCH_HEADER)); - - // Read the patch, depending on patch type - if(nError == ERROR_SUCCESS) - { - switch(PatchHeader.dwPatchType) - { - case 0x59504f43: // 'COPY' - nError = LoadFilePatch_COPY(hf, pFullPatch); - break; - - case 0x30445342: // 'BSD0' - nError = LoadFilePatch_BSD0(hf, pFullPatch); - break; - - default: - nError = ERROR_FILE_CORRUPT; - break; - } - } - - // If something failed, free the patch buffer - if(nError != ERROR_SUCCESS) - { - STORM_FREE(pFullPatch); - pFullPatch = NULL; - } - } - - // Give the result to the caller - return pFullPatch; -} - -static int ApplyFilePatch( - TMPQPatcher * pPatcher, - PMPQ_PATCH_HEADER pFullPatch) -{ - LPBYTE pbSource = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData2 : pPatcher->pbFileData1; - LPBYTE pbTarget = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData1 : pPatcher->pbFileData2; - int nError; - - // Sanity checks - assert(pFullPatch->dwSizeAfterPatch <= pPatcher->cbMaxFileData); - - // Apply the patch according to the type - switch(pFullPatch->dwPatchType) - { - case 0x59504f43: // 'COPY' - nError = ApplyFilePatch_COPY(pPatcher, pFullPatch, pbTarget, pbSource); - break; - - case 0x30445342: // 'BSD0' - nError = ApplyFilePatch_BSD0(pPatcher, pFullPatch, pbTarget, pbSource); - break; - - default: - nError = ERROR_FILE_CORRUPT; - break; - } - - // Verify MD5 after patch - if(nError == ERROR_SUCCESS && pFullPatch->dwSizeAfterPatch != 0) - { - // Verify the patched file - if(!VerifyDataBlockHash(pbTarget, pFullPatch->dwSizeAfterPatch, pFullPatch->md5_after_patch)) - nError = ERROR_FILE_CORRUPT; - - // Copy the MD5 of the new block - memcpy(pPatcher->this_md5, pFullPatch->md5_after_patch, MD5_DIGEST_SIZE); - } - - return nError; -} - -//----------------------------------------------------------------------------- -// Local functions (patch prefix matching) - -static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, size_t nLength) -{ - TMPQNamePrefix * pNewPrefix; - - // If the end of the patch prefix was not entered, find it - if(szFileName != NULL && nLength == 0) - nLength = strlen(szFileName); - - // Create the patch prefix - pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength); - if(pNewPrefix != NULL) - { - // Fill the name prefix - pNewPrefix->nLength = nLength; - pNewPrefix->szPatchPrefix[0] = 0; - - // Fill the name prefix. Also add the backslash - if(szFileName && nLength) - { - memcpy(pNewPrefix->szPatchPrefix, szFileName, nLength); - pNewPrefix->szPatchPrefix[nLength] = 0; - } - } - - ha->pPatchPrefix = pNewPrefix; - return (pNewPrefix != NULL); -} - -static bool IsMatchingPatchFile( - TMPQArchive * ha, - const char * szFileName, - LPBYTE pbFileMd5) -{ - MPQ_PATCH_HEADER PatchHeader = {0}; - HANDLE hFile = NULL; - DWORD dwTransferred = 0; - bool bResult = false; - - // Open the file and load the patch header - if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile)) - { - // Load the patch header - SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL); - BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6); - - // If the file contains an incremental patch, - // compare the "MD5 before patching" with the base file MD5 - if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER) - bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE)); - - // Close the file - SFileCloseFile(hFile); - } - - return bResult; -} - -static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pMpqInfo) -{ - TFileEntry * pFileEntry; - const char * szLanguage = LanguageList; - char szFileName[0x40]; - - // Iterate through all localized languages - while(pMpqInfo->szNameTemplate != NULL) - { - // Iterate through all languages - for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage += 4) - { - // Construct the file name - memcpy(szFileName, pMpqInfo->szNameTemplate, pMpqInfo->nLength); - szFileName[pMpqInfo->nLangOffset + 0] = szLanguage[0]; - szFileName[pMpqInfo->nLangOffset + 1] = szLanguage[1]; - szFileName[pMpqInfo->nLangOffset + 2] = szLanguage[2]; - szFileName[pMpqInfo->nLangOffset + 3] = szLanguage[3]; - - // Append the suffix - memcpy(szFileName + pMpqInfo->nLength, "-md5.lst", 9); - - // Check whether the name exists - pFileEntry = GetFileEntryLocale(ha, szFileName, 0); - if(pFileEntry != NULL) - return szLanguage; - } - - // Move to the next language name - pMpqInfo++; - } - - // Not found - return NULL; -} - -static TFileEntry * FindBaseLstFile(TMPQArchive * ha) -{ - TFileEntry * pFileEntry; - const char * szLanguage; - char szFileName[0x40]; - - // Prepare the file name tenplate - memcpy(szFileName, "####-md5.lst", 13); - - // Try all languages - for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage++) - { - // Copy the language name - szFileName[0] = szLanguage[0]; - szFileName[1] = szLanguage[1]; - szFileName[2] = szLanguage[2]; - szFileName[3] = szLanguage[3]; - - // Check whether this file exists - pFileEntry = GetFileEntryLocale(ha, szFileName, 0); - if(pFileEntry != NULL) - return pFileEntry; - } - - return NULL; -} - -static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch) -{ - const char * szPatchPrefix; - char szNamePrefix[0x08]; - - // Try to find the language of the MPQ archive - szPatchPrefix = FindArchiveLanguage(haBase, LocaleMpqs_WoW); - if(szPatchPrefix == NULL) - szPatchPrefix = "Base"; - - // Format the patch prefix - szNamePrefix[0] = szPatchPrefix[0]; - szNamePrefix[1] = szPatchPrefix[1]; - szNamePrefix[2] = szPatchPrefix[2]; - szNamePrefix[3] = szPatchPrefix[3]; - szNamePrefix[4] = '\\'; - szNamePrefix[5] = 0; - return CreatePatchPrefix(haPatch, szNamePrefix, 5); -} - -// -// Find match in Starcraft II patch MPQs -// Match a LST file in the root directory if the MPQ with any of the file in subdirectories -// -// The problem: -// Base: enGB-md5.lst -// Patch: Campaigns\Liberty.SC2Campaign\enGB.SC2Assets\enGB-md5.lst -// Campaigns\Liberty.SC2Campaign\enGB.SC2Data\enGB-md5.lst -// Campaigns\LibertyStory.SC2Campaign\enGB.SC2Data\enGB-md5.lst -// Campaigns\LibertyStory.SC2Campaign\enGB.SC2Data\enGB-md5.lst Mods\Core.SC2Mod\enGB.SC2Assets\enGB-md5.lst -// Mods\Core.SC2Mod\enGB.SC2Data\enGB-md5.lst -// Mods\Liberty.SC2Mod\enGB.SC2Assets\enGB-md5.lst -// Mods\Liberty.SC2Mod\enGB.SC2Data\enGB-md5.lst -// Mods\LibertyMulti.SC2Mod\enGB.SC2Data\enGB-md5.lst -// -// Solution: -// We need to match the file by its MD5 -// - - -static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch) -{ - TMPQNamePrefix * pPatchPrefix; - TFileEntry * pBaseEntry; - char * szLstFileName; - char * szPlainName; - size_t cchWorkBuffer = 0x400; - bool bResult = false; - - // First-level patches: Find the same file within the patch archive - // and verify by MD5-before-patch - if(haBase->haPatch == NULL) - { - TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize; - TFileEntry * pFileEntry; - - // Allocate working buffer for merging LST file - szLstFileName = STORM_ALLOC(char, cchWorkBuffer); - if(szLstFileName != NULL) - { - // Find a *-md5.lst file in the base archive - pBaseEntry = FindBaseLstFile(haBase); - if(pBaseEntry == NULL) - { - STORM_FREE(szLstFileName); - return false; - } - - // Parse the entire file table - for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) - { - // Look for "patch_metadata" file - if(IsPatchMetadataFile(pFileEntry)) - { - // Construct the name of the MD5 file - strcpy(szLstFileName, pFileEntry->szFileName); - szPlainName = (char *)GetPlainFileName(szLstFileName); - strcpy(szPlainName, pBaseEntry->szFileName); - - // Check for matching MD5 file - if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5)) - { - bResult = CreatePatchPrefix(haPatch, szLstFileName, (size_t)(szPlainName - szLstFileName)); - break; - } - } - } - - // Delete the merge buffer - STORM_FREE(szLstFileName); - } - } - - // For second-level patches, just take the patch prefix from the lower level patch - else - { - // There must be at least two patches in the chain - assert(haBase->haPatch->pPatchPrefix != NULL); - pPatchPrefix = haBase->haPatch->pPatchPrefix; - - // Copy the patch prefix - bResult = CreatePatchPrefix(haPatch, - pPatchPrefix->szPatchPrefix, - pPatchPrefix->nLength); - } - - return bResult; -} - -static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const char * szPatchPathPrefix) -{ - // If the patch prefix was explicitly entered, we use that one - if(szPatchPathPrefix != NULL) - return CreatePatchPrefix(haPatch, szPatchPathPrefix, 0); - - // Patches for World of Warcraft - they mostly do not use prefix. - // All patches that use patch prefix have the "base\\(patch_metadata) file present - if(GetFileEntryLocale(haPatch, "base\\" PATCH_METADATA_NAME, 0)) - return FindPatchPrefix_WoW_13164_13623(haBase, haPatch); - - // Updates for Starcraft II - // Match: LocalizedData\GameHotkeys.txt <==> Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt - // All Starcraft II base archives seem to have the file "StreamingBuckets.txt" present - if(GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0)) - return FindPatchPrefix_SC2(haBase, haPatch); - - // Diablo III patch MPQs don't use patch prefix - // Hearthstone MPQs don't use patch prefix - CreatePatchPrefix(haPatch, NULL, 0); - return true; -} - -//----------------------------------------------------------------------------- -// Public functions (StormLib internals) - -bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize) -{ - PMPQ_PATCH_HEADER pPatchHeader = (PMPQ_PATCH_HEADER)pvData; - BLIZZARD_BSDIFF40_FILE DiffFile; - DWORD dwPatchType; - - if(cbData >= sizeof(MPQ_PATCH_HEADER) + sizeof(BLIZZARD_BSDIFF40_FILE)) - { - dwPatchType = BSWAP_INT32_UNSIGNED(pPatchHeader->dwPatchType); - if(dwPatchType == 0x30445342) - { - // Give the caller the patch file size - if(pdwPatchedFileSize != NULL) - { - Decompress_RLE((LPBYTE)&DiffFile, sizeof(BLIZZARD_BSDIFF40_FILE), (LPBYTE)(pPatchHeader + 1), sizeof(BLIZZARD_BSDIFF40_FILE)); - DiffFile.NewFileSize = BSWAP_INT64_UNSIGNED(DiffFile.NewFileSize); - *pdwPatchedFileSize = (DWORD)DiffFile.NewFileSize; - return true; - } - } - } - - return false; -} - -int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf) -{ - DWORD cbMaxFileData = 0; - - // Overflow check - if((sizeof(MPQ_PATCH_HEADER) + cbMaxFileData) < cbMaxFileData) - return ERROR_NOT_ENOUGH_MEMORY; - if(hf->hfPatch == NULL) - return ERROR_INVALID_PARAMETER; - - // Initialize the entire structure with zeros - memset(pPatcher, 0, sizeof(TMPQPatcher)); - - // Copy the MD5 of the current file - memcpy(pPatcher->this_md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE); - - // Find out the biggest data size needed during the patching process - while(hf != NULL) - { - if(hf->pFileEntry->dwFileSize > cbMaxFileData) - cbMaxFileData = hf->pFileEntry->dwFileSize; - hf = hf->hfPatch; - } - - // Allocate primary and secondary buffer - pPatcher->pbFileData1 = STORM_ALLOC(BYTE, cbMaxFileData); - pPatcher->pbFileData2 = STORM_ALLOC(BYTE, cbMaxFileData); - if(!pPatcher->pbFileData1 || !pPatcher->pbFileData2) - return ERROR_NOT_ENOUGH_MEMORY; - - pPatcher->cbMaxFileData = cbMaxFileData; - return ERROR_SUCCESS; -} - -// -// Note: The patch may either be applied to the base file or to the previous version -// In Starcraft II, Mods\Core.SC2Mod\Base.SC2Data, file StreamingBuckets.txt: -// -// Base file MD5: 31376b0344b6df59ad009d4296125539 -// -// s2-update-base-23258: from 31376b0344b6df59ad009d4296125539 to 941a82683452e54bf024a8d491501824 -// s2-update-base-24540: from 31376b0344b6df59ad009d4296125539 to 941a82683452e54bf024a8d491501824 -// s2-update-base-26147: from 31376b0344b6df59ad009d4296125539 to d5d5253c762fac6b9761240288a0771a -// s2-update-base-28522: from 31376b0344b6df59ad009d4296125539 to 5a76c4b356920aab7afd22e0e1913d7a -// s2-update-base-30508: from 31376b0344b6df59ad009d4296125539 to 8cb0d4799893fe801cc78ae4488a3671 -// s2-update-base-32283: from 31376b0344b6df59ad009d4296125539 to 8cb0d4799893fe801cc78ae4488a3671 -// -// We don't keep all intermediate versions in memory, as it would cause massive -// memory usage during patching process. A prime example is the file -// DBFilesClient\\Item-Sparse.db2 from locale-enGB.MPQ (WoW 16965), which has -// 9 patches in a row, each requiring 70 MB memory (35 MB patch data + 35 MB work buffer) -// - -int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf) -{ - PMPQ_PATCH_HEADER pFullPatch; - MPQ_PATCH_HEADER PatchHeader1; - MPQ_PATCH_HEADER PatchHeader2 = {0}; - TMPQFile * hfBase = hf; - DWORD cbBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Move to the first patch - assert(hfBase->pbFileData == NULL); - assert(hfBase->cbFileData == 0); - hf = hf->hfPatch; - - // Read the header of the current patch - SFileReadFile((HANDLE)hf, &PatchHeader1, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL); - if(cbBytesRead != sizeof(MPQ_PATCH_HEADER)) - return ERROR_FILE_CORRUPT; - - // Perform the patching process - while(nError == ERROR_SUCCESS && hf != NULL) - { - // Try to read the next patch header. If the md5_before_patch - // still matches we go directly to the next one and repeat - while(hf->hfPatch != NULL) - { - // Attempt to read the patch header - SFileReadFile((HANDLE)hf->hfPatch, &PatchHeader2, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL); - if(cbBytesRead != sizeof(MPQ_PATCH_HEADER)) - return ERROR_FILE_CORRUPT; - - // Compare the md5_before_patch - if(memcmp(PatchHeader2.md5_before_patch, pPatcher->this_md5, MD5_DIGEST_SIZE)) - break; - - // Move one patch fuhrter - PatchHeader1 = PatchHeader2; - hf = hf->hfPatch; - } - - // Allocate memory for the patch data - pFullPatch = LoadFullFilePatch(hf, PatchHeader1); - if(pFullPatch != NULL) - { - // Apply the patch - nError = ApplyFilePatch(pPatcher, pFullPatch); - STORM_FREE(pFullPatch); - } - else - { - nError = ERROR_FILE_CORRUPT; - } - - // Move to the next patch - PatchHeader1 = PatchHeader2; - pPatcher->nCounter++; - hf = hf->hfPatch; - } - - // Put the result data to the file structure - if(nError == ERROR_SUCCESS) - { - // Swap the pointer to the file data structure - if(pPatcher->nCounter & 0x01) - { - hfBase->pbFileData = pPatcher->pbFileData2; - pPatcher->pbFileData2 = NULL; - } - else - { - hfBase->pbFileData = pPatcher->pbFileData1; - pPatcher->pbFileData1 = NULL; - } - - // Also supply the data size - hfBase->cbFileData = pPatcher->cbFileData; - } - - return ERROR_SUCCESS; -} - -void Patch_Finalize(TMPQPatcher * pPatcher) -{ - if(pPatcher != NULL) - { - if(pPatcher->pbFileData1 != NULL) - STORM_FREE(pPatcher->pbFileData1); - if(pPatcher->pbFileData2 != NULL) - STORM_FREE(pPatcher->pbFileData2); - - memset(pPatcher, 0, sizeof(TMPQPatcher)); - } -} - - -//----------------------------------------------------------------------------- -// Public functions - -// -// Patch prefix is the path subdirectory where the patched files are within MPQ. -// -// Example 1: -// Main MPQ: locale-enGB.MPQ -// Patch MPQ: wow-update-12694.MPQ -// File in main MPQ: DBFilesClient\Achievement.dbc -// File in patch MPQ: enGB\DBFilesClient\Achievement.dbc -// Path prefix: enGB -// -// Example 2: -// Main MPQ: expansion1.MPQ -// Patch MPQ: wow-update-12694.MPQ -// File in main MPQ: DBFilesClient\Achievement.dbc -// File in patch MPQ: Base\DBFilesClient\Achievement.dbc -// Path prefix: Base -// - -bool WINAPI SFileOpenPatchArchive( - HANDLE hMpq, - const TCHAR * szPatchMpqName, - const char * szPatchPathPrefix, - DWORD dwFlags) -{ - TMPQArchive * haPatch; - TMPQArchive * ha = (TMPQArchive *)hMpq; - HANDLE hPatchMpq = NULL; - int nError = ERROR_SUCCESS; - - // Keep compiler happy - dwFlags = dwFlags; - - // Verify input parameters - if(!IsValidMpqHandle(hMpq)) - nError = ERROR_INVALID_HANDLE; - if(szPatchMpqName == NULL || *szPatchMpqName == 0) - nError = ERROR_INVALID_PARAMETER; - - // - // We don't allow adding patches to archives that have been open for write - // - // Error scenario: - // - // 1) Open archive for writing - // 2) Modify or replace a file - // 3) Add patch archive to the opened MPQ - // 4) Read patched file - // 5) Now what ? - // - - if(nError == ERROR_SUCCESS) - { - if(!(ha->dwFlags & MPQ_FLAG_READ_ONLY)) - nError = ERROR_ACCESS_DENIED; - } - - // Open the archive like it is normal archive - if(nError == ERROR_SUCCESS) - { - if(!SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY | MPQ_OPEN_PATCH, &hPatchMpq)) - return false; - haPatch = (TMPQArchive *)hPatchMpq; - - // We need to remember the proper patch prefix to match names of patched files - FindPatchPrefix(ha, (TMPQArchive *)hPatchMpq, szPatchPathPrefix); - - // Now add the patch archive to the list of patches to the original MPQ - while(ha != NULL) - { - if(ha->haPatch == NULL) - { - haPatch->haBase = ha; - ha->haPatch = haPatch; - return true; - } - - // Move to the next archive - ha = ha->haPatch; - } - - // Should never happen - nError = ERROR_CAN_NOT_COMPLETE; - } - - SetLastError(nError); - return false; -} - -bool WINAPI SFileIsPatchedArchive(HANDLE hMpq) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - - // Verify input parameters - if(!IsValidMpqHandle(hMpq)) - return false; - - return (ha->haPatch != NULL); -} +/*****************************************************************************/
+/* SFilePatchArchives.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* Description: */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 18.08.10 1.00 Lad The first version of SFilePatchArchives.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+#define PATCH_SIGNATURE_HEADER 0x48435450
+#define PATCH_SIGNATURE_MD5 0x5f35444d
+#define PATCH_SIGNATURE_XFRM 0x4d524658
+
+#define SIZE_OF_XFRM_HEADER 0x0C
+
+// Header for incremental patch files
+typedef struct _MPQ_PATCH_HEADER
+{
+ //-- PATCH header -----------------------------------
+ DWORD dwSignature; // 'PTCH'
+ 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
+ BYTE md5_before_patch[0x10]; // MD5 of the original (unpached) file
+ BYTE md5_after_patch[0x10]; // MD5 of the patched file
+
+ //-- XFRM block -------------------------------------
+ DWORD dwXFRM; // 'XFRM'
+ DWORD dwXfrmBlockSize; // Size of the XFRM block, includes XFRM header and patch data
+ DWORD dwPatchType; // Type of patch ('BSD0' or 'COPY')
+
+ // Followed by the patch data
+} MPQ_PATCH_HEADER, *PMPQ_PATCH_HEADER;
+
+typedef struct _BLIZZARD_BSDIFF40_FILE
+{
+ ULONGLONG Signature;
+ ULONGLONG CtrlBlockSize;
+ ULONGLONG DataBlockSize;
+ ULONGLONG NewFileSize;
+} BLIZZARD_BSDIFF40_FILE, *PBLIZZARD_BSDIFF40_FILE;
+
+typedef struct _BSDIFF_CTRL_BLOCK
+{
+ DWORD dwAddDataLength;
+ DWORD dwMovDataLength;
+ DWORD dwOldMoveLength;
+
+} BSDIFF_CTRL_BLOCK, *PBSDIFF_CTRL_BLOCK;
+
+typedef struct _LOCALIZED_MPQ_INFO
+{
+ const char * szNameTemplate; // Name template
+ size_t nLangOffset; // Offset of the language
+ size_t nLength; // Length of the name template
+} LOCALIZED_MPQ_INFO, *PLOCALIZED_MPQ_INFO;
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+// 4-byte groups for all languages
+static const char * LanguageList = "baseteenenUSenGBenCNenTWdeDEesESesMXfrFRitITkoKRptBRptPTruRUzhCNzhTW";
+
+// List of localized MPQs for World of Warcraft
+static LOCALIZED_MPQ_INFO LocaleMpqs_WoW[] =
+{
+ {"expansion1-locale-####", 18, 22},
+ {"expansion1-speech-####", 18, 22},
+ {"expansion2-locale-####", 18, 22},
+ {"expansion2-speech-####", 18, 22},
+ {"expansion3-locale-####", 18, 22},
+ {"expansion3-speech-####", 18, 22},
+ {"locale-####", 7, 11},
+ {"speech-####", 7, 11},
+ {NULL, 0, 0}
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static inline bool IsPatchMetadataFile(TFileEntry * pFileEntry)
+{
+ // The file must ave a name
+ if(pFileEntry->szFileName != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
+ {
+ // The file must be small
+ if(0 < pFileEntry->dwFileSize && pFileEntry->dwFileSize < 0x40)
+ {
+ // Compare the plain name
+ return (_stricmp(GetPlainFileName(pFileEntry->szFileName), PATCH_METADATA_NAME) == 0);
+ }
+ }
+
+ // Not a patch_metadata
+ return false;
+}
+
+static void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE pbCompressed, DWORD cbCompressed)
+{
+ LPBYTE pbDecompressedEnd = pbDecompressed + cbDecompressed;
+ LPBYTE pbCompressedEnd = pbCompressed + cbCompressed;
+ BYTE RepeatCount;
+ BYTE OneByte;
+
+ // Cut the initial DWORD from the compressed chunk
+ pbCompressed += sizeof(DWORD);
+
+ // Pre-fill decompressed buffer with zeros
+ memset(pbDecompressed, 0, cbDecompressed);
+
+ // Unpack
+ while(pbCompressed < pbCompressedEnd && pbDecompressed < pbDecompressedEnd)
+ {
+ OneByte = *pbCompressed++;
+
+ // Is it a repetition byte ?
+ if(OneByte & 0x80)
+ {
+ RepeatCount = (OneByte & 0x7F) + 1;
+ for(BYTE i = 0; i < RepeatCount; i++)
+ {
+ if(pbDecompressed == pbDecompressedEnd || pbCompressed == pbCompressedEnd)
+ break;
+
+ *pbDecompressed++ = *pbCompressed++;
+ }
+ }
+ else
+ {
+ pbDecompressed += (OneByte + 1);
+ }
+ }
+}
+
+static int LoadFilePatch_COPY(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch)
+{
+ DWORD cbBytesToRead = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER);
+ DWORD cbBytesRead = 0;
+
+ // Simply load the rest of the patch
+ SFileReadFile((HANDLE)hf, (pFullPatch + 1), cbBytesToRead, &cbBytesRead, NULL);
+ return (cbBytesRead == cbBytesToRead) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
+}
+
+static int LoadFilePatch_BSD0(TMPQFile * hf, PMPQ_PATCH_HEADER pFullPatch)
+{
+ LPBYTE pbDecompressed = (LPBYTE)(pFullPatch + 1);
+ LPBYTE pbCompressed = NULL;
+ DWORD cbDecompressed = 0;
+ DWORD cbCompressed = 0;
+ DWORD dwBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Calculate the size of compressed data
+ cbDecompressed = pFullPatch->dwSizeOfPatchData - sizeof(MPQ_PATCH_HEADER);
+ cbCompressed = pFullPatch->dwXfrmBlockSize - SIZE_OF_XFRM_HEADER;
+
+ // Is that file compressed?
+ if(cbCompressed < cbDecompressed)
+ {
+ pbCompressed = STORM_ALLOC(BYTE, cbCompressed);
+ if(pbCompressed == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Read the compressed patch data
+ if(nError == ERROR_SUCCESS)
+ {
+ SFileReadFile((HANDLE)hf, pbCompressed, cbCompressed, &dwBytesRead, NULL);
+ if(dwBytesRead != cbCompressed)
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Decompress the data
+ if(nError == ERROR_SUCCESS)
+ Decompress_RLE(pbDecompressed, cbDecompressed, pbCompressed, cbCompressed);
+
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+ }
+ else
+ {
+ SFileReadFile((HANDLE)hf, pbDecompressed, cbDecompressed, &dwBytesRead, NULL);
+ if(dwBytesRead != cbDecompressed)
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ return nError;
+}
+
+static int ApplyFilePatch_COPY(
+ TMPQPatcher * pPatcher,
+ PMPQ_PATCH_HEADER pFullPatch,
+ LPBYTE pbTarget,
+ LPBYTE pbSource)
+{
+ // Sanity checks
+ assert(pPatcher->cbMaxFileData >= pPatcher->cbFileData);
+ pFullPatch = pFullPatch;
+
+ // Copy the patch data as-is
+ memcpy(pbTarget, pbSource, pPatcher->cbFileData);
+ return ERROR_SUCCESS;
+}
+
+static int ApplyFilePatch_BSD0(
+ TMPQPatcher * pPatcher,
+ PMPQ_PATCH_HEADER pFullPatch,
+ LPBYTE pbTarget,
+ LPBYTE pbSource)
+{
+ PBLIZZARD_BSDIFF40_FILE pBsdiff;
+ PBSDIFF_CTRL_BLOCK pCtrlBlock;
+ LPBYTE pbPatchData = (LPBYTE)(pFullPatch + 1);
+ LPBYTE pDataBlock;
+ LPBYTE pExtraBlock;
+ LPBYTE pbOldData = pbSource;
+ LPBYTE pbNewData = pbTarget;
+ DWORD dwCombineSize;
+ DWORD dwNewOffset = 0; // Current position to patch
+ DWORD dwOldOffset = 0; // Current source position
+ DWORD dwNewSize; // Patched file size
+ DWORD dwOldSize = pPatcher->cbFileData; // File size before patch
+
+ // Get pointer to the patch header
+ // Format of BSDIFF header corresponds to original BSDIFF, which is:
+ // 0000 8 bytes signature "BSDIFF40"
+ // 0008 8 bytes size of the control block
+ // 0010 8 bytes size of the data block
+ // 0018 8 bytes new size of the patched file
+ pBsdiff = (PBLIZZARD_BSDIFF40_FILE)pbPatchData;
+ pbPatchData += sizeof(BLIZZARD_BSDIFF40_FILE);
+
+ // Get pointer to the 32-bit BSDIFF control block
+ // The control block follows immediately after the BSDIFF header
+ // and consists of three 32-bit integers
+ // 0000 4 bytes Length to copy from the BSDIFF data block the new file
+ // 0004 4 bytes Length to copy from the BSDIFF extra block
+ // 0008 4 bytes Size to increment source file offset
+ pCtrlBlock = (PBSDIFF_CTRL_BLOCK)pbPatchData;
+ pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->CtrlBlockSize);
+
+ // Get the pointer to the data block
+ pDataBlock = (LPBYTE)pbPatchData;
+ pbPatchData += (size_t)BSWAP_INT64_UNSIGNED(pBsdiff->DataBlockSize);
+
+ // Get the pointer to the extra block
+ pExtraBlock = (LPBYTE)pbPatchData;
+ dwNewSize = (DWORD)BSWAP_INT64_UNSIGNED(pBsdiff->NewFileSize);
+
+ // Now patch the file
+ while(dwNewOffset < dwNewSize)
+ {
+ DWORD dwAddDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwAddDataLength);
+ DWORD dwMovDataLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwMovDataLength);
+ DWORD dwOldMoveLength = BSWAP_INT32_UNSIGNED(pCtrlBlock->dwOldMoveLength);
+ DWORD i;
+
+ // Sanity check
+ if((dwNewOffset + dwAddDataLength) > dwNewSize)
+ return ERROR_FILE_CORRUPT;
+
+ // Read the diff string to the target buffer
+ memcpy(pbNewData + dwNewOffset, pDataBlock, dwAddDataLength);
+ pDataBlock += dwAddDataLength;
+
+ // Get the longest block that we can combine
+ dwCombineSize = ((dwOldOffset + dwAddDataLength) >= dwOldSize) ? (dwOldSize - dwOldOffset) : dwAddDataLength;
+
+ // Now combine the patch data with the original file
+ for(i = 0; i < dwCombineSize; i++)
+ pbNewData[dwNewOffset + i] = pbNewData[dwNewOffset + i] + pbOldData[dwOldOffset + i];
+
+ // Move the offsets
+ dwNewOffset += dwAddDataLength;
+ dwOldOffset += dwAddDataLength;
+
+ // Sanity check
+ if((dwNewOffset + dwMovDataLength) > dwNewSize)
+ return ERROR_FILE_CORRUPT;
+
+ // Copy the data from the extra block in BSDIFF patch
+ memcpy(pbNewData + dwNewOffset, pExtraBlock, dwMovDataLength);
+ pExtraBlock += dwMovDataLength;
+ dwNewOffset += dwMovDataLength;
+
+ // Move the old offset
+ if(dwOldMoveLength & 0x80000000)
+ dwOldMoveLength = 0x80000000 - dwOldMoveLength;
+ dwOldOffset += dwOldMoveLength;
+ pCtrlBlock++;
+ }
+
+ // The size after patch must match
+ if(dwNewOffset != pFullPatch->dwSizeAfterPatch)
+ return ERROR_FILE_CORRUPT;
+
+ // Update the new data size
+ pPatcher->cbFileData = dwNewOffset;
+ return ERROR_SUCCESS;
+}
+
+static PMPQ_PATCH_HEADER LoadFullFilePatch(TMPQFile * hf, MPQ_PATCH_HEADER & PatchHeader)
+{
+ PMPQ_PATCH_HEADER pFullPatch;
+ int nError = ERROR_SUCCESS;
+
+ // BSWAP the entire header, if needed
+ BSWAP_ARRAY32_UNSIGNED(&PatchHeader, sizeof(DWORD) * 6);
+ BSWAP_ARRAY32_UNSIGNED(&PatchHeader.dwXFRM, sizeof(DWORD) * 3);
+
+ // Verify the signatures in the patch header
+ if(PatchHeader.dwSignature != PATCH_SIGNATURE_HEADER || PatchHeader.dwMD5 != PATCH_SIGNATURE_MD5 || PatchHeader.dwXFRM != PATCH_SIGNATURE_XFRM)
+ return NULL;
+
+ // Allocate space for patch header and compressed data
+ pFullPatch = (PMPQ_PATCH_HEADER)STORM_ALLOC(BYTE, PatchHeader.dwSizeOfPatchData);
+ if(pFullPatch != NULL)
+ {
+ // Copy the patch header
+ memcpy(pFullPatch, &PatchHeader, sizeof(MPQ_PATCH_HEADER));
+
+ // Read the patch, depending on patch type
+ if(nError == ERROR_SUCCESS)
+ {
+ switch(PatchHeader.dwPatchType)
+ {
+ case 0x59504f43: // 'COPY'
+ nError = LoadFilePatch_COPY(hf, pFullPatch);
+ break;
+
+ case 0x30445342: // 'BSD0'
+ nError = LoadFilePatch_BSD0(hf, pFullPatch);
+ break;
+
+ default:
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+ }
+
+ // If something failed, free the patch buffer
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pFullPatch);
+ pFullPatch = NULL;
+ }
+ }
+
+ // Give the result to the caller
+ return pFullPatch;
+}
+
+static int ApplyFilePatch(
+ TMPQPatcher * pPatcher,
+ PMPQ_PATCH_HEADER pFullPatch)
+{
+ LPBYTE pbSource = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData2 : pPatcher->pbFileData1;
+ LPBYTE pbTarget = (pPatcher->nCounter & 0x1) ? pPatcher->pbFileData1 : pPatcher->pbFileData2;
+ int nError;
+
+ // Sanity checks
+ assert(pFullPatch->dwSizeAfterPatch <= pPatcher->cbMaxFileData);
+
+ // Apply the patch according to the type
+ switch(pFullPatch->dwPatchType)
+ {
+ case 0x59504f43: // 'COPY'
+ nError = ApplyFilePatch_COPY(pPatcher, pFullPatch, pbTarget, pbSource);
+ break;
+
+ case 0x30445342: // 'BSD0'
+ nError = ApplyFilePatch_BSD0(pPatcher, pFullPatch, pbTarget, pbSource);
+ break;
+
+ default:
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Verify MD5 after patch
+ if(nError == ERROR_SUCCESS && pFullPatch->dwSizeAfterPatch != 0)
+ {
+ // Verify the patched file
+ if(!VerifyDataBlockHash(pbTarget, pFullPatch->dwSizeAfterPatch, pFullPatch->md5_after_patch))
+ nError = ERROR_FILE_CORRUPT;
+
+ // Copy the MD5 of the new block
+ memcpy(pPatcher->this_md5, pFullPatch->md5_after_patch, MD5_DIGEST_SIZE);
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions (patch prefix matching)
+
+static bool CreatePatchPrefix(TMPQArchive * ha, const char * szFileName, size_t nLength)
+{
+ TMPQNamePrefix * pNewPrefix;
+
+ // If the end of the patch prefix was not entered, find it
+ if(szFileName != NULL && nLength == 0)
+ nLength = strlen(szFileName);
+
+ // Create the patch prefix
+ pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength);
+ if(pNewPrefix != NULL)
+ {
+ // Fill the name prefix
+ pNewPrefix->nLength = nLength;
+ pNewPrefix->szPatchPrefix[0] = 0;
+
+ // Fill the name prefix. Also add the backslash
+ if(szFileName && nLength)
+ {
+ memcpy(pNewPrefix->szPatchPrefix, szFileName, nLength);
+ pNewPrefix->szPatchPrefix[nLength] = 0;
+ }
+ }
+
+ ha->pPatchPrefix = pNewPrefix;
+ return (pNewPrefix != NULL);
+}
+
+static bool IsMatchingPatchFile(
+ TMPQArchive * ha,
+ const char * szFileName,
+ LPBYTE pbFileMd5)
+{
+ MPQ_PATCH_HEADER PatchHeader = {0};
+ HANDLE hFile = NULL;
+ DWORD dwTransferred = 0;
+ bool bResult = false;
+
+ // Open the file and load the patch header
+ if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
+ {
+ // Load the patch header
+ SFileReadFile(hFile, &PatchHeader, sizeof(MPQ_PATCH_HEADER), &dwTransferred, NULL);
+ BSWAP_ARRAY32_UNSIGNED(pPatchHeader, sizeof(DWORD) * 6);
+
+ // If the file contains an incremental patch,
+ // compare the "MD5 before patching" with the base file MD5
+ if(dwTransferred == sizeof(MPQ_PATCH_HEADER) && PatchHeader.dwSignature == PATCH_SIGNATURE_HEADER)
+ bResult = (!memcmp(PatchHeader.md5_before_patch, pbFileMd5, MD5_DIGEST_SIZE));
+
+ // Close the file
+ SFileCloseFile(hFile);
+ }
+
+ return bResult;
+}
+
+static const char * FindArchiveLanguage(TMPQArchive * ha, PLOCALIZED_MPQ_INFO pMpqInfo)
+{
+ TFileEntry * pFileEntry;
+ const char * szLanguage = LanguageList;
+ char szFileName[0x40];
+
+ // Iterate through all localized languages
+ while(pMpqInfo->szNameTemplate != NULL)
+ {
+ // Iterate through all languages
+ for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage += 4)
+ {
+ // Construct the file name
+ memcpy(szFileName, pMpqInfo->szNameTemplate, pMpqInfo->nLength);
+ szFileName[pMpqInfo->nLangOffset + 0] = szLanguage[0];
+ szFileName[pMpqInfo->nLangOffset + 1] = szLanguage[1];
+ szFileName[pMpqInfo->nLangOffset + 2] = szLanguage[2];
+ szFileName[pMpqInfo->nLangOffset + 3] = szLanguage[3];
+
+ // Append the suffix
+ memcpy(szFileName + pMpqInfo->nLength, "-md5.lst", 9);
+
+ // Check whether the name exists
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
+ if(pFileEntry != NULL)
+ return szLanguage;
+ }
+
+ // Move to the next language name
+ pMpqInfo++;
+ }
+
+ // Not found
+ return NULL;
+}
+
+static TFileEntry * FindBaseLstFile(TMPQArchive * ha)
+{
+ TFileEntry * pFileEntry;
+ const char * szLanguage;
+ char szFileName[0x40];
+
+ // Prepare the file name tenplate
+ memcpy(szFileName, "####-md5.lst", 13);
+
+ // Try all languages
+ for(szLanguage = LanguageList; szLanguage[0] != 0; szLanguage++)
+ {
+ // Copy the language name
+ szFileName[0] = szLanguage[0];
+ szFileName[1] = szLanguage[1];
+ szFileName[2] = szLanguage[2];
+ szFileName[3] = szLanguage[3];
+
+ // Check whether this file exists
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
+ if(pFileEntry != NULL)
+ return pFileEntry;
+ }
+
+ return NULL;
+}
+
+static bool FindPatchPrefix_WoW_13164_13623(TMPQArchive * haBase, TMPQArchive * haPatch)
+{
+ const char * szPatchPrefix;
+ char szNamePrefix[0x08];
+
+ // Try to find the language of the MPQ archive
+ szPatchPrefix = FindArchiveLanguage(haBase, LocaleMpqs_WoW);
+ if(szPatchPrefix == NULL)
+ szPatchPrefix = "Base";
+
+ // Format the patch prefix
+ szNamePrefix[0] = szPatchPrefix[0];
+ szNamePrefix[1] = szPatchPrefix[1];
+ szNamePrefix[2] = szPatchPrefix[2];
+ szNamePrefix[3] = szPatchPrefix[3];
+ szNamePrefix[4] = '\\';
+ szNamePrefix[5] = 0;
+ return CreatePatchPrefix(haPatch, szNamePrefix, 5);
+}
+
+//
+// Find match in Starcraft II patch MPQs
+// Match a LST file in the root directory if the MPQ with any of the file in subdirectories
+//
+// The problem:
+// Base: enGB-md5.lst
+// Patch: Campaigns\Liberty.SC2Campaign\enGB.SC2Assets\enGB-md5.lst
+// Campaigns\Liberty.SC2Campaign\enGB.SC2Data\enGB-md5.lst
+// Campaigns\LibertyStory.SC2Campaign\enGB.SC2Data\enGB-md5.lst
+// Campaigns\LibertyStory.SC2Campaign\enGB.SC2Data\enGB-md5.lst Mods\Core.SC2Mod\enGB.SC2Assets\enGB-md5.lst
+// Mods\Core.SC2Mod\enGB.SC2Data\enGB-md5.lst
+// Mods\Liberty.SC2Mod\enGB.SC2Assets\enGB-md5.lst
+// Mods\Liberty.SC2Mod\enGB.SC2Data\enGB-md5.lst
+// Mods\LibertyMulti.SC2Mod\enGB.SC2Data\enGB-md5.lst
+//
+// Solution:
+// We need to match the file by its MD5
+//
+
+
+static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch)
+{
+ TMPQNamePrefix * pPatchPrefix;
+ TFileEntry * pBaseEntry;
+ char * szLstFileName;
+ char * szPlainName;
+ size_t cchWorkBuffer = 0x400;
+ bool bResult = false;
+
+ // First-level patches: Find the same file within the patch archive
+ // and verify by MD5-before-patch
+ if(haBase->haPatch == NULL)
+ {
+ TFileEntry * pFileTableEnd = haPatch->pFileTable + haPatch->dwFileTableSize;
+ TFileEntry * pFileEntry;
+
+ // Allocate working buffer for merging LST file
+ szLstFileName = STORM_ALLOC(char, cchWorkBuffer);
+ if(szLstFileName != NULL)
+ {
+ // Find a *-md5.lst file in the base archive
+ pBaseEntry = FindBaseLstFile(haBase);
+ if(pBaseEntry == NULL)
+ {
+ STORM_FREE(szLstFileName);
+ return false;
+ }
+
+ // Parse the entire file table
+ for(pFileEntry = haPatch->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Look for "patch_metadata" file
+ if(IsPatchMetadataFile(pFileEntry))
+ {
+ // Construct the name of the MD5 file
+ strcpy(szLstFileName, pFileEntry->szFileName);
+ szPlainName = (char *)GetPlainFileName(szLstFileName);
+ strcpy(szPlainName, pBaseEntry->szFileName);
+
+ // Check for matching MD5 file
+ if(IsMatchingPatchFile(haPatch, szLstFileName, pBaseEntry->md5))
+ {
+ bResult = CreatePatchPrefix(haPatch, szLstFileName, (size_t)(szPlainName - szLstFileName));
+ break;
+ }
+ }
+ }
+
+ // Delete the merge buffer
+ STORM_FREE(szLstFileName);
+ }
+ }
+
+ // For second-level patches, just take the patch prefix from the lower level patch
+ else
+ {
+ // There must be at least two patches in the chain
+ assert(haBase->haPatch->pPatchPrefix != NULL);
+ pPatchPrefix = haBase->haPatch->pPatchPrefix;
+
+ // Copy the patch prefix
+ bResult = CreatePatchPrefix(haPatch,
+ pPatchPrefix->szPatchPrefix,
+ pPatchPrefix->nLength);
+ }
+
+ return bResult;
+}
+
+static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const char * szPatchPathPrefix)
+{
+ // If the patch prefix was explicitly entered, we use that one
+ if(szPatchPathPrefix != NULL)
+ return CreatePatchPrefix(haPatch, szPatchPathPrefix, 0);
+
+ // Patches for World of Warcraft - they mostly do not use prefix.
+ // All patches that use patch prefix have the "base\\(patch_metadata) file present
+ if(GetFileEntryLocale(haPatch, "base\\" PATCH_METADATA_NAME, 0))
+ return FindPatchPrefix_WoW_13164_13623(haBase, haPatch);
+
+ // Updates for Starcraft II
+ // Match: LocalizedData\GameHotkeys.txt <==> Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt
+ // All Starcraft II base archives seem to have the file "StreamingBuckets.txt" present
+ if(GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0))
+ return FindPatchPrefix_SC2(haBase, haPatch);
+
+ // Diablo III patch MPQs don't use patch prefix
+ // Hearthstone MPQs don't use patch prefix
+ CreatePatchPrefix(haPatch, NULL, 0);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions (StormLib internals)
+
+bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize)
+{
+ PMPQ_PATCH_HEADER pPatchHeader = (PMPQ_PATCH_HEADER)pvData;
+ BLIZZARD_BSDIFF40_FILE DiffFile;
+ DWORD dwPatchType;
+
+ if(cbData >= sizeof(MPQ_PATCH_HEADER) + sizeof(BLIZZARD_BSDIFF40_FILE))
+ {
+ dwPatchType = BSWAP_INT32_UNSIGNED(pPatchHeader->dwPatchType);
+ if(dwPatchType == 0x30445342)
+ {
+ // Give the caller the patch file size
+ if(pdwPatchedFileSize != NULL)
+ {
+ Decompress_RLE((LPBYTE)&DiffFile, sizeof(BLIZZARD_BSDIFF40_FILE), (LPBYTE)(pPatchHeader + 1), sizeof(BLIZZARD_BSDIFF40_FILE));
+ DiffFile.NewFileSize = BSWAP_INT64_UNSIGNED(DiffFile.NewFileSize);
+ *pdwPatchedFileSize = (DWORD)DiffFile.NewFileSize;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf)
+{
+ DWORD cbMaxFileData = 0;
+
+ // Overflow check
+ if((sizeof(MPQ_PATCH_HEADER) + cbMaxFileData) < cbMaxFileData)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ if(hf->hfPatch == NULL)
+ return ERROR_INVALID_PARAMETER;
+
+ // Initialize the entire structure with zeros
+ memset(pPatcher, 0, sizeof(TMPQPatcher));
+
+ // Copy the MD5 of the current file
+ memcpy(pPatcher->this_md5, hf->pFileEntry->md5, MD5_DIGEST_SIZE);
+
+ // Find out the biggest data size needed during the patching process
+ while(hf != NULL)
+ {
+ if(hf->pFileEntry->dwFileSize > cbMaxFileData)
+ cbMaxFileData = hf->pFileEntry->dwFileSize;
+ hf = hf->hfPatch;
+ }
+
+ // Allocate primary and secondary buffer
+ pPatcher->pbFileData1 = STORM_ALLOC(BYTE, cbMaxFileData);
+ pPatcher->pbFileData2 = STORM_ALLOC(BYTE, cbMaxFileData);
+ if(!pPatcher->pbFileData1 || !pPatcher->pbFileData2)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ pPatcher->cbMaxFileData = cbMaxFileData;
+ return ERROR_SUCCESS;
+}
+
+//
+// Note: The patch may either be applied to the base file or to the previous version
+// In Starcraft II, Mods\Core.SC2Mod\Base.SC2Data, file StreamingBuckets.txt:
+//
+// Base file MD5: 31376b0344b6df59ad009d4296125539
+//
+// s2-update-base-23258: from 31376b0344b6df59ad009d4296125539 to 941a82683452e54bf024a8d491501824
+// s2-update-base-24540: from 31376b0344b6df59ad009d4296125539 to 941a82683452e54bf024a8d491501824
+// s2-update-base-26147: from 31376b0344b6df59ad009d4296125539 to d5d5253c762fac6b9761240288a0771a
+// s2-update-base-28522: from 31376b0344b6df59ad009d4296125539 to 5a76c4b356920aab7afd22e0e1913d7a
+// s2-update-base-30508: from 31376b0344b6df59ad009d4296125539 to 8cb0d4799893fe801cc78ae4488a3671
+// s2-update-base-32283: from 31376b0344b6df59ad009d4296125539 to 8cb0d4799893fe801cc78ae4488a3671
+//
+// We don't keep all intermediate versions in memory, as it would cause massive
+// memory usage during patching process. A prime example is the file
+// DBFilesClient\\Item-Sparse.db2 from locale-enGB.MPQ (WoW 16965), which has
+// 9 patches in a row, each requiring 70 MB memory (35 MB patch data + 35 MB work buffer)
+//
+
+int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf)
+{
+ PMPQ_PATCH_HEADER pFullPatch;
+ MPQ_PATCH_HEADER PatchHeader1;
+ MPQ_PATCH_HEADER PatchHeader2 = {0};
+ TMPQFile * hfBase = hf;
+ DWORD cbBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Move to the first patch
+ assert(hfBase->pbFileData == NULL);
+ assert(hfBase->cbFileData == 0);
+ hf = hf->hfPatch;
+
+ // Read the header of the current patch
+ SFileReadFile((HANDLE)hf, &PatchHeader1, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL);
+ if(cbBytesRead != sizeof(MPQ_PATCH_HEADER))
+ return ERROR_FILE_CORRUPT;
+
+ // Perform the patching process
+ while(nError == ERROR_SUCCESS && hf != NULL)
+ {
+ // Try to read the next patch header. If the md5_before_patch
+ // still matches we go directly to the next one and repeat
+ while(hf->hfPatch != NULL)
+ {
+ // Attempt to read the patch header
+ SFileReadFile((HANDLE)hf->hfPatch, &PatchHeader2, sizeof(MPQ_PATCH_HEADER), &cbBytesRead, NULL);
+ if(cbBytesRead != sizeof(MPQ_PATCH_HEADER))
+ return ERROR_FILE_CORRUPT;
+
+ // Compare the md5_before_patch
+ if(memcmp(PatchHeader2.md5_before_patch, pPatcher->this_md5, MD5_DIGEST_SIZE))
+ break;
+
+ // Move one patch fuhrter
+ PatchHeader1 = PatchHeader2;
+ hf = hf->hfPatch;
+ }
+
+ // Allocate memory for the patch data
+ pFullPatch = LoadFullFilePatch(hf, PatchHeader1);
+ if(pFullPatch != NULL)
+ {
+ // Apply the patch
+ nError = ApplyFilePatch(pPatcher, pFullPatch);
+ STORM_FREE(pFullPatch);
+ }
+ else
+ {
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Move to the next patch
+ PatchHeader1 = PatchHeader2;
+ pPatcher->nCounter++;
+ hf = hf->hfPatch;
+ }
+
+ // Put the result data to the file structure
+ if(nError == ERROR_SUCCESS)
+ {
+ // Swap the pointer to the file data structure
+ if(pPatcher->nCounter & 0x01)
+ {
+ hfBase->pbFileData = pPatcher->pbFileData2;
+ pPatcher->pbFileData2 = NULL;
+ }
+ else
+ {
+ hfBase->pbFileData = pPatcher->pbFileData1;
+ pPatcher->pbFileData1 = NULL;
+ }
+
+ // Also supply the data size
+ hfBase->cbFileData = pPatcher->cbFileData;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+void Patch_Finalize(TMPQPatcher * pPatcher)
+{
+ if(pPatcher != NULL)
+ {
+ if(pPatcher->pbFileData1 != NULL)
+ STORM_FREE(pPatcher->pbFileData1);
+ if(pPatcher->pbFileData2 != NULL)
+ STORM_FREE(pPatcher->pbFileData2);
+
+ memset(pPatcher, 0, sizeof(TMPQPatcher));
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+//
+// Patch prefix is the path subdirectory where the patched files are within MPQ.
+//
+// Example 1:
+// Main MPQ: locale-enGB.MPQ
+// Patch MPQ: wow-update-12694.MPQ
+// File in main MPQ: DBFilesClient\Achievement.dbc
+// File in patch MPQ: enGB\DBFilesClient\Achievement.dbc
+// Path prefix: enGB
+//
+// Example 2:
+// Main MPQ: expansion1.MPQ
+// Patch MPQ: wow-update-12694.MPQ
+// File in main MPQ: DBFilesClient\Achievement.dbc
+// File in patch MPQ: Base\DBFilesClient\Achievement.dbc
+// Path prefix: Base
+//
+
+bool WINAPI SFileOpenPatchArchive(
+ HANDLE hMpq,
+ const TCHAR * szPatchMpqName,
+ const char * szPatchPathPrefix,
+ DWORD dwFlags)
+{
+ TMPQArchive * haPatch;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ HANDLE hPatchMpq = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Keep compiler happy
+ dwFlags = dwFlags;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(hMpq))
+ nError = ERROR_INVALID_HANDLE;
+ if(szPatchMpqName == NULL || *szPatchMpqName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+
+ //
+ // We don't allow adding patches to archives that have been open for write
+ //
+ // Error scenario:
+ //
+ // 1) Open archive for writing
+ // 2) Modify or replace a file
+ // 3) Add patch archive to the opened MPQ
+ // 4) Read patched file
+ // 5) Now what ?
+ //
+
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!(ha->dwFlags & MPQ_FLAG_READ_ONLY))
+ nError = ERROR_ACCESS_DENIED;
+ }
+
+ // Open the archive like it is normal archive
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!SFileOpenArchive(szPatchMpqName, 0, MPQ_OPEN_READ_ONLY | MPQ_OPEN_PATCH, &hPatchMpq))
+ return false;
+ haPatch = (TMPQArchive *)hPatchMpq;
+
+ // We need to remember the proper patch prefix to match names of patched files
+ FindPatchPrefix(ha, (TMPQArchive *)hPatchMpq, szPatchPathPrefix);
+
+ // Now add the patch archive to the list of patches to the original MPQ
+ while(ha != NULL)
+ {
+ if(ha->haPatch == NULL)
+ {
+ haPatch->haBase = ha;
+ ha->haPatch = haPatch;
+ return true;
+ }
+
+ // Move to the next archive
+ ha = ha->haPatch;
+ }
+
+ // Should never happen
+ nError = ERROR_CAN_NOT_COMPLETE;
+ }
+
+ SetLastError(nError);
+ return false;
+}
+
+bool WINAPI SFileIsPatchedArchive(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(hMpq))
+ return false;
+
+ return (ha->haPatch != NULL);
+}
diff --git a/src/SFileReadFile.cpp b/src/SFileReadFile.cpp index 73e6879..30dc587 100644 --- a/src/SFileReadFile.cpp +++ b/src/SFileReadFile.cpp @@ -680,14 +680,14 @@ bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD return false; } - if(pvBuffer == NULL || hf->pFileEntry == NULL) + if(pvBuffer == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return false; } // If we didn't load the patch info yet, do it now - if((hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) && hf->pPatchInfo == NULL) + if(hf->pFileEntry != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) && hf->pPatchInfo == NULL) { nError = AllocatePatchInfo(hf, true); if(nError != ERROR_SUCCESS || hf->pPatchInfo == NULL) diff --git a/src/SFileVerify.cpp b/src/SFileVerify.cpp index 45ff58e..e68d51a 100644 --- a/src/SFileVerify.cpp +++ b/src/SFileVerify.cpp @@ -1,1054 +1,1054 @@ -/*****************************************************************************/ -/* SFileVerify.cpp Copyright (c) Ladislav Zezula 2010 */ -/*---------------------------------------------------------------------------*/ -/* MPQ files and MPQ archives verification. */ -/* */ -/* The MPQ signature verification has been written by Jean-Francois Roy */ -/* <bahamut@macstorm.org> and Justin Olbrantz (Quantam). */ -/* The MPQ public keys have been created by MPQKit, using OpenSSL library. */ -/* */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 04.05.10 1.00 Lad The first version of SFileVerify.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -//----------------------------------------------------------------------------- -// Local defines - -#define MPQ_DIGEST_UNIT_SIZE 0x10000 - -//----------------------------------------------------------------------------- -// Known Blizzard public keys -// Created by Jean-Francois Roy using OpenSSL - -static const char * szBlizzardWeakPrivateKey = - "-----BEGIN PRIVATE KEY-----" - "MIIBOQIBAAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe2kfdfEk3G/j66w4KkhZ1" - "V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQJANtiztVDMJh2hE1hjPDKy" - "UmEJ9U/aN3gomuKOjbQbQ/bWWcM/WfhSVHmPqtqh/bQI2UXFr0rnXngeteZHLr/b" - "8QIhAMuWriSKGMACw18/rVVfUrThs915odKBH1Alr3vMVVzZAiEAuBHPSQkgwcb6" - "L4MWaiKuOzq08mSyNqPeN8oSy18q848CIHeMn+3s+eOmu7su1UYQl6yH7OrdBd1q" - "3UxfFNEJiAbhAiAqxdCyOxHGlbM7aS3DOg3cq5ayoN2cvtV7h1R4t8OmVwIgF+5z" - "/6vkzBUsZhd8Nwyis+MeQYH0rpFpMKdTlqmPF2Q=" - "-----END PRIVATE KEY-----"; - -static const char * szBlizzardWeakPublicKey = - "-----BEGIN PUBLIC KEY-----" - "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe" - "2kfdfEk3G/j66w4KkhZ1V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQ==" - "-----END PUBLIC KEY-----"; - -static const char * szBlizzardStrongPublicKey = - "-----BEGIN PUBLIC KEY-----" - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQZ+ziT2h8h+J/iMQpgd" - "tH1HaJzOBE3agjU4yMPcrixaPOZoA4t8bwfey7qczfWywocYo3pleytFF+IuD4HD" - "Fl9OXN1SFyupSgMx1EGZlgbFAomnbq9MQJyMqQtMhRAjFgg4TndS7YNb+JMSAEKp" - "kXNqY28n/EVBHD5TsMuVCL579gIenbr61dI92DDEdy790IzIG0VKWLh/KOTcTJfm" - "Ds/7HQTkGouVW+WUsfekuqNQo7ND9DBnhLjLjptxeFE2AZqYcA1ao3S9LN3GL1tW" - "lVXFIX9c7fWqaVTQlZ2oNsI/ARVApOK3grNgqvwH6YoVYVXjNJEo5sQJsPsdV/hk" - "dwIDAQAB" - "-----END PUBLIC KEY-----"; - -static const char * szWarcraft3MapPublicKey = - "-----BEGIN PUBLIC KEY-----" - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1BwklUUQ3UvjizOBRoF5" - "yyOVc7KD+oGOQH5i6eUk1yfs0luCC70kNucNrfqhmviywVtahRse1JtXCPrx2bd3" - "iN8Dx91fbkxjYIOGTsjYoHKTp0BbaFkJih776fcHgnFSb+7mJcDuJVvJOXxEH6w0" - "1vo6VtujCqj1arqbyoal+xtAaczF3us5cOEp45sR1zAWTn1+7omN7VWV4QqJPaDS" - "gBSESc0l1grO0i1VUSumayk7yBKIkb+LBvcG6WnYZHCi7VdLmaxER5m8oZfER66b" - "heHoiSQIZf9PAY6Guw2DT5BTc54j/AaLQAKf2qcRSgQLVo5kQaddF3rCpsXoB/74" - "6QIDAQAB" - "-----END PUBLIC KEY-----"; - -static const char * szWowPatchPublicKey = - "-----BEGIN PUBLIC KEY-----" - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOsMV0LagAWPEtEQM6b9" - "6FHFkUyGbbyda2/Dfc9dyl21E9QvX+Yw7qKRMAKPzA2TlQQLZKvXpnKXF/YIK5xa" - "5uwg9CEHCEAYolLG4xn0FUOE0E/0PuuytI0p0ICe6rk00PifZzTr8na2wI/l/GnQ" - "bvnIVF1ck6cslATpQJ5JJVMXzoFlUABS19WESw4MXuJAS3AbMhxNWdEhVv7eO51c" - "yGjRLy9QjogZODZTY0fSEksgBqQxNCoYVJYI/sF5K2flDsGqrIp0OdJ6teJlzg1Y" - "UjYnb6bKjlidXoHEXI2TgA/mD6O3XFIt08I9s3crOCTgICq7cgX35qrZiIVWZdRv" - "TwIDAQAB" - "-----END PUBLIC KEY-----"; - -static const char * szWowSurveyPublicKey = - "-----BEGIN PUBLIC KEY-----" - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIt1DR6nRyyKsy2qahHe" - "MKLtacatn/KxieHcwH87wLBxKy+jZ0gycTmJ7SaTdBAEMDs/V5IPIXEtoqYnid2c" - "63TmfGDU92oc3Ph1PWUZ2PWxBhT06HYxRdbrgHw9/I29pNPi/607x+lzPORITOgU" - "BR6MR8au8HsQP4bn4vkJNgnSgojh48/XQOB/cAln7As1neP61NmVimoLR4Bwi3zt" - "zfgrZaUpyeNCUrOYJmH09YIjbBySTtXOUidoPHjFrMsCWpr6xs8xbETbs7MJFL6a" - "vcUfTT67qfIZ9RsuKfnXJTIrV0kwDSjjuNXiPTmWAehSsiHIsrUXX5RNcwsSjClr" - "nQIDAQAB" - "-----END PUBLIC KEY-----"; - -static const char * szStarcraft2MapPublicKey = - "-----BEGIN PUBLIC KEY-----" - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmk4GT8zb+ICC25a17KZB" - "q/ygKGJ2VSO6IT5PGHJlm1KfnHBA4B6SH3xMlJ4c6eG2k7QevZv+FOhjsAHubyWq" - "2VKqWbrIFKv2ILc2RfMn8J9EDVRxvcxh6slRrVL69D0w1tfVGjMiKq2Fym5yGoRT" - "E7CRgDqbAbXP9LBsCNWHiJLwfxMGzHbk8pIl9oia5pvM7ofZamSHchxlpy6xa4GJ" - "7xKN01YCNvklTL1D7uol3wkwcHc7vrF8QwuJizuA5bSg4poEGtH62BZOYi+UL/z0" - "31YK+k9CbQyM0X0pJoJoYz1TK+Y5J7vBnXCZtfcTYQ/ZzN6UcxTa57dJaiOlCh9z" - "nQIDAQAB" - "-----END PUBLIC KEY-----"; - -//----------------------------------------------------------------------------- -// Local functions - -static void memrev(unsigned char *buf, size_t count) -{ - unsigned char *r; - - for (r = buf + count - 1; buf < r; buf++, r--) - { - *buf ^= *r; - *r ^= *buf; - *buf ^= *r; - } -} - -static bool decode_base64_key(const char * szKeyBase64, rsa_key * key) -{ - unsigned char decoded_key[0x200]; - const char * szBase64Begin; - const char * szBase64End; - unsigned long decoded_length = sizeof(decoded_key); - unsigned long length; - - // Find out the begin of the BASE64 data - szBase64Begin = szKeyBase64 + strlen("-----BEGIN PUBLIC KEY-----"); - szBase64End = szBase64Begin + strlen(szBase64Begin) - strlen("-----END PUBLIC KEY-----"); - if(szBase64End[0] != '-') - return false; - - // decode the base64 string - length = (unsigned long)(szBase64End - szBase64Begin); - if(base64_decode((unsigned char *)szBase64Begin, length, decoded_key, &decoded_length) != CRYPT_OK) - return false; - - // Create RSA key - if(rsa_import(decoded_key, decoded_length, key) != CRYPT_OK) - return false; - - return true; -} - -static void GetPlainAnsiFileName( - const TCHAR * szFileName, - char * szPlainName) -{ - const TCHAR * szPlainNameT = GetPlainFileName(szFileName); - - // Convert the plain name to ANSI - while(*szPlainNameT != 0) - *szPlainName++ = (char)*szPlainNameT++; - *szPlainName = 0; -} - -// Calculate begin and end of the MPQ archive -static void CalculateArchiveRange( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI) -{ - ULONGLONG TempPos = 0; - char szMapHeader[0x200]; - - // Get the MPQ begin - pSI->BeginMpqData = ha->MpqPos; - - // Warcraft III maps are signed from the map header to the end - if(FileStream_Read(ha->pStream, &TempPos, szMapHeader, sizeof(szMapHeader))) - { - // Is it a map header ? - if(szMapHeader[0] == 'H' && szMapHeader[1] == 'M' && szMapHeader[2] == '3' && szMapHeader[3] == 'W') - { - // We will have to hash since the map header - pSI->BeginMpqData = 0; - } - } - - // Get the MPQ data end. This is stored in the MPQ header - pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64; - - // Get the size of the entire file - FileStream_GetSize(ha->pStream, &pSI->EndOfFile); -} - -static bool CalculateMpqHashMd5( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI, - LPBYTE pMd5Digest) -{ - hash_state md5_state; - ULONGLONG BeginBuffer; - ULONGLONG EndBuffer; - LPBYTE pbDigestBuffer = NULL; - - // Allocate buffer for creating the MPQ digest. - pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE); - if(pbDigestBuffer == NULL) - return false; - - // Initialize the MD5 hash state - md5_init(&md5_state); - - // Set the byte offset of begin of the data - BeginBuffer = pSI->BeginMpqData; - - // Create the digest - for(;;) - { - ULONGLONG BytesRemaining; - LPBYTE pbSigBegin = NULL; - LPBYTE pbSigEnd = NULL; - DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE; - - // Check the number of bytes remaining - BytesRemaining = pSI->EndMpqData - BeginBuffer; - if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE) - dwToRead = (DWORD)BytesRemaining; - if(dwToRead == 0) - break; - - // Read the next chunk - if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead)) - { - STORM_FREE(pbDigestBuffer); - return false; - } - - // Move the current byte offset - EndBuffer = BeginBuffer + dwToRead; - - // Check if the signature is within the loaded digest - if(BeginBuffer <= pSI->BeginExclude && pSI->BeginExclude < EndBuffer) - pbSigBegin = pbDigestBuffer + (size_t)(pSI->BeginExclude - BeginBuffer); - if(BeginBuffer <= pSI->EndExclude && pSI->EndExclude < EndBuffer) - pbSigEnd = pbDigestBuffer + (size_t)(pSI->EndExclude - BeginBuffer); - - // Zero the part that belongs to the signature - if(pbSigBegin != NULL || pbSigEnd != NULL) - { - if(pbSigBegin == NULL) - pbSigBegin = pbDigestBuffer; - if(pbSigEnd == NULL) - pbSigEnd = pbDigestBuffer + dwToRead; - - memset(pbSigBegin, 0, (pbSigEnd - pbSigBegin)); - } - - // Pass the buffer to the hashing function - md5_process(&md5_state, pbDigestBuffer, dwToRead); - - // Move pointers - BeginBuffer += dwToRead; - } - - // Finalize the MD5 hash - md5_done(&md5_state, pMd5Digest); - STORM_FREE(pbDigestBuffer); - return true; -} - -static void AddTailToSha1( - hash_state * psha1_state, - const char * szTail) -{ - unsigned char * pbTail = (unsigned char *)szTail; - unsigned char szUpperCase[0x200]; - unsigned long nLength = 0; - - // Convert the tail to uppercase - // Note that we don't need to terminate the string with zero - while(*pbTail != 0) - { - szUpperCase[nLength++] = AsciiToUpperTable[*pbTail++]; - } - - // Append the tail to the SHA1 - sha1_process(psha1_state, szUpperCase, nLength); -} - -static bool CalculateMpqHashSha1( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI, - unsigned char * sha1_tail0, - unsigned char * sha1_tail1, - unsigned char * sha1_tail2) -{ - ULONGLONG BeginBuffer; - hash_state sha1_state_temp; - hash_state sha1_state; - LPBYTE pbDigestBuffer = NULL; - char szPlainName[MAX_PATH]; - - // Allocate buffer for creating the MPQ digest. - pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE); - if(pbDigestBuffer == NULL) - return false; - - // Initialize SHA1 state structure - sha1_init(&sha1_state); - - // Calculate begin of data to be hashed - BeginBuffer = pSI->BeginMpqData; - - // Create the digest - for(;;) - { - ULONGLONG BytesRemaining; - DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE; - - // Check the number of bytes remaining - BytesRemaining = pSI->EndMpqData - BeginBuffer; - if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE) - dwToRead = (DWORD)BytesRemaining; - if(dwToRead == 0) - break; - - // Read the next chunk - if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead)) - { - STORM_FREE(pbDigestBuffer); - return false; - } - - // Pass the buffer to the hashing function - sha1_process(&sha1_state, pbDigestBuffer, dwToRead); - - // Move pointers - BeginBuffer += dwToRead; - } - - // Add all three known tails and generate three hashes - memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); - sha1_done(&sha1_state_temp, sha1_tail0); - - memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); - GetPlainAnsiFileName(FileStream_GetFileName(ha->pStream), szPlainName); - AddTailToSha1(&sha1_state_temp, szPlainName); - sha1_done(&sha1_state_temp, sha1_tail1); - - memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state)); - AddTailToSha1(&sha1_state_temp, "ARCHIVE"); - sha1_done(&sha1_state_temp, sha1_tail2); - - // Finalize the MD5 hash - STORM_FREE(pbDigestBuffer); - return true; -} - -static int VerifyRawMpqData( - TMPQArchive * ha, - ULONGLONG ByteOffset, - DWORD dwDataSize) -{ - ULONGLONG DataOffset = ha->MpqPos + ByteOffset; - LPBYTE pbDataChunk; - LPBYTE pbMD5Array1; // Calculated MD5 array - LPBYTE pbMD5Array2; // MD5 array loaded from the MPQ - DWORD dwBytesInChunk; - DWORD dwChunkCount; - DWORD dwChunkSize = ha->pHeader->dwRawChunkSize; - 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 - 1) / dwChunkSize) + 1; - dwMD5Size = dwChunkCount * MD5_DIGEST_SIZE; - - // Allocate space for data chunk and for the MD5 array - pbDataChunk = STORM_ALLOC(BYTE, dwChunkSize); - if(pbDataChunk == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Allocate space for MD5 array - pbMD5Array1 = STORM_ALLOC(BYTE, dwMD5Size); - pbMD5Array2 = STORM_ALLOC(BYTE, dwMD5Size); - if(pbMD5Array1 == NULL || pbMD5Array2 == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - - // Calculate MD5 of each data chunk - if(nError == ERROR_SUCCESS) - { - LPBYTE pbMD5 = pbMD5Array1; - - for(DWORD i = 0; i < dwChunkCount; i++) - { - // Get the number of bytes in the chunk - dwBytesInChunk = STORMLIB_MIN(dwChunkSize, dwDataSize); - - // Read the data chunk - if(!FileStream_Read(ha->pStream, &DataOffset, pbDataChunk, dwBytesInChunk)) - { - nError = ERROR_FILE_CORRUPT; - break; - } - - // Calculate MD5 - CalculateDataBlockHash(pbDataChunk, dwBytesInChunk, pbMD5); - - // Move pointers and offsets - DataOffset += dwBytesInChunk; - dwDataSize -= dwBytesInChunk; - pbMD5 += MD5_DIGEST_SIZE; - } - } - - // Read the MD5 array - if(nError == ERROR_SUCCESS) - { - // Read the array of MD5 - if(!FileStream_Read(ha->pStream, &DataOffset, pbMD5Array2, dwMD5Size)) - nError = GetLastError(); - } - - // Compare the array of MD5 - if(nError == ERROR_SUCCESS) - { - // Compare the MD5 - if(memcmp(pbMD5Array1, pbMD5Array2, dwMD5Size)) - nError = ERROR_FILE_CORRUPT; - } - - // Free memory and return result - if(pbMD5Array2 != NULL) - STORM_FREE(pbMD5Array2); - if(pbMD5Array1 != NULL) - STORM_FREE(pbMD5Array1); - if(pbDataChunk != NULL) - STORM_FREE(pbDataChunk); - return nError; -} - -static DWORD VerifyWeakSignature( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI) -{ - BYTE RevSignature[MPQ_WEAK_SIGNATURE_SIZE]; - BYTE Md5Digest[MD5_DIGEST_SIZE]; - rsa_key key; - int hash_idx = find_hash("md5"); - int result = 0; - - // The signature might be zeroed out. In that case, we ignore it - if(!IsValidSignature(pSI->Signature)) - return ERROR_WEAK_SIGNATURE_OK; - - // Calculate hash of the entire archive, skipping the (signature) file - if(!CalculateMpqHashMd5(ha, pSI, Md5Digest)) - return ERROR_VERIFY_FAILED; - - // Import the Blizzard key in OpenSSL format - if(!decode_base64_key(szBlizzardWeakPublicKey, &key)) - return ERROR_VERIFY_FAILED; - - // Verify the signature - memcpy(RevSignature, &pSI->Signature[8], MPQ_WEAK_SIGNATURE_SIZE); - memrev(RevSignature, MPQ_WEAK_SIGNATURE_SIZE); - rsa_verify_hash_ex(RevSignature, MPQ_WEAK_SIGNATURE_SIZE, Md5Digest, sizeof(Md5Digest), LTC_LTC_PKCS_1_V1_5, hash_idx, 0, &result, &key); - rsa_free(&key); - - // Return the result - return result ? ERROR_WEAK_SIGNATURE_OK : ERROR_WEAK_SIGNATURE_ERROR; -} - -static DWORD VerifyStrongSignatureWithKey( - unsigned char * reversed_signature, - unsigned char * padded_digest, - const char * szPublicKey) -{ - rsa_key key; - int result = 0; - - // Import the Blizzard key in OpenSSL format - if(!decode_base64_key(szPublicKey, &key)) - { - assert(false); - return ERROR_VERIFY_FAILED; - } - - // Verify the signature - if(rsa_verify_simple(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE, padded_digest, MPQ_STRONG_SIGNATURE_SIZE, &result, &key) != CRYPT_OK) - return ERROR_VERIFY_FAILED; - - // Free the key and return result - rsa_free(&key); - return result ? ERROR_STRONG_SIGNATURE_OK : ERROR_STRONG_SIGNATURE_ERROR; -} - -static DWORD VerifyStrongSignature( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI) -{ - unsigned char reversed_signature[MPQ_STRONG_SIGNATURE_SIZE]; - unsigned char Sha1Digest_tail0[SHA1_DIGEST_SIZE]; - unsigned char Sha1Digest_tail1[SHA1_DIGEST_SIZE]; - unsigned char Sha1Digest_tail2[SHA1_DIGEST_SIZE]; - unsigned char padded_digest[MPQ_STRONG_SIGNATURE_SIZE]; - DWORD dwResult; - size_t digest_offset; - - // Calculate SHA1 hash of the archive - if(!CalculateMpqHashSha1(ha, pSI, Sha1Digest_tail0, Sha1Digest_tail1, Sha1Digest_tail2)) - return ERROR_VERIFY_FAILED; - - // Prepare the signature for decryption - memcpy(reversed_signature, &pSI->Signature[4], MPQ_STRONG_SIGNATURE_SIZE); - memrev(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE); - - // Prepare the padded digest for comparison - digest_offset = sizeof(padded_digest) - SHA1_DIGEST_SIZE; - memset(padded_digest, 0xbb, digest_offset); - padded_digest[0] = 0x0b; - - // Try Blizzard Strong public key with no SHA1 tail - memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE); - memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE); - dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szBlizzardStrongPublicKey); - if(dwResult == ERROR_STRONG_SIGNATURE_OK) - return dwResult; - - // Try War 3 map public key with plain file name as SHA1 tail - memcpy(padded_digest + digest_offset, Sha1Digest_tail1, SHA1_DIGEST_SIZE); - memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE); - dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWarcraft3MapPublicKey); - if(dwResult == ERROR_STRONG_SIGNATURE_OK) - return dwResult; - - // Try WoW-TBC public key with "ARCHIVE" as SHA1 tail - memcpy(padded_digest + digest_offset, Sha1Digest_tail2, SHA1_DIGEST_SIZE); - memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE); - dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowPatchPublicKey); - if(dwResult == ERROR_STRONG_SIGNATURE_OK) - return dwResult; - - // Try Survey public key with no SHA1 tail - memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE); - memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE); - dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowSurveyPublicKey); - if(dwResult == ERROR_STRONG_SIGNATURE_OK) - return dwResult; - - // Try Starcraft II public key with no SHA1 tail - memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE); - memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE); - dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szStarcraft2MapPublicKey); - if(dwResult == ERROR_STRONG_SIGNATURE_OK) - return dwResult; - - return ERROR_STRONG_SIGNATURE_ERROR; -} - -static DWORD VerifyFile( - HANDLE hMpq, - const char * szFileName, - LPDWORD pdwCrc32, - char * pMD5, - DWORD dwFlags) -{ - hash_state md5_state; - unsigned char * pFileMd5; - unsigned char md5[MD5_DIGEST_SIZE]; - TFileEntry * pFileEntry; - TMPQFile * hf; - BYTE Buffer[0x1000]; - HANDLE hFile = NULL; - DWORD dwVerifyResult = 0; - DWORD dwTotalBytes = 0; - DWORD dwCrc32 = 0; - - // - // Note: When the MPQ is patched, it will - // automatically check the patched version of the file - // - - // Make sure the md5 is initialized - memset(md5, 0, sizeof(md5)); - - // 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, SFILE_OPEN_FROM_MPQ, &hFile)) - { - // Get the file size - hf = (TMPQFile *)hFile; - pFileEntry = hf->pFileEntry; - dwTotalBytes = SFileGetFileSize(hFile, NULL); - - // Initialize the CRC32 and MD5 contexts - md5_init(&md5_state); - dwCrc32 = crc32(0, Z_NULL, 0); - - // Also turn on sector checksum verification - if(dwFlags & SFILE_VERIFY_SECTOR_CRC) - hf->bCheckSectorCRCs = true; - - // Go through entire file and update both CRC32 and MD5 - for(;;) - { - DWORD dwBytesRead = 0; - - // Read data from file - SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL); - if(dwBytesRead == 0) - { - if(GetLastError() == ERROR_CHECKSUM_ERROR) - dwVerifyResult |= VERIFY_FILE_SECTOR_CRC_ERROR; - break; - } - - // Update CRC32 value - if(dwFlags & SFILE_VERIFY_FILE_CRC) - dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead); - - // Update MD5 value - if(dwFlags & SFILE_VERIFY_FILE_MD5) - md5_process(&md5_state, Buffer, dwBytesRead); - - // Decrement the total size - dwTotalBytes -= dwBytesRead; - } - - // If the file has sector checksums, indicate it in the flags - if(dwFlags & SFILE_VERIFY_SECTOR_CRC) - { - if((hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->SectorChksums != NULL && hf->SectorChksums[0] != 0) - dwVerifyResult |= VERIFY_FILE_HAS_SECTOR_CRC; - } - - // Check if the entire file has been read - // No point in checking CRC32 and MD5 if not - // Skip checksum checks if the file has patches - if(dwTotalBytes == 0) - { - // Check CRC32 and MD5 only if there is no patches - if(hf->hfPatch == NULL) - { - // Check if the CRC32 matches. - if(dwFlags & SFILE_VERIFY_FILE_CRC) - { - // Only check the CRC32 if it is valid - if(pFileEntry->dwCrc32 != 0) - { - dwVerifyResult |= VERIFY_FILE_HAS_CHECKSUM; - if(dwCrc32 != pFileEntry->dwCrc32) - dwVerifyResult |= VERIFY_FILE_CHECKSUM_ERROR; - } - } - - // Check if MD5 matches - if(dwFlags & SFILE_VERIFY_FILE_MD5) - { - // Patch files have their MD5 saved in the patch info - pFileMd5 = (hf->pPatchInfo != NULL) ? hf->pPatchInfo->md5 : pFileEntry->md5; - md5_done(&md5_state, md5); - - // Only check the MD5 if it is valid - if(IsValidMD5(pFileMd5)) - { - dwVerifyResult |= VERIFY_FILE_HAS_MD5; - if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE)) - dwVerifyResult |= VERIFY_FILE_MD5_ERROR; - } - } - } - else - { - // Patched files are MD5-checked automatically - dwVerifyResult |= VERIFY_FILE_HAS_MD5; - } - } - else - { - dwVerifyResult |= VERIFY_READ_ERROR; - } - - SFileCloseFile(hFile); - } - else - { - // Remember that the file couldn't be open - dwVerifyResult |= VERIFY_OPEN_ERROR; - } - - // If the caller required CRC32 and/or MD5, give it to him - if(pdwCrc32 != NULL) - *pdwCrc32 = dwCrc32; - if(pMD5 != NULL) - memcpy(pMD5, md5, MD5_DIGEST_SIZE); - - return dwVerifyResult; -} - -// Used in SFileGetFileInfo -bool QueryMpqSignatureInfo( - TMPQArchive * ha, - PMPQ_SIGNATURE_INFO pSI) -{ - TFileEntry * pFileEntry; - ULONGLONG ExtraBytes; - DWORD dwFileSize; - - // Make sure it's all zeroed - memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO)); - - // Calculate the range of the MPQ - CalculateArchiveRange(ha, pSI); - - // If there is "(signature)" file in the MPQ, it has a weak signature - pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL); - if(pFileEntry != NULL) - { - // Calculate the begin and end of the signature file itself - pSI->BeginExclude = ha->MpqPos + pFileEntry->ByteOffset; - pSI->EndExclude = pSI->BeginExclude + pFileEntry->dwCmpSize; - dwFileSize = (DWORD)(pSI->EndExclude - pSI->BeginExclude); - - // Does the signature have proper size? - if(dwFileSize == MPQ_SIGNATURE_FILE_SIZE) - { - // Read the weak signature - if(!FileStream_Read(ha->pStream, &pSI->BeginExclude, pSI->Signature, dwFileSize)) - return false; - - pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK; - pSI->cbSignatureSize = dwFileSize; - return true; - } - } - - // If there is extra bytes beyond the end of the archive, - // it's the strong signature - ExtraBytes = pSI->EndOfFile - pSI->EndMpqData; - if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4)) - { - // Read the strong signature - if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4))) - return false; - - // Check the signature header "NGIS" - if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S') - return false; - - pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG; - return true; - } - - // Succeeded, but no known signature found - return true; -} - -//----------------------------------------------------------------------------- -// Support for weak signature - -int SSignFileCreate(TMPQArchive * ha) -{ - TMPQFile * hf = NULL; - BYTE EmptySignature[MPQ_SIGNATURE_FILE_SIZE]; - int nError = ERROR_SUCCESS; - - // Only save the signature if we should do so - if(ha->dwFileFlags3 != 0) - { - // The (signature) file must be non-encrypted and non-compressed - assert(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW); - assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS); - assert(ha->dwReservedFiles > 0); - - // Create the (signature) file file in the MPQ - // Note that the file must not be compressed or encrypted - nError = SFileAddFile_Init(ha, SIGNATURE_NAME, - 0, - sizeof(EmptySignature), - LANG_NEUTRAL, - ha->dwFileFlags3 | MPQ_FILE_REPLACEEXISTING, - &hf); - - // Write the empty signature file to the archive - if(nError == ERROR_SUCCESS) - { - // Write the empty zeroed file to the MPQ - memset(EmptySignature, 0, sizeof(EmptySignature)); - nError = SFileAddFile_Write(hf, EmptySignature, (DWORD)sizeof(EmptySignature), 0); - SFileAddFile_Finish(hf); - - // Clear the invalid mark - ha->dwFlags &= ~(MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_SIGNATURE_NONE); - ha->dwReservedFiles--; - } - } - - return nError; -} - -int SSignFileFinish(TMPQArchive * ha) -{ - MPQ_SIGNATURE_INFO si; - unsigned long signature_len = MPQ_WEAK_SIGNATURE_SIZE; - BYTE WeakSignature[MPQ_SIGNATURE_FILE_SIZE]; - BYTE Md5Digest[MD5_DIGEST_SIZE]; - rsa_key key; - int hash_idx = find_hash("md5"); - - // Sanity checks - assert((ha->dwFlags & MPQ_FLAG_CHANGED) == 0); - assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS); - - // Query the weak signature info - memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO)); - if(!QueryMpqSignatureInfo(ha, &si)) - return ERROR_FILE_CORRUPT; - - // There must be exactly one signature - if(si.SignatureTypes != SIGNATURE_TYPE_WEAK) - return ERROR_FILE_CORRUPT; - - // Calculate MD5 of the entire archive - if(!CalculateMpqHashMd5(ha, &si, Md5Digest)) - return ERROR_VERIFY_FAILED; - - // Decode the private key - if(!decode_base64_key(szBlizzardWeakPrivateKey, &key)) - return ERROR_VERIFY_FAILED; - - // Sign the hash - memset(WeakSignature, 0, sizeof(WeakSignature)); - rsa_sign_hash_ex(Md5Digest, sizeof(Md5Digest), WeakSignature + 8, &signature_len, LTC_LTC_PKCS_1_V1_5, 0, 0, hash_idx, 0, &key); - memrev(WeakSignature + 8, MPQ_WEAK_SIGNATURE_SIZE); - rsa_free(&key); - - // Write the signature to the MPQ. Don't use SFile* functions, but write the hash directly - if(!FileStream_Write(ha->pStream, &si.BeginExclude, WeakSignature, MPQ_SIGNATURE_FILE_SIZE)) - return GetLastError(); - - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// Public (exported) functions - -bool WINAPI SFileGetFileChecksums(HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5) -{ - DWORD dwVerifyResult; - DWORD dwVerifyFlags = 0; - - if(pdwCrc32 != NULL) - dwVerifyFlags |= SFILE_VERIFY_FILE_CRC; - if(pMD5 != NULL) - dwVerifyFlags |= SFILE_VERIFY_FILE_MD5; - - dwVerifyResult = VerifyFile(hMpq, - szFileName, - pdwCrc32, - pMD5, - dwVerifyFlags); - - // If verification failed, return zero - if(dwVerifyResult & VERIFY_FILE_ERROR_MASK) - { - SetLastError(ERROR_FILE_CORRUPT); - return false; - } - - return true; -} - - -DWORD WINAPI SFileVerifyFile(HANDLE hMpq, const char * szFileName, DWORD dwFlags) -{ - return VerifyFile(hMpq, - szFileName, - NULL, - NULL, - dwFlags); -} - -// Verifies raw data of the archive Only works for MPQs version 4 or newer -int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * szFileName) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - TFileEntry * pFileEntry; - TMPQHeader * pHeader; - - // Verify input parameters - if(!IsValidMpqHandle(hMpq)) - return ERROR_INVALID_PARAMETER; - pHeader = ha->pHeader; - - // If the archive doesn't have raw data MD5, report it as OK - if(pHeader->dwRawChunkSize == 0) - return ERROR_SUCCESS; - - // If we have to verify MPQ header, do it - switch(dwWhatToVerify) - { - case SFILE_VERIFY_MPQ_HEADER: - - // Only if the header is of version 4 or newer - if(pHeader->dwHeaderSize >= (MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE)) - return VerifyRawMpqData(ha, 0, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE); - return ERROR_SUCCESS; - - case SFILE_VERIFY_HET_TABLE: - - // Only if we have HET table - if(pHeader->HetTablePos64 && pHeader->HetTableSize64) - return VerifyRawMpqData(ha, pHeader->HetTablePos64, (DWORD)pHeader->HetTableSize64); - return ERROR_SUCCESS; - - case SFILE_VERIFY_BET_TABLE: - - // Only if we have BET table - if(pHeader->BetTablePos64 && pHeader->BetTableSize64) - return VerifyRawMpqData(ha, pHeader->BetTablePos64, (DWORD)pHeader->BetTableSize64); - return ERROR_SUCCESS; - - case SFILE_VERIFY_HASH_TABLE: - - // Hash table is not protected by MD5 - return ERROR_SUCCESS; - - case SFILE_VERIFY_BLOCK_TABLE: - - // Block table is not protected by MD5 - return ERROR_SUCCESS; - - case SFILE_VERIFY_HIBLOCK_TABLE: - - // It is unknown if the hi-block table is protected my MD5 or not. - return ERROR_SUCCESS; - - case SFILE_VERIFY_FILE: - - // Verify parameters - if(szFileName == NULL || *szFileName == 0) - return ERROR_INVALID_PARAMETER; - - // Get the offset of a file - pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); - if(pFileEntry == NULL) - return ERROR_FILE_NOT_FOUND; - - return VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize); - } - - return ERROR_INVALID_PARAMETER; -} - - -// Verifies the archive against the signature -DWORD WINAPI SFileVerifyArchive(HANDLE hMpq) -{ - MPQ_SIGNATURE_INFO si; - TMPQArchive * ha = (TMPQArchive *)hMpq; - - // Verify input parameters - if(!IsValidMpqHandle(hMpq)) - return ERROR_VERIFY_FAILED; - - // If the archive was modified, we need to flush it - if(ha->dwFlags & MPQ_FLAG_CHANGED) - SFileFlushArchive(hMpq); - - // Get the MPQ signature and signature type - memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO)); - if(!QueryMpqSignatureInfo(ha, &si)) - return ERROR_VERIFY_FAILED; - - // If there is no signature - if(si.SignatureTypes == 0) - return ERROR_NO_SIGNATURE; - - // We haven't seen a MPQ with both signatures - assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG); - - // Verify the strong signature, if present - if(si.SignatureTypes & SIGNATURE_TYPE_STRONG) - return VerifyStrongSignature(ha, &si); - - // Verify the weak signature, if present - if(si.SignatureTypes & SIGNATURE_TYPE_WEAK) - return VerifyWeakSignature(ha, &si); - - return ERROR_NO_SIGNATURE; -} - -// Verifies the archive against the signature -bool WINAPI SFileSignArchive(HANDLE hMpq, DWORD dwSignatureType) -{ - TMPQArchive * ha; - - // Verify the archive handle - ha = IsValidMpqHandle(hMpq); - if(ha == NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // We only support weak signature, and only for MPQs version 1.0 - if(dwSignatureType != SIGNATURE_TYPE_WEAK) - { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - // The archive must not be malformed and must not be read-only - if(ha->dwFlags & (MPQ_FLAG_READ_ONLY | MPQ_FLAG_MALFORMED)) - { - SetLastError(ERROR_ACCESS_DENIED); - return false; - } - - // If the signature is not there yet - if(ha->dwFileFlags3 == 0) - { - // Turn the signature on. The signature will - // be applied when the archive is closed - ha->dwFlags |= MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_CHANGED; - ha->dwFileFlags3 = MPQ_FILE_EXISTS; - ha->dwReservedFiles++; - } - - return true; -} - +/*****************************************************************************/
+/* SFileVerify.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* MPQ files and MPQ archives verification. */
+/* */
+/* The MPQ signature verification has been written by Jean-Francois Roy */
+/* <bahamut@macstorm.org> and Justin Olbrantz (Quantam). */
+/* The MPQ public keys have been created by MPQKit, using OpenSSL library. */
+/* */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 04.05.10 1.00 Lad The first version of SFileVerify.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local defines
+
+#define MPQ_DIGEST_UNIT_SIZE 0x10000
+
+//-----------------------------------------------------------------------------
+// Known Blizzard public keys
+// Created by Jean-Francois Roy using OpenSSL
+
+static const char * szBlizzardWeakPrivateKey =
+ "-----BEGIN PRIVATE KEY-----"
+ "MIIBOQIBAAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe2kfdfEk3G/j66w4KkhZ1"
+ "V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQJANtiztVDMJh2hE1hjPDKy"
+ "UmEJ9U/aN3gomuKOjbQbQ/bWWcM/WfhSVHmPqtqh/bQI2UXFr0rnXngeteZHLr/b"
+ "8QIhAMuWriSKGMACw18/rVVfUrThs915odKBH1Alr3vMVVzZAiEAuBHPSQkgwcb6"
+ "L4MWaiKuOzq08mSyNqPeN8oSy18q848CIHeMn+3s+eOmu7su1UYQl6yH7OrdBd1q"
+ "3UxfFNEJiAbhAiAqxdCyOxHGlbM7aS3DOg3cq5ayoN2cvtV7h1R4t8OmVwIgF+5z"
+ "/6vkzBUsZhd8Nwyis+MeQYH0rpFpMKdTlqmPF2Q="
+ "-----END PRIVATE KEY-----";
+
+static const char * szBlizzardWeakPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJJidwS/uILMBSO5DLGsBFknIXWWjQJe"
+ "2kfdfEk3G/j66w4KkhZ1V61Rt4zLaMVCYpDun7FLwRjkMDSepO1q2DcCAwEAAQ=="
+ "-----END PUBLIC KEY-----";
+
+static const char * szBlizzardStrongPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQZ+ziT2h8h+J/iMQpgd"
+ "tH1HaJzOBE3agjU4yMPcrixaPOZoA4t8bwfey7qczfWywocYo3pleytFF+IuD4HD"
+ "Fl9OXN1SFyupSgMx1EGZlgbFAomnbq9MQJyMqQtMhRAjFgg4TndS7YNb+JMSAEKp"
+ "kXNqY28n/EVBHD5TsMuVCL579gIenbr61dI92DDEdy790IzIG0VKWLh/KOTcTJfm"
+ "Ds/7HQTkGouVW+WUsfekuqNQo7ND9DBnhLjLjptxeFE2AZqYcA1ao3S9LN3GL1tW"
+ "lVXFIX9c7fWqaVTQlZ2oNsI/ARVApOK3grNgqvwH6YoVYVXjNJEo5sQJsPsdV/hk"
+ "dwIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szWarcraft3MapPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1BwklUUQ3UvjizOBRoF5"
+ "yyOVc7KD+oGOQH5i6eUk1yfs0luCC70kNucNrfqhmviywVtahRse1JtXCPrx2bd3"
+ "iN8Dx91fbkxjYIOGTsjYoHKTp0BbaFkJih776fcHgnFSb+7mJcDuJVvJOXxEH6w0"
+ "1vo6VtujCqj1arqbyoal+xtAaczF3us5cOEp45sR1zAWTn1+7omN7VWV4QqJPaDS"
+ "gBSESc0l1grO0i1VUSumayk7yBKIkb+LBvcG6WnYZHCi7VdLmaxER5m8oZfER66b"
+ "heHoiSQIZf9PAY6Guw2DT5BTc54j/AaLQAKf2qcRSgQLVo5kQaddF3rCpsXoB/74"
+ "6QIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szWowPatchPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOsMV0LagAWPEtEQM6b9"
+ "6FHFkUyGbbyda2/Dfc9dyl21E9QvX+Yw7qKRMAKPzA2TlQQLZKvXpnKXF/YIK5xa"
+ "5uwg9CEHCEAYolLG4xn0FUOE0E/0PuuytI0p0ICe6rk00PifZzTr8na2wI/l/GnQ"
+ "bvnIVF1ck6cslATpQJ5JJVMXzoFlUABS19WESw4MXuJAS3AbMhxNWdEhVv7eO51c"
+ "yGjRLy9QjogZODZTY0fSEksgBqQxNCoYVJYI/sF5K2flDsGqrIp0OdJ6teJlzg1Y"
+ "UjYnb6bKjlidXoHEXI2TgA/mD6O3XFIt08I9s3crOCTgICq7cgX35qrZiIVWZdRv"
+ "TwIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szWowSurveyPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIt1DR6nRyyKsy2qahHe"
+ "MKLtacatn/KxieHcwH87wLBxKy+jZ0gycTmJ7SaTdBAEMDs/V5IPIXEtoqYnid2c"
+ "63TmfGDU92oc3Ph1PWUZ2PWxBhT06HYxRdbrgHw9/I29pNPi/607x+lzPORITOgU"
+ "BR6MR8au8HsQP4bn4vkJNgnSgojh48/XQOB/cAln7As1neP61NmVimoLR4Bwi3zt"
+ "zfgrZaUpyeNCUrOYJmH09YIjbBySTtXOUidoPHjFrMsCWpr6xs8xbETbs7MJFL6a"
+ "vcUfTT67qfIZ9RsuKfnXJTIrV0kwDSjjuNXiPTmWAehSsiHIsrUXX5RNcwsSjClr"
+ "nQIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+static const char * szStarcraft2MapPublicKey =
+ "-----BEGIN PUBLIC KEY-----"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmk4GT8zb+ICC25a17KZB"
+ "q/ygKGJ2VSO6IT5PGHJlm1KfnHBA4B6SH3xMlJ4c6eG2k7QevZv+FOhjsAHubyWq"
+ "2VKqWbrIFKv2ILc2RfMn8J9EDVRxvcxh6slRrVL69D0w1tfVGjMiKq2Fym5yGoRT"
+ "E7CRgDqbAbXP9LBsCNWHiJLwfxMGzHbk8pIl9oia5pvM7ofZamSHchxlpy6xa4GJ"
+ "7xKN01YCNvklTL1D7uol3wkwcHc7vrF8QwuJizuA5bSg4poEGtH62BZOYi+UL/z0"
+ "31YK+k9CbQyM0X0pJoJoYz1TK+Y5J7vBnXCZtfcTYQ/ZzN6UcxTa57dJaiOlCh9z"
+ "nQIDAQAB"
+ "-----END PUBLIC KEY-----";
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static void memrev(unsigned char *buf, size_t count)
+{
+ unsigned char *r;
+
+ for (r = buf + count - 1; buf < r; buf++, r--)
+ {
+ *buf ^= *r;
+ *r ^= *buf;
+ *buf ^= *r;
+ }
+}
+
+static bool decode_base64_key(const char * szKeyBase64, rsa_key * key)
+{
+ unsigned char decoded_key[0x200];
+ const char * szBase64Begin;
+ const char * szBase64End;
+ unsigned long decoded_length = sizeof(decoded_key);
+ unsigned long length;
+
+ // Find out the begin of the BASE64 data
+ szBase64Begin = szKeyBase64 + strlen("-----BEGIN PUBLIC KEY-----");
+ szBase64End = szBase64Begin + strlen(szBase64Begin) - strlen("-----END PUBLIC KEY-----");
+ if(szBase64End[0] != '-')
+ return false;
+
+ // decode the base64 string
+ length = (unsigned long)(szBase64End - szBase64Begin);
+ if(base64_decode((unsigned char *)szBase64Begin, length, decoded_key, &decoded_length) != CRYPT_OK)
+ return false;
+
+ // Create RSA key
+ if(rsa_import(decoded_key, decoded_length, key) != CRYPT_OK)
+ return false;
+
+ return true;
+}
+
+static void GetPlainAnsiFileName(
+ const TCHAR * szFileName,
+ char * szPlainName)
+{
+ const TCHAR * szPlainNameT = GetPlainFileName(szFileName);
+
+ // Convert the plain name to ANSI
+ while(*szPlainNameT != 0)
+ *szPlainName++ = (char)*szPlainNameT++;
+ *szPlainName = 0;
+}
+
+// Calculate begin and end of the MPQ archive
+static void CalculateArchiveRange(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ ULONGLONG TempPos = 0;
+ char szMapHeader[0x200];
+
+ // Get the MPQ begin
+ pSI->BeginMpqData = ha->MpqPos;
+
+ // Warcraft III maps are signed from the map header to the end
+ if(FileStream_Read(ha->pStream, &TempPos, szMapHeader, sizeof(szMapHeader)))
+ {
+ // Is it a map header ?
+ if(szMapHeader[0] == 'H' && szMapHeader[1] == 'M' && szMapHeader[2] == '3' && szMapHeader[3] == 'W')
+ {
+ // We will have to hash since the map header
+ pSI->BeginMpqData = 0;
+ }
+ }
+
+ // Get the MPQ data end. This is stored in the MPQ header
+ pSI->EndMpqData = ha->MpqPos + ha->pHeader->ArchiveSize64;
+
+ // Get the size of the entire file
+ FileStream_GetSize(ha->pStream, &pSI->EndOfFile);
+}
+
+static bool CalculateMpqHashMd5(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI,
+ LPBYTE pMd5Digest)
+{
+ hash_state md5_state;
+ ULONGLONG BeginBuffer;
+ ULONGLONG EndBuffer;
+ LPBYTE pbDigestBuffer = NULL;
+
+ // Allocate buffer for creating the MPQ digest.
+ pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE);
+ if(pbDigestBuffer == NULL)
+ return false;
+
+ // Initialize the MD5 hash state
+ md5_init(&md5_state);
+
+ // Set the byte offset of begin of the data
+ BeginBuffer = pSI->BeginMpqData;
+
+ // Create the digest
+ for(;;)
+ {
+ ULONGLONG BytesRemaining;
+ LPBYTE pbSigBegin = NULL;
+ LPBYTE pbSigEnd = NULL;
+ DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE;
+
+ // Check the number of bytes remaining
+ BytesRemaining = pSI->EndMpqData - BeginBuffer;
+ if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE)
+ dwToRead = (DWORD)BytesRemaining;
+ if(dwToRead == 0)
+ break;
+
+ // Read the next chunk
+ if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead))
+ {
+ STORM_FREE(pbDigestBuffer);
+ return false;
+ }
+
+ // Move the current byte offset
+ EndBuffer = BeginBuffer + dwToRead;
+
+ // Check if the signature is within the loaded digest
+ if(BeginBuffer <= pSI->BeginExclude && pSI->BeginExclude < EndBuffer)
+ pbSigBegin = pbDigestBuffer + (size_t)(pSI->BeginExclude - BeginBuffer);
+ if(BeginBuffer <= pSI->EndExclude && pSI->EndExclude < EndBuffer)
+ pbSigEnd = pbDigestBuffer + (size_t)(pSI->EndExclude - BeginBuffer);
+
+ // Zero the part that belongs to the signature
+ if(pbSigBegin != NULL || pbSigEnd != NULL)
+ {
+ if(pbSigBegin == NULL)
+ pbSigBegin = pbDigestBuffer;
+ if(pbSigEnd == NULL)
+ pbSigEnd = pbDigestBuffer + dwToRead;
+
+ memset(pbSigBegin, 0, (pbSigEnd - pbSigBegin));
+ }
+
+ // Pass the buffer to the hashing function
+ md5_process(&md5_state, pbDigestBuffer, dwToRead);
+
+ // Move pointers
+ BeginBuffer += dwToRead;
+ }
+
+ // Finalize the MD5 hash
+ md5_done(&md5_state, pMd5Digest);
+ STORM_FREE(pbDigestBuffer);
+ return true;
+}
+
+static void AddTailToSha1(
+ hash_state * psha1_state,
+ const char * szTail)
+{
+ unsigned char * pbTail = (unsigned char *)szTail;
+ unsigned char szUpperCase[0x200];
+ unsigned long nLength = 0;
+
+ // Convert the tail to uppercase
+ // Note that we don't need to terminate the string with zero
+ while(*pbTail != 0)
+ {
+ szUpperCase[nLength++] = AsciiToUpperTable[*pbTail++];
+ }
+
+ // Append the tail to the SHA1
+ sha1_process(psha1_state, szUpperCase, nLength);
+}
+
+static bool CalculateMpqHashSha1(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI,
+ unsigned char * sha1_tail0,
+ unsigned char * sha1_tail1,
+ unsigned char * sha1_tail2)
+{
+ ULONGLONG BeginBuffer;
+ hash_state sha1_state_temp;
+ hash_state sha1_state;
+ LPBYTE pbDigestBuffer = NULL;
+ char szPlainName[MAX_PATH];
+
+ // Allocate buffer for creating the MPQ digest.
+ pbDigestBuffer = STORM_ALLOC(BYTE, MPQ_DIGEST_UNIT_SIZE);
+ if(pbDigestBuffer == NULL)
+ return false;
+
+ // Initialize SHA1 state structure
+ sha1_init(&sha1_state);
+
+ // Calculate begin of data to be hashed
+ BeginBuffer = pSI->BeginMpqData;
+
+ // Create the digest
+ for(;;)
+ {
+ ULONGLONG BytesRemaining;
+ DWORD dwToRead = MPQ_DIGEST_UNIT_SIZE;
+
+ // Check the number of bytes remaining
+ BytesRemaining = pSI->EndMpqData - BeginBuffer;
+ if(BytesRemaining < MPQ_DIGEST_UNIT_SIZE)
+ dwToRead = (DWORD)BytesRemaining;
+ if(dwToRead == 0)
+ break;
+
+ // Read the next chunk
+ if(!FileStream_Read(ha->pStream, &BeginBuffer, pbDigestBuffer, dwToRead))
+ {
+ STORM_FREE(pbDigestBuffer);
+ return false;
+ }
+
+ // Pass the buffer to the hashing function
+ sha1_process(&sha1_state, pbDigestBuffer, dwToRead);
+
+ // Move pointers
+ BeginBuffer += dwToRead;
+ }
+
+ // Add all three known tails and generate three hashes
+ memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
+ sha1_done(&sha1_state_temp, sha1_tail0);
+
+ memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
+ GetPlainAnsiFileName(FileStream_GetFileName(ha->pStream), szPlainName);
+ AddTailToSha1(&sha1_state_temp, szPlainName);
+ sha1_done(&sha1_state_temp, sha1_tail1);
+
+ memcpy(&sha1_state_temp, &sha1_state, sizeof(hash_state));
+ AddTailToSha1(&sha1_state_temp, "ARCHIVE");
+ sha1_done(&sha1_state_temp, sha1_tail2);
+
+ // Finalize the MD5 hash
+ STORM_FREE(pbDigestBuffer);
+ return true;
+}
+
+static int VerifyRawMpqData(
+ TMPQArchive * ha,
+ ULONGLONG ByteOffset,
+ DWORD dwDataSize)
+{
+ ULONGLONG DataOffset = ha->MpqPos + ByteOffset;
+ LPBYTE pbDataChunk;
+ LPBYTE pbMD5Array1; // Calculated MD5 array
+ LPBYTE pbMD5Array2; // MD5 array loaded from the MPQ
+ DWORD dwBytesInChunk;
+ DWORD dwChunkCount;
+ DWORD dwChunkSize = ha->pHeader->dwRawChunkSize;
+ 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 - 1) / dwChunkSize) + 1;
+ dwMD5Size = dwChunkCount * MD5_DIGEST_SIZE;
+
+ // Allocate space for data chunk and for the MD5 array
+ pbDataChunk = STORM_ALLOC(BYTE, dwChunkSize);
+ if(pbDataChunk == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Allocate space for MD5 array
+ pbMD5Array1 = STORM_ALLOC(BYTE, dwMD5Size);
+ pbMD5Array2 = STORM_ALLOC(BYTE, dwMD5Size);
+ if(pbMD5Array1 == NULL || pbMD5Array2 == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Calculate MD5 of each data chunk
+ if(nError == ERROR_SUCCESS)
+ {
+ LPBYTE pbMD5 = pbMD5Array1;
+
+ for(DWORD i = 0; i < dwChunkCount; i++)
+ {
+ // Get the number of bytes in the chunk
+ dwBytesInChunk = STORMLIB_MIN(dwChunkSize, dwDataSize);
+
+ // Read the data chunk
+ if(!FileStream_Read(ha->pStream, &DataOffset, pbDataChunk, dwBytesInChunk))
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Calculate MD5
+ CalculateDataBlockHash(pbDataChunk, dwBytesInChunk, pbMD5);
+
+ // Move pointers and offsets
+ DataOffset += dwBytesInChunk;
+ dwDataSize -= dwBytesInChunk;
+ pbMD5 += MD5_DIGEST_SIZE;
+ }
+ }
+
+ // Read the MD5 array
+ if(nError == ERROR_SUCCESS)
+ {
+ // Read the array of MD5
+ if(!FileStream_Read(ha->pStream, &DataOffset, pbMD5Array2, dwMD5Size))
+ nError = GetLastError();
+ }
+
+ // Compare the array of MD5
+ if(nError == ERROR_SUCCESS)
+ {
+ // Compare the MD5
+ if(memcmp(pbMD5Array1, pbMD5Array2, dwMD5Size))
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Free memory and return result
+ if(pbMD5Array2 != NULL)
+ STORM_FREE(pbMD5Array2);
+ if(pbMD5Array1 != NULL)
+ STORM_FREE(pbMD5Array1);
+ if(pbDataChunk != NULL)
+ STORM_FREE(pbDataChunk);
+ return nError;
+}
+
+static DWORD VerifyWeakSignature(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ BYTE RevSignature[MPQ_WEAK_SIGNATURE_SIZE];
+ BYTE Md5Digest[MD5_DIGEST_SIZE];
+ rsa_key key;
+ int hash_idx = find_hash("md5");
+ int result = 0;
+
+ // The signature might be zeroed out. In that case, we ignore it
+ if(!IsValidSignature(pSI->Signature))
+ return ERROR_WEAK_SIGNATURE_OK;
+
+ // Calculate hash of the entire archive, skipping the (signature) file
+ if(!CalculateMpqHashMd5(ha, pSI, Md5Digest))
+ return ERROR_VERIFY_FAILED;
+
+ // Import the Blizzard key in OpenSSL format
+ if(!decode_base64_key(szBlizzardWeakPublicKey, &key))
+ return ERROR_VERIFY_FAILED;
+
+ // Verify the signature
+ memcpy(RevSignature, &pSI->Signature[8], MPQ_WEAK_SIGNATURE_SIZE);
+ memrev(RevSignature, MPQ_WEAK_SIGNATURE_SIZE);
+ rsa_verify_hash_ex(RevSignature, MPQ_WEAK_SIGNATURE_SIZE, Md5Digest, sizeof(Md5Digest), LTC_LTC_PKCS_1_V1_5, hash_idx, 0, &result, &key);
+ rsa_free(&key);
+
+ // Return the result
+ return result ? ERROR_WEAK_SIGNATURE_OK : ERROR_WEAK_SIGNATURE_ERROR;
+}
+
+static DWORD VerifyStrongSignatureWithKey(
+ unsigned char * reversed_signature,
+ unsigned char * padded_digest,
+ const char * szPublicKey)
+{
+ rsa_key key;
+ int result = 0;
+
+ // Import the Blizzard key in OpenSSL format
+ if(!decode_base64_key(szPublicKey, &key))
+ {
+ assert(false);
+ return ERROR_VERIFY_FAILED;
+ }
+
+ // Verify the signature
+ if(rsa_verify_simple(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE, padded_digest, MPQ_STRONG_SIGNATURE_SIZE, &result, &key) != CRYPT_OK)
+ return ERROR_VERIFY_FAILED;
+
+ // Free the key and return result
+ rsa_free(&key);
+ return result ? ERROR_STRONG_SIGNATURE_OK : ERROR_STRONG_SIGNATURE_ERROR;
+}
+
+static DWORD VerifyStrongSignature(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ unsigned char reversed_signature[MPQ_STRONG_SIGNATURE_SIZE];
+ unsigned char Sha1Digest_tail0[SHA1_DIGEST_SIZE];
+ unsigned char Sha1Digest_tail1[SHA1_DIGEST_SIZE];
+ unsigned char Sha1Digest_tail2[SHA1_DIGEST_SIZE];
+ unsigned char padded_digest[MPQ_STRONG_SIGNATURE_SIZE];
+ DWORD dwResult;
+ size_t digest_offset;
+
+ // Calculate SHA1 hash of the archive
+ if(!CalculateMpqHashSha1(ha, pSI, Sha1Digest_tail0, Sha1Digest_tail1, Sha1Digest_tail2))
+ return ERROR_VERIFY_FAILED;
+
+ // Prepare the signature for decryption
+ memcpy(reversed_signature, &pSI->Signature[4], MPQ_STRONG_SIGNATURE_SIZE);
+ memrev(reversed_signature, MPQ_STRONG_SIGNATURE_SIZE);
+
+ // Prepare the padded digest for comparison
+ digest_offset = sizeof(padded_digest) - SHA1_DIGEST_SIZE;
+ memset(padded_digest, 0xbb, digest_offset);
+ padded_digest[0] = 0x0b;
+
+ // Try Blizzard Strong public key with no SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szBlizzardStrongPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try War 3 map public key with plain file name as SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail1, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWarcraft3MapPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try WoW-TBC public key with "ARCHIVE" as SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail2, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowPatchPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try Survey public key with no SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szWowSurveyPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ // Try Starcraft II public key with no SHA1 tail
+ memcpy(padded_digest + digest_offset, Sha1Digest_tail0, SHA1_DIGEST_SIZE);
+ memrev(padded_digest + digest_offset, SHA1_DIGEST_SIZE);
+ dwResult = VerifyStrongSignatureWithKey(reversed_signature, padded_digest, szStarcraft2MapPublicKey);
+ if(dwResult == ERROR_STRONG_SIGNATURE_OK)
+ return dwResult;
+
+ return ERROR_STRONG_SIGNATURE_ERROR;
+}
+
+static DWORD VerifyFile(
+ HANDLE hMpq,
+ const char * szFileName,
+ LPDWORD pdwCrc32,
+ char * pMD5,
+ DWORD dwFlags)
+{
+ hash_state md5_state;
+ unsigned char * pFileMd5;
+ unsigned char md5[MD5_DIGEST_SIZE];
+ TFileEntry * pFileEntry;
+ TMPQFile * hf;
+ BYTE Buffer[0x1000];
+ HANDLE hFile = NULL;
+ DWORD dwVerifyResult = 0;
+ DWORD dwTotalBytes = 0;
+ DWORD dwCrc32 = 0;
+
+ //
+ // Note: When the MPQ is patched, it will
+ // automatically check the patched version of the file
+ //
+
+ // Make sure the md5 is initialized
+ memset(md5, 0, sizeof(md5));
+
+ // 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, SFILE_OPEN_FROM_MPQ, &hFile))
+ {
+ // Get the file size
+ hf = (TMPQFile *)hFile;
+ pFileEntry = hf->pFileEntry;
+ dwTotalBytes = SFileGetFileSize(hFile, NULL);
+
+ // Initialize the CRC32 and MD5 contexts
+ md5_init(&md5_state);
+ dwCrc32 = crc32(0, Z_NULL, 0);
+
+ // Also turn on sector checksum verification
+ if(dwFlags & SFILE_VERIFY_SECTOR_CRC)
+ hf->bCheckSectorCRCs = true;
+
+ // Go through entire file and update both CRC32 and MD5
+ for(;;)
+ {
+ DWORD dwBytesRead = 0;
+
+ // Read data from file
+ SFileReadFile(hFile, Buffer, sizeof(Buffer), &dwBytesRead, NULL);
+ if(dwBytesRead == 0)
+ {
+ if(GetLastError() == ERROR_CHECKSUM_ERROR)
+ dwVerifyResult |= VERIFY_FILE_SECTOR_CRC_ERROR;
+ break;
+ }
+
+ // Update CRC32 value
+ if(dwFlags & SFILE_VERIFY_FILE_CRC)
+ dwCrc32 = crc32(dwCrc32, Buffer, dwBytesRead);
+
+ // Update MD5 value
+ if(dwFlags & SFILE_VERIFY_FILE_MD5)
+ md5_process(&md5_state, Buffer, dwBytesRead);
+
+ // Decrement the total size
+ dwTotalBytes -= dwBytesRead;
+ }
+
+ // If the file has sector checksums, indicate it in the flags
+ if(dwFlags & SFILE_VERIFY_SECTOR_CRC)
+ {
+ if((hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->SectorChksums != NULL && hf->SectorChksums[0] != 0)
+ dwVerifyResult |= VERIFY_FILE_HAS_SECTOR_CRC;
+ }
+
+ // Check if the entire file has been read
+ // No point in checking CRC32 and MD5 if not
+ // Skip checksum checks if the file has patches
+ if(dwTotalBytes == 0)
+ {
+ // Check CRC32 and MD5 only if there is no patches
+ if(hf->hfPatch == NULL)
+ {
+ // Check if the CRC32 matches.
+ if(dwFlags & SFILE_VERIFY_FILE_CRC)
+ {
+ // Only check the CRC32 if it is valid
+ if(pFileEntry->dwCrc32 != 0)
+ {
+ dwVerifyResult |= VERIFY_FILE_HAS_CHECKSUM;
+ if(dwCrc32 != pFileEntry->dwCrc32)
+ dwVerifyResult |= VERIFY_FILE_CHECKSUM_ERROR;
+ }
+ }
+
+ // Check if MD5 matches
+ if(dwFlags & SFILE_VERIFY_FILE_MD5)
+ {
+ // Patch files have their MD5 saved in the patch info
+ pFileMd5 = (hf->pPatchInfo != NULL) ? hf->pPatchInfo->md5 : pFileEntry->md5;
+ md5_done(&md5_state, md5);
+
+ // Only check the MD5 if it is valid
+ if(IsValidMD5(pFileMd5))
+ {
+ dwVerifyResult |= VERIFY_FILE_HAS_MD5;
+ if(memcmp(md5, pFileMd5, MD5_DIGEST_SIZE))
+ dwVerifyResult |= VERIFY_FILE_MD5_ERROR;
+ }
+ }
+ }
+ else
+ {
+ // Patched files are MD5-checked automatically
+ dwVerifyResult |= VERIFY_FILE_HAS_MD5;
+ }
+ }
+ else
+ {
+ dwVerifyResult |= VERIFY_READ_ERROR;
+ }
+
+ SFileCloseFile(hFile);
+ }
+ else
+ {
+ // Remember that the file couldn't be open
+ dwVerifyResult |= VERIFY_OPEN_ERROR;
+ }
+
+ // If the caller required CRC32 and/or MD5, give it to him
+ if(pdwCrc32 != NULL)
+ *pdwCrc32 = dwCrc32;
+ if(pMD5 != NULL)
+ memcpy(pMD5, md5, MD5_DIGEST_SIZE);
+
+ return dwVerifyResult;
+}
+
+// Used in SFileGetFileInfo
+bool QueryMpqSignatureInfo(
+ TMPQArchive * ha,
+ PMPQ_SIGNATURE_INFO pSI)
+{
+ TFileEntry * pFileEntry;
+ ULONGLONG ExtraBytes;
+ DWORD dwFileSize;
+
+ // Make sure it's all zeroed
+ memset(pSI, 0, sizeof(MPQ_SIGNATURE_INFO));
+
+ // Calculate the range of the MPQ
+ CalculateArchiveRange(ha, pSI);
+
+ // If there is "(signature)" file in the MPQ, it has a weak signature
+ pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ // Calculate the begin and end of the signature file itself
+ pSI->BeginExclude = ha->MpqPos + pFileEntry->ByteOffset;
+ pSI->EndExclude = pSI->BeginExclude + pFileEntry->dwCmpSize;
+ dwFileSize = (DWORD)(pSI->EndExclude - pSI->BeginExclude);
+
+ // Does the signature have proper size?
+ if(dwFileSize == MPQ_SIGNATURE_FILE_SIZE)
+ {
+ // Read the weak signature
+ if(!FileStream_Read(ha->pStream, &pSI->BeginExclude, pSI->Signature, dwFileSize))
+ return false;
+
+ pSI->SignatureTypes |= SIGNATURE_TYPE_WEAK;
+ pSI->cbSignatureSize = dwFileSize;
+ return true;
+ }
+ }
+
+ // If there is extra bytes beyond the end of the archive,
+ // it's the strong signature
+ ExtraBytes = pSI->EndOfFile - pSI->EndMpqData;
+ if(ExtraBytes >= (MPQ_STRONG_SIGNATURE_SIZE + 4))
+ {
+ // Read the strong signature
+ if(!FileStream_Read(ha->pStream, &pSI->EndMpqData, pSI->Signature, (MPQ_STRONG_SIGNATURE_SIZE + 4)))
+ return false;
+
+ // Check the signature header "NGIS"
+ if(pSI->Signature[0] != 'N' || pSI->Signature[1] != 'G' || pSI->Signature[2] != 'I' || pSI->Signature[3] != 'S')
+ return false;
+
+ pSI->SignatureTypes |= SIGNATURE_TYPE_STRONG;
+ return true;
+ }
+
+ // Succeeded, but no known signature found
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Support for weak signature
+
+int SSignFileCreate(TMPQArchive * ha)
+{
+ TMPQFile * hf = NULL;
+ BYTE EmptySignature[MPQ_SIGNATURE_FILE_SIZE];
+ int nError = ERROR_SUCCESS;
+
+ // Only save the signature if we should do so
+ if(ha->dwFileFlags3 != 0)
+ {
+ // The (signature) file must be non-encrypted and non-compressed
+ assert(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW);
+ assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
+ assert(ha->dwReservedFiles > 0);
+
+ // Create the (signature) file file in the MPQ
+ // Note that the file must not be compressed or encrypted
+ nError = SFileAddFile_Init(ha, SIGNATURE_NAME,
+ 0,
+ sizeof(EmptySignature),
+ LANG_NEUTRAL,
+ ha->dwFileFlags3 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Write the empty signature file to the archive
+ if(nError == ERROR_SUCCESS)
+ {
+ // Write the empty zeroed file to the MPQ
+ memset(EmptySignature, 0, sizeof(EmptySignature));
+ nError = SFileAddFile_Write(hf, EmptySignature, (DWORD)sizeof(EmptySignature), 0);
+ SFileAddFile_Finish(hf);
+
+ // Clear the invalid mark
+ ha->dwFlags &= ~(MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_SIGNATURE_NONE);
+ ha->dwReservedFiles--;
+ }
+ }
+
+ return nError;
+}
+
+int SSignFileFinish(TMPQArchive * ha)
+{
+ MPQ_SIGNATURE_INFO si;
+ unsigned long signature_len = MPQ_WEAK_SIGNATURE_SIZE;
+ BYTE WeakSignature[MPQ_SIGNATURE_FILE_SIZE];
+ BYTE Md5Digest[MD5_DIGEST_SIZE];
+ rsa_key key;
+ int hash_idx = find_hash("md5");
+
+ // Sanity checks
+ assert((ha->dwFlags & MPQ_FLAG_CHANGED) == 0);
+ assert(ha->dwFileFlags3 == MPQ_FILE_EXISTS);
+
+ // Query the weak signature info
+ memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO));
+ if(!QueryMpqSignatureInfo(ha, &si))
+ return ERROR_FILE_CORRUPT;
+
+ // There must be exactly one signature
+ if(si.SignatureTypes != SIGNATURE_TYPE_WEAK)
+ return ERROR_FILE_CORRUPT;
+
+ // Calculate MD5 of the entire archive
+ if(!CalculateMpqHashMd5(ha, &si, Md5Digest))
+ return ERROR_VERIFY_FAILED;
+
+ // Decode the private key
+ if(!decode_base64_key(szBlizzardWeakPrivateKey, &key))
+ return ERROR_VERIFY_FAILED;
+
+ // Sign the hash
+ memset(WeakSignature, 0, sizeof(WeakSignature));
+ rsa_sign_hash_ex(Md5Digest, sizeof(Md5Digest), WeakSignature + 8, &signature_len, LTC_LTC_PKCS_1_V1_5, 0, 0, hash_idx, 0, &key);
+ memrev(WeakSignature + 8, MPQ_WEAK_SIGNATURE_SIZE);
+ rsa_free(&key);
+
+ // Write the signature to the MPQ. Don't use SFile* functions, but write the hash directly
+ if(!FileStream_Write(ha->pStream, &si.BeginExclude, WeakSignature, MPQ_SIGNATURE_FILE_SIZE))
+ return GetLastError();
+
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Public (exported) functions
+
+bool WINAPI SFileGetFileChecksums(HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5)
+{
+ DWORD dwVerifyResult;
+ DWORD dwVerifyFlags = 0;
+
+ if(pdwCrc32 != NULL)
+ dwVerifyFlags |= SFILE_VERIFY_FILE_CRC;
+ if(pMD5 != NULL)
+ dwVerifyFlags |= SFILE_VERIFY_FILE_MD5;
+
+ dwVerifyResult = VerifyFile(hMpq,
+ szFileName,
+ pdwCrc32,
+ pMD5,
+ dwVerifyFlags);
+
+ // If verification failed, return zero
+ if(dwVerifyResult & VERIFY_FILE_ERROR_MASK)
+ {
+ SetLastError(ERROR_FILE_CORRUPT);
+ return false;
+ }
+
+ return true;
+}
+
+
+DWORD WINAPI SFileVerifyFile(HANDLE hMpq, const char * szFileName, DWORD dwFlags)
+{
+ return VerifyFile(hMpq,
+ szFileName,
+ NULL,
+ NULL,
+ dwFlags);
+}
+
+// Verifies raw data of the archive Only works for MPQs version 4 or newer
+int WINAPI SFileVerifyRawData(HANDLE hMpq, DWORD dwWhatToVerify, const char * szFileName)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TFileEntry * pFileEntry;
+ TMPQHeader * pHeader;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(hMpq))
+ return ERROR_INVALID_PARAMETER;
+ pHeader = ha->pHeader;
+
+ // If the archive doesn't have raw data MD5, report it as OK
+ if(pHeader->dwRawChunkSize == 0)
+ return ERROR_SUCCESS;
+
+ // If we have to verify MPQ header, do it
+ switch(dwWhatToVerify)
+ {
+ case SFILE_VERIFY_MPQ_HEADER:
+
+ // Only if the header is of version 4 or newer
+ if(pHeader->dwHeaderSize >= (MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE))
+ return VerifyRawMpqData(ha, 0, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE);
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_HET_TABLE:
+
+ // Only if we have HET table
+ if(pHeader->HetTablePos64 && pHeader->HetTableSize64)
+ return VerifyRawMpqData(ha, pHeader->HetTablePos64, (DWORD)pHeader->HetTableSize64);
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_BET_TABLE:
+
+ // Only if we have BET table
+ if(pHeader->BetTablePos64 && pHeader->BetTableSize64)
+ return VerifyRawMpqData(ha, pHeader->BetTablePos64, (DWORD)pHeader->BetTableSize64);
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_HASH_TABLE:
+
+ // Hash table is not protected by MD5
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_BLOCK_TABLE:
+
+ // Block table is not protected by MD5
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_HIBLOCK_TABLE:
+
+ // It is unknown if the hi-block table is protected my MD5 or not.
+ return ERROR_SUCCESS;
+
+ case SFILE_VERIFY_FILE:
+
+ // Verify parameters
+ if(szFileName == NULL || *szFileName == 0)
+ return ERROR_INVALID_PARAMETER;
+
+ // Get the offset of a file
+ pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale);
+ if(pFileEntry == NULL)
+ return ERROR_FILE_NOT_FOUND;
+
+ return VerifyRawMpqData(ha, pFileEntry->ByteOffset, pFileEntry->dwCmpSize);
+ }
+
+ return ERROR_INVALID_PARAMETER;
+}
+
+
+// Verifies the archive against the signature
+DWORD WINAPI SFileVerifyArchive(HANDLE hMpq)
+{
+ MPQ_SIGNATURE_INFO si;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Verify input parameters
+ if(!IsValidMpqHandle(hMpq))
+ return ERROR_VERIFY_FAILED;
+
+ // If the archive was modified, we need to flush it
+ if(ha->dwFlags & MPQ_FLAG_CHANGED)
+ SFileFlushArchive(hMpq);
+
+ // Get the MPQ signature and signature type
+ memset(&si, 0, sizeof(MPQ_SIGNATURE_INFO));
+ if(!QueryMpqSignatureInfo(ha, &si))
+ return ERROR_VERIFY_FAILED;
+
+ // If there is no signature
+ if(si.SignatureTypes == 0)
+ return ERROR_NO_SIGNATURE;
+
+ // We haven't seen a MPQ with both signatures
+ assert(si.SignatureTypes == SIGNATURE_TYPE_WEAK || si.SignatureTypes == SIGNATURE_TYPE_STRONG);
+
+ // Verify the strong signature, if present
+ if(si.SignatureTypes & SIGNATURE_TYPE_STRONG)
+ return VerifyStrongSignature(ha, &si);
+
+ // Verify the weak signature, if present
+ if(si.SignatureTypes & SIGNATURE_TYPE_WEAK)
+ return VerifyWeakSignature(ha, &si);
+
+ return ERROR_NO_SIGNATURE;
+}
+
+// Verifies the archive against the signature
+bool WINAPI SFileSignArchive(HANDLE hMpq, DWORD dwSignatureType)
+{
+ TMPQArchive * ha;
+
+ // Verify the archive handle
+ ha = IsValidMpqHandle(hMpq);
+ if(ha == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // We only support weak signature, and only for MPQs version 1.0
+ if(dwSignatureType != SIGNATURE_TYPE_WEAK)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // The archive must not be malformed and must not be read-only
+ if(ha->dwFlags & (MPQ_FLAG_READ_ONLY | MPQ_FLAG_MALFORMED))
+ {
+ SetLastError(ERROR_ACCESS_DENIED);
+ return false;
+ }
+
+ // If the signature is not there yet
+ if(ha->dwFileFlags3 == 0)
+ {
+ // Turn the signature on. The signature will
+ // be applied when the archive is closed
+ ha->dwFlags |= MPQ_FLAG_SIGNATURE_NEW | MPQ_FLAG_CHANGED;
+ ha->dwFileFlags3 = MPQ_FILE_EXISTS;
+ ha->dwReservedFiles++;
+ }
+
+ return true;
+}
+
diff --git a/src/StormCommon.h b/src/StormCommon.h index 0b1ccb2..3c2148e 100644 --- a/src/StormCommon.h +++ b/src/StormCommon.h @@ -1,372 +1,372 @@ -/*****************************************************************************/ -/* SCommon.h Copyright (c) Ladislav Zezula 2003 */ -/*---------------------------------------------------------------------------*/ -/* Common functions for encryption/decryption from Storm.dll. Included by */ -/* SFile*** functions, do not include and do not use this file directly */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 24.03.03 1.00 Lad The first version of SFileCommon.h */ -/* 12.06.04 1.00 Lad Renamed to SCommon.h */ -/* 06.09.10 1.00 Lad Renamed to StormCommon.h */ -/*****************************************************************************/ - -#ifndef __STORMCOMMON_H__ -#define __STORMCOMMON_H__ - -//----------------------------------------------------------------------------- -// Compression support - -// Include functions from Pkware Data Compression Library -#include "pklib/pklib.h" - -// Include functions from Huffmann compression -#include "huffman/huff.h" - -// Include functions from IMA ADPCM compression -#include "adpcm/adpcm.h" - -// Include functions from SPARSE compression -#include "sparse/sparse.h" - -// Include functions from LZMA compression -#include "lzma/C/LzmaEnc.h" -#include "lzma/C/LzmaDec.h" - -// Include functions from zlib -#ifndef __SYS_ZLIB - #include "zlib/zlib.h" -#else - #include <zlib.h> -#endif - -// Include functions from bzlib -#ifndef __SYS_BZLIB - #include "bzip2/bzlib.h" -#else - #include <bzlib.h> -#endif - -//----------------------------------------------------------------------------- -// Cryptography support - -// Headers from LibTomCrypt -#include "libtomcrypt/src/headers/tomcrypt.h" - -// For HashStringJenkins -#include "jenkins/lookup.h" - -//----------------------------------------------------------------------------- -// StormLib private defines - -#define ID_MPQ_FILE 0x46494c45 // Used internally for checking TMPQFile ('FILE') - -// Prevent problems with CRT "min" and "max" functions, -// as they are not defined on all platforms -#define STORMLIB_MIN(a, b) ((a < b) ? a : b) -#define STORMLIB_MAX(a, b) ((a > b) ? a : b) -#define STORMLIB_UNUSED(p) ((void)(p)) - -// Macro for building 64-bit file offset from two 32-bit -#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo) - -//----------------------------------------------------------------------------- -// MPQ signature information - -// Size of each signature type -#define MPQ_WEAK_SIGNATURE_SIZE 64 -#define MPQ_STRONG_SIGNATURE_SIZE 256 -#define MPQ_STRONG_SIGNATURE_ID 0x5349474E // ID of the strong signature ("NGIS") -#define MPQ_SIGNATURE_FILE_SIZE (MPQ_WEAK_SIGNATURE_SIZE + 8) - -// MPQ signature info -typedef struct _MPQ_SIGNATURE_INFO -{ - ULONGLONG BeginMpqData; // File offset where the hashing starts - ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file) - ULONGLONG EndExclude; // End of the excluded area (used for (signature) file) - ULONGLONG EndMpqData; // File offset where the hashing ends - ULONGLONG EndOfFile; // Size of the entire file - BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10]; - DWORD cbSignatureSize; // Length of the signature - DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX - -} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO; - -//----------------------------------------------------------------------------- -// Memory management -// -// We use our own macros for allocating/freeing memory. If you want -// to redefine them, please keep the following rules: -// -// - The memory allocation must return NULL if not enough memory -// (i.e not to throw exception) -// - The allocating function does not need to fill the allocated buffer with zeros -// - Memory freeing function doesn't have to test the pointer to NULL -// - -//#if defined(_MSC_VER) && defined(_DEBUG) -// -//#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type))) -//#define STORM_REALLOC(type, ptr, nitems) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((nitems) * sizeof(type))) -//#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr) -// -//#else - -#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type)) -#define STORM_REALLOC(type, ptr, nitems) (type *)realloc(ptr, ((nitems) * sizeof(type))) -#define STORM_FREE(ptr) free(ptr) - -//#endif - -//----------------------------------------------------------------------------- -// StormLib internal global variables - -extern LCID lcFileLocale; // Preferred file locale - -//----------------------------------------------------------------------------- -// Conversion to uppercase/lowercase (and "/" to "\") - -extern unsigned char AsciiToLowerTable[256]; -extern unsigned char AsciiToUpperTable[256]; - -//----------------------------------------------------------------------------- -// Safe string functions - -void StringCopyA(char * dest, const char * src, size_t nMaxChars); -void StringCatA(char * dest, const char * src, size_t nMaxChars); - -void StringCopyT(TCHAR * dest, const TCHAR * src, size_t nMaxChars); -void StringCatT(TCHAR * dest, const TCHAR * src, size_t nMaxChars); - -//----------------------------------------------------------------------------- -// Encryption and decryption functions - -#define MPQ_HASH_TABLE_INDEX 0x000 -#define MPQ_HASH_NAME_A 0x100 -#define MPQ_HASH_NAME_B 0x200 -#define MPQ_HASH_FILE_KEY 0x300 -#define MPQ_HASH_KEY2_MIX 0x400 - -DWORD HashString(const char * szFileName, DWORD dwHashType); -DWORD HashStringSlash(const char * szFileName, DWORD dwHashType); -DWORD HashStringLower(const char * szFileName, DWORD dwHashType); - -void InitializeMpqCryptography(); - -DWORD GetHashTableSizeForFileCount(DWORD dwFileCount); - -bool IsPseudoFileName(const char * szFileName, LPDWORD pdwFileIndex); -ULONGLONG HashStringJenkins(const char * szFileName); - -DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion); - -void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey); -void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey); - -DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwSectorOffsLen); -DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize); -DWORD DecryptFileKey(const char * szFileName, ULONGLONG MpqPos, DWORD dwFileSize, DWORD dwFlags); - -bool IsValidMD5(LPBYTE pbMd5); -bool IsValidSignature(LPBYTE pbSignature); -bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5); -void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash); - -//----------------------------------------------------------------------------- -// Handle validation functions - -TMPQArchive * IsValidMpqHandle(HANDLE hMpq); -TMPQFile * IsValidFileHandle(HANDLE hFile); - -//----------------------------------------------------------------------------- -// Support for MPQ file tables - -ULONGLONG FileOffsetFromMpqOffset(TMPQArchive * ha, ULONGLONG MpqOffset); -ULONGLONG CalculateRawSectorOffset(TMPQFile * hf, DWORD dwSectorOffset); - -int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags); - -bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash); - -TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale); -TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName); -TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash); -TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry, LCID lcLocale); - -TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey); -TMPQHetTable * LoadHetTable(TMPQArchive * ha); -TMPQBetTable * LoadBetTable(TMPQArchive * ha); - -TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries = false); -TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable); - -ULONGLONG FindFreeMpqSpace(TMPQArchive * ha); - -// Functions that load the HET and BET tables -int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize); -int LoadAnyHashTable(TMPQArchive * ha); -int BuildFileTable(TMPQArchive * ha); -int DefragmentFileTable(TMPQArchive * ha); - -int CreateFileTable(TMPQArchive * ha, DWORD dwFileTableSize); -int RebuildHetTable(TMPQArchive * ha); -int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize); -int SaveMPQTables(TMPQArchive * ha); - -TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwHashBitSize, LPBYTE pbSrcData); -void FreeHetTable(TMPQHetTable * pHetTable); - -TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount); -void FreeBetTable(TMPQBetTable * pBetTable); - -// Functions for finding files in the file table -TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex); -TFileEntry * GetFileEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale); -TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex); - -// Allocates file name in the file entry -void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName); - -// Allocates new file entry in the MPQ tables. Reuses existing, if possible -TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex); -int RenameFileEntry(TMPQArchive * ha, TMPQFile * hf, const char * szNewFileName); -int DeleteFileEntry(TMPQArchive * ha, TMPQFile * hf); - -// Invalidates entries for (listfile) and (attributes) -void InvalidateInternalFiles(TMPQArchive * ha); - -// Retrieves information about the strong signature -bool QueryMpqSignatureInfo(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSignatureInfo); - -//----------------------------------------------------------------------------- -// Support for alternate file formats (SBaseSubTypes.cpp) - -int ConvertSqpHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); -TMPQHash * LoadSqpHashTable(TMPQArchive * ha); -TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha); - -int ConvertMpkHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags); -void DecryptMpkTable(void * pvMpkTable, size_t cbSize); -TMPQHash * LoadMpkHashTable(TMPQArchive * ha); -TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha); -int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer); - -//----------------------------------------------------------------------------- -// Common functions - MPQ File - -TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry); -TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize); -void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey, bool * pbTableIsCut); -int AllocateSectorBuffer(TMPQFile * hf); -int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile); -int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile); -int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile); -int WritePatchInfo(TMPQFile * hf); -int WriteSectorOffsets(TMPQFile * hf); -int WriteSectorChecksums(TMPQFile * hf); -int WriteMemDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, void * pvRawData, DWORD dwRawDataSize, DWORD dwChunkSize, LPDWORD pcbTotalSize); -int WriteMpqDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, DWORD dwRawDataSize, DWORD dwChunkSize); -void FreeFileHandle(TMPQFile *& hf); -void FreeArchiveHandle(TMPQArchive *& ha); - -//----------------------------------------------------------------------------- -// Patch functions - -// Structure used for the patching process -typedef struct _TMPQPatcher -{ - BYTE this_md5[MD5_DIGEST_SIZE]; // MD5 of the current file state - LPBYTE pbFileData1; // Primary working buffer - LPBYTE pbFileData2; // Secondary working buffer - DWORD cbMaxFileData; // Maximum allowed size of the patch data - DWORD cbFileData; // Current size of the result data - DWORD nCounter; // Counter of the patch process - -} TMPQPatcher; - -bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize); -int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf); -int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf); -void Patch_Finalize(TMPQPatcher * pPatcher); - -//----------------------------------------------------------------------------- -// Utility functions - -bool CheckWildCard(const char * szString, const char * szWildCard); -bool IsInternalMpqFileName(const char * szFileName); - -const TCHAR * GetPlainFileName(const TCHAR * szFileName); -const char * GetPlainFileName(const char * szFileName); - -void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength); -void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength); - -//----------------------------------------------------------------------------- -// Internal support for MPQ modifications - -int SFileAddFile_Init( - TMPQArchive * ha, - const char * szArchivedName, - ULONGLONG ft, - DWORD dwFileSize, - LCID lcLocale, - DWORD dwFlags, - TMPQFile ** phf - ); - -int SFileAddFile_Init( - TMPQArchive * ha, - TMPQFile * hfSrc, - TMPQFile ** phf - ); - -int SFileAddFile_Write( - TMPQFile * hf, - const void * pvData, - DWORD dwSize, - DWORD dwCompression - ); - -int SFileAddFile_Finish( - TMPQFile * hf - ); - -//----------------------------------------------------------------------------- -// Attributes support - -int SAttrLoadAttributes(TMPQArchive * ha); -int SAttrFileSaveToMpq(TMPQArchive * ha); - -//----------------------------------------------------------------------------- -// Listfile functions - -int SListFileSaveToMpq(TMPQArchive * ha); - -//----------------------------------------------------------------------------- -// Weak signature support - -int SSignFileCreate(TMPQArchive * ha); -int SSignFileFinish(TMPQArchive * ha); - -//----------------------------------------------------------------------------- -// Dump data support - -#ifdef __STORMLIB_DUMP_DATA__ - -void DumpMpqHeader(TMPQHeader * pHeader); -void DumpHashTable(TMPQHash * pHashTable, DWORD dwHashTableSize); -void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable); -void DumpFileTable(TFileEntry * pFileTable, DWORD dwFileTableSize); - -#else - -#define DumpMpqHeader(h) /* */ -#define DumpHashTable(t, s) /* */ -#define DumpHetAndBetTable(t, s) /* */ -#define DumpFileTable(t, s) /* */ - -#endif - -#endif // __STORMCOMMON_H__ - +/*****************************************************************************/
+/* SCommon.h Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Common functions for encryption/decryption from Storm.dll. Included by */
+/* SFile*** functions, do not include and do not use this file directly */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 24.03.03 1.00 Lad The first version of SFileCommon.h */
+/* 12.06.04 1.00 Lad Renamed to SCommon.h */
+/* 06.09.10 1.00 Lad Renamed to StormCommon.h */
+/*****************************************************************************/
+
+#ifndef __STORMCOMMON_H__
+#define __STORMCOMMON_H__
+
+//-----------------------------------------------------------------------------
+// Compression support
+
+// Include functions from Pkware Data Compression Library
+#include "pklib/pklib.h"
+
+// Include functions from Huffmann compression
+#include "huffman/huff.h"
+
+// Include functions from IMA ADPCM compression
+#include "adpcm/adpcm.h"
+
+// Include functions from SPARSE compression
+#include "sparse/sparse.h"
+
+// Include functions from LZMA compression
+#include "lzma/C/LzmaEnc.h"
+#include "lzma/C/LzmaDec.h"
+
+// Include functions from zlib
+#ifndef __SYS_ZLIB
+ #include "zlib/zlib.h"
+#else
+ #include <zlib.h>
+#endif
+
+// Include functions from bzlib
+#ifndef __SYS_BZLIB
+ #include "bzip2/bzlib.h"
+#else
+ #include <bzlib.h>
+#endif
+
+//-----------------------------------------------------------------------------
+// Cryptography support
+
+// Headers from LibTomCrypt
+#include "libtomcrypt/src/headers/tomcrypt.h"
+
+// For HashStringJenkins
+#include "jenkins/lookup.h"
+
+//-----------------------------------------------------------------------------
+// StormLib private defines
+
+#define ID_MPQ_FILE 0x46494c45 // Used internally for checking TMPQFile ('FILE')
+
+// Prevent problems with CRT "min" and "max" functions,
+// as they are not defined on all platforms
+#define STORMLIB_MIN(a, b) ((a < b) ? a : b)
+#define STORMLIB_MAX(a, b) ((a > b) ? a : b)
+#define STORMLIB_UNUSED(p) ((void)(p))
+
+// Macro for building 64-bit file offset from two 32-bit
+#define MAKE_OFFSET64(hi, lo) (((ULONGLONG)hi << 32) | (ULONGLONG)lo)
+
+//-----------------------------------------------------------------------------
+// MPQ signature information
+
+// Size of each signature type
+#define MPQ_WEAK_SIGNATURE_SIZE 64
+#define MPQ_STRONG_SIGNATURE_SIZE 256
+#define MPQ_STRONG_SIGNATURE_ID 0x5349474E // ID of the strong signature ("NGIS")
+#define MPQ_SIGNATURE_FILE_SIZE (MPQ_WEAK_SIGNATURE_SIZE + 8)
+
+// MPQ signature info
+typedef struct _MPQ_SIGNATURE_INFO
+{
+ ULONGLONG BeginMpqData; // File offset where the hashing starts
+ ULONGLONG BeginExclude; // Begin of the excluded area (used for (signature) file)
+ ULONGLONG EndExclude; // End of the excluded area (used for (signature) file)
+ ULONGLONG EndMpqData; // File offset where the hashing ends
+ ULONGLONG EndOfFile; // Size of the entire file
+ BYTE Signature[MPQ_STRONG_SIGNATURE_SIZE + 0x10];
+ DWORD cbSignatureSize; // Length of the signature
+ DWORD SignatureTypes; // See SIGNATURE_TYPE_XXX
+
+} MPQ_SIGNATURE_INFO, *PMPQ_SIGNATURE_INFO;
+
+//-----------------------------------------------------------------------------
+// Memory management
+//
+// We use our own macros for allocating/freeing memory. If you want
+// to redefine them, please keep the following rules:
+//
+// - The memory allocation must return NULL if not enough memory
+// (i.e not to throw exception)
+// - The allocating function does not need to fill the allocated buffer with zeros
+// - Memory freeing function doesn't have to test the pointer to NULL
+//
+
+//#if defined(_MSC_VER) && defined(_DEBUG)
+//
+//#define STORM_ALLOC(type, nitems) (type *)HeapAlloc(GetProcessHeap(), 0, ((nitems) * sizeof(type)))
+//#define STORM_REALLOC(type, ptr, nitems) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((nitems) * sizeof(type)))
+//#define STORM_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
+//
+//#else
+
+#define STORM_ALLOC(type, nitems) (type *)malloc((nitems) * sizeof(type))
+#define STORM_REALLOC(type, ptr, nitems) (type *)realloc(ptr, ((nitems) * sizeof(type)))
+#define STORM_FREE(ptr) free(ptr)
+
+//#endif
+
+//-----------------------------------------------------------------------------
+// StormLib internal global variables
+
+extern LCID lcFileLocale; // Preferred file locale
+
+//-----------------------------------------------------------------------------
+// Conversion to uppercase/lowercase (and "/" to "\")
+
+extern unsigned char AsciiToLowerTable[256];
+extern unsigned char AsciiToUpperTable[256];
+
+//-----------------------------------------------------------------------------
+// Safe string functions
+
+void StringCopyA(char * dest, const char * src, size_t nMaxChars);
+void StringCatA(char * dest, const char * src, size_t nMaxChars);
+
+void StringCopyT(TCHAR * dest, const TCHAR * src, size_t nMaxChars);
+void StringCatT(TCHAR * dest, const TCHAR * src, size_t nMaxChars);
+
+//-----------------------------------------------------------------------------
+// Encryption and decryption functions
+
+#define MPQ_HASH_TABLE_INDEX 0x000
+#define MPQ_HASH_NAME_A 0x100
+#define MPQ_HASH_NAME_B 0x200
+#define MPQ_HASH_FILE_KEY 0x300
+#define MPQ_HASH_KEY2_MIX 0x400
+
+DWORD HashString(const char * szFileName, DWORD dwHashType);
+DWORD HashStringSlash(const char * szFileName, DWORD dwHashType);
+DWORD HashStringLower(const char * szFileName, DWORD dwHashType);
+
+void InitializeMpqCryptography();
+
+DWORD GetHashTableSizeForFileCount(DWORD dwFileCount);
+
+bool IsPseudoFileName(const char * szFileName, LPDWORD pdwFileIndex);
+ULONGLONG HashStringJenkins(const char * szFileName);
+
+DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion);
+
+void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey);
+void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey);
+
+DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwSectorOffsLen);
+DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize);
+DWORD DecryptFileKey(const char * szFileName, ULONGLONG MpqPos, DWORD dwFileSize, DWORD dwFlags);
+
+bool IsValidMD5(LPBYTE pbMd5);
+bool IsValidSignature(LPBYTE pbSignature);
+bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5);
+void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash);
+
+//-----------------------------------------------------------------------------
+// Handle validation functions
+
+TMPQArchive * IsValidMpqHandle(HANDLE hMpq);
+TMPQFile * IsValidFileHandle(HANDLE hFile);
+
+//-----------------------------------------------------------------------------
+// Support for MPQ file tables
+
+ULONGLONG FileOffsetFromMpqOffset(TMPQArchive * ha, ULONGLONG MpqOffset);
+ULONGLONG CalculateRawSectorOffset(TMPQFile * hf, DWORD dwSectorOffset);
+
+int ConvertMpqHeaderToFormat4(TMPQArchive * ha, ULONGLONG MpqOffset, ULONGLONG FileSize, DWORD dwFlags);
+
+bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash);
+
+TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale);
+TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName);
+TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pPrevHash);
+TMPQHash * AllocateHashEntry(TMPQArchive * ha, TFileEntry * pFileEntry, LCID lcLocale);
+
+TMPQExtHeader * LoadExtTable(TMPQArchive * ha, ULONGLONG ByteOffset, size_t Size, DWORD dwSignature, DWORD dwKey);
+TMPQHetTable * LoadHetTable(TMPQArchive * ha);
+TMPQBetTable * LoadBetTable(TMPQArchive * ha);
+
+TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool bDontFixEntries = false);
+TMPQBlock * TranslateBlockTable(TMPQArchive * ha, ULONGLONG * pcbTableSize, bool * pbNeedHiBlockTable);
+
+ULONGLONG FindFreeMpqSpace(TMPQArchive * ha);
+
+// Functions that load the HET and BET tables
+int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize);
+int LoadAnyHashTable(TMPQArchive * ha);
+int BuildFileTable(TMPQArchive * ha);
+int DefragmentFileTable(TMPQArchive * ha);
+
+int CreateFileTable(TMPQArchive * ha, DWORD dwFileTableSize);
+int RebuildHetTable(TMPQArchive * ha);
+int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize);
+int SaveMPQTables(TMPQArchive * ha);
+
+TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwHashBitSize, LPBYTE pbSrcData);
+void FreeHetTable(TMPQHetTable * pHetTable);
+
+TMPQBetTable * CreateBetTable(DWORD dwMaxFileCount);
+void FreeBetTable(TMPQBetTable * pBetTable);
+
+// Functions for finding files in the file table
+TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex);
+TFileEntry * GetFileEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale);
+TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex);
+
+// Allocates file name in the file entry
+void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName);
+
+// Allocates new file entry in the MPQ tables. Reuses existing, if possible
+TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex);
+int RenameFileEntry(TMPQArchive * ha, TMPQFile * hf, const char * szNewFileName);
+int DeleteFileEntry(TMPQArchive * ha, TMPQFile * hf);
+
+// Invalidates entries for (listfile) and (attributes)
+void InvalidateInternalFiles(TMPQArchive * ha);
+
+// Retrieves information about the strong signature
+bool QueryMpqSignatureInfo(TMPQArchive * ha, PMPQ_SIGNATURE_INFO pSignatureInfo);
+
+//-----------------------------------------------------------------------------
+// Support for alternate file formats (SBaseSubTypes.cpp)
+
+int ConvertSqpHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags);
+TMPQHash * LoadSqpHashTable(TMPQArchive * ha);
+TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha);
+
+int ConvertMpkHeaderToFormat4(TMPQArchive * ha, ULONGLONG FileSize, DWORD dwFlags);
+void DecryptMpkTable(void * pvMpkTable, size_t cbSize);
+TMPQHash * LoadMpkHashTable(TMPQArchive * ha);
+TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha);
+int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+
+//-----------------------------------------------------------------------------
+// Common functions - MPQ File
+
+TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry);
+TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize);
+void * LoadMpqTable(TMPQArchive * ha, ULONGLONG ByteOffset, DWORD dwCompressedSize, DWORD dwRealSize, DWORD dwKey, bool * pbTableIsCut);
+int AllocateSectorBuffer(TMPQFile * hf);
+int AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile);
+int AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile);
+int AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile);
+int WritePatchInfo(TMPQFile * hf);
+int WriteSectorOffsets(TMPQFile * hf);
+int WriteSectorChecksums(TMPQFile * hf);
+int WriteMemDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, void * pvRawData, DWORD dwRawDataSize, DWORD dwChunkSize, LPDWORD pcbTotalSize);
+int WriteMpqDataMD5(TFileStream * pStream, ULONGLONG RawDataOffs, DWORD dwRawDataSize, DWORD dwChunkSize);
+void FreeFileHandle(TMPQFile *& hf);
+void FreeArchiveHandle(TMPQArchive *& ha);
+
+//-----------------------------------------------------------------------------
+// Patch functions
+
+// Structure used for the patching process
+typedef struct _TMPQPatcher
+{
+ BYTE this_md5[MD5_DIGEST_SIZE]; // MD5 of the current file state
+ LPBYTE pbFileData1; // Primary working buffer
+ LPBYTE pbFileData2; // Secondary working buffer
+ DWORD cbMaxFileData; // Maximum allowed size of the patch data
+ DWORD cbFileData; // Current size of the result data
+ DWORD nCounter; // Counter of the patch process
+
+} TMPQPatcher;
+
+bool IsIncrementalPatchFile(const void * pvData, DWORD cbData, LPDWORD pdwPatchedFileSize);
+int Patch_InitPatcher(TMPQPatcher * pPatcher, TMPQFile * hf);
+int Patch_Process(TMPQPatcher * pPatcher, TMPQFile * hf);
+void Patch_Finalize(TMPQPatcher * pPatcher);
+
+//-----------------------------------------------------------------------------
+// Utility functions
+
+bool CheckWildCard(const char * szString, const char * szWildCard);
+bool IsInternalMpqFileName(const char * szFileName);
+
+const TCHAR * GetPlainFileName(const TCHAR * szFileName);
+const char * GetPlainFileName(const char * szFileName);
+
+void CopyFileName(TCHAR * szTarget, const char * szSource, size_t cchLength);
+void CopyFileName(char * szTarget, const TCHAR * szSource, size_t cchLength);
+
+//-----------------------------------------------------------------------------
+// Internal support for MPQ modifications
+
+int SFileAddFile_Init(
+ TMPQArchive * ha,
+ const char * szArchivedName,
+ ULONGLONG ft,
+ DWORD dwFileSize,
+ LCID lcLocale,
+ DWORD dwFlags,
+ TMPQFile ** phf
+ );
+
+int SFileAddFile_Init(
+ TMPQArchive * ha,
+ TMPQFile * hfSrc,
+ TMPQFile ** phf
+ );
+
+int SFileAddFile_Write(
+ TMPQFile * hf,
+ const void * pvData,
+ DWORD dwSize,
+ DWORD dwCompression
+ );
+
+int SFileAddFile_Finish(
+ TMPQFile * hf
+ );
+
+//-----------------------------------------------------------------------------
+// Attributes support
+
+int SAttrLoadAttributes(TMPQArchive * ha);
+int SAttrFileSaveToMpq(TMPQArchive * ha);
+
+//-----------------------------------------------------------------------------
+// Listfile functions
+
+int SListFileSaveToMpq(TMPQArchive * ha);
+
+//-----------------------------------------------------------------------------
+// Weak signature support
+
+int SSignFileCreate(TMPQArchive * ha);
+int SSignFileFinish(TMPQArchive * ha);
+
+//-----------------------------------------------------------------------------
+// Dump data support
+
+#ifdef __STORMLIB_DUMP_DATA__
+
+void DumpMpqHeader(TMPQHeader * pHeader);
+void DumpHashTable(TMPQHash * pHashTable, DWORD dwHashTableSize);
+void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable);
+void DumpFileTable(TFileEntry * pFileTable, DWORD dwFileTableSize);
+
+#else
+
+#define DumpMpqHeader(h) /* */
+#define DumpHashTable(t, s) /* */
+#define DumpHetAndBetTable(t, s) /* */
+#define DumpFileTable(t, s) /* */
+
+#endif
+
+#endif // __STORMCOMMON_H__
+
diff --git a/src/StormPort.h b/src/StormPort.h index 41b4307..154d573 100644 --- a/src/StormPort.h +++ b/src/StormPort.h @@ -1,290 +1,290 @@ -/*****************************************************************************/ -/* StormPort.h Copyright (c) Marko Friedemann 2001 */ -/*---------------------------------------------------------------------------*/ -/* Portability module for the StormLib library. Contains a wrapper symbols */ -/* to make the compilation under Linux work */ -/* */ -/* Author: Marko Friedemann <marko.friedemann@bmx-chemnitz.de> */ -/* Created at: Mon Jan 29 18:26:01 CEST 2001 */ -/* Computer: whiplash.flachland-chemnitz.de */ -/* System: Linux 2.4.0 on i686 */ -/* */ -/* Author: Sam Wilkins <swilkins1337@gmail.com> */ -/* System: Mac OS X and port to big endian processor */ -/* */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 29.01.01 1.00 Mar Created */ -/* 24.03.03 1.01 Lad Some cosmetic changes */ -/* 12.11.03 1.02 Dan Macintosh compatibility */ -/* 24.07.04 1.03 Sam Mac OS X compatibility */ -/* 22.11.06 1.04 Sam Mac OS X compatibility (for StormLib 6.0) */ -/* 31.12.06 1.05 XPinguin Full GNU/Linux compatibility */ -/* 17.10.12 1.05 Lad Moved error codes so they don't overlap with errno.h */ -/*****************************************************************************/ - -#ifndef __STORMPORT_H__ -#define __STORMPORT_H__ - -#ifndef __cplusplus - #define bool char - #define true 1 - #define false 0 -#endif - -//----------------------------------------------------------------------------- -// Defines for Windows - -#if !defined(PLATFORM_DEFINED) && defined(_WIN32) - - // In MSVC 8.0, there are some functions declared as deprecated. - #if _MSC_VER >= 1400 - #define _CRT_SECURE_NO_DEPRECATE - #define _CRT_NON_CONFORMING_SWPRINTFS - #endif - - #include <tchar.h> - #include <assert.h> - #include <ctype.h> - #include <stdio.h> - #include <windows.h> - #include <wininet.h> - #define PLATFORM_LITTLE_ENDIAN - - #ifdef _WIN64 - #define PLATFORM_64BIT - #else - #define PLATFORM_32BIT - #endif - - #define PLATFORM_WINDOWS - #define PLATFORM_DEFINED // The platform is known now - -#endif - -//----------------------------------------------------------------------------- -// Defines for Mac - -#if !defined(PLATFORM_DEFINED) && defined(__APPLE__) // Mac BSD API - - // 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> - - // Support for PowerPC on Max OS X - #if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1) - #include <stdint.h> - #include <CoreFoundation/CFByteOrder.h> - #endif - - #define PKEXPORT - #define __SYS_ZLIB - #define __SYS_BZLIB - - #ifndef __BIG_ENDIAN__ - #define PLATFORM_LITTLE_ENDIAN - #endif - - #define PLATFORM_MAC - #define PLATFORM_DEFINED // The platform is known now - -#endif - -//----------------------------------------------------------------------------- -// Assumption: we are not on Windows nor Macintosh, so this must be linux *grin* - -#if !defined(PLATFORM_DEFINED) - - #include <sys/types.h> - #include <sys/stat.h> - #include <sys/mman.h> - #include <fcntl.h> - #include <unistd.h> - #include <stdint.h> - #include <stdlib.h> - #include <stdio.h> - #include <stdarg.h> - #include <string.h> - #include <ctype.h> - #include <assert.h> - #include <errno.h> - - #define PLATFORM_LITTLE_ENDIAN - #define PLATFORM_LINUX - #define PLATFORM_DEFINED - -#endif - -//----------------------------------------------------------------------------- -// Definition of Windows-specific types for non-Windows platforms - -#ifndef PLATFORM_WINDOWS - #if __LP64__ - #define PLATFORM_64BIT - #else - #define PLATFORM_32BIT - #endif - - // Typedefs for ANSI C - typedef unsigned char BYTE; - typedef unsigned short USHORT; - typedef int LONG; - typedef unsigned int DWORD; - typedef unsigned long DWORD_PTR; - typedef long LONG_PTR; - typedef long INT_PTR; - typedef long long LONGLONG; - typedef unsigned long long ULONGLONG; - typedef void * HANDLE; - typedef void * LPOVERLAPPED; // Unsupported on Linux and Mac - typedef char TCHAR; - typedef unsigned int LCID; - typedef LONG * PLONG; - typedef DWORD * LPDWORD; - typedef BYTE * LPBYTE; - - #ifdef PLATFORM_32BIT - #define _LZMA_UINT32_IS_ULONG - #endif - - // Some Windows-specific defines - #ifndef MAX_PATH - #define MAX_PATH 1024 - #endif - - #define WINAPI - - #define FILE_BEGIN SEEK_SET - #define FILE_CURRENT SEEK_CUR - #define FILE_END SEEK_END - - #define _T(x) x - #define _tcslen strlen - #define _tcscpy strcpy - #define _tcscat strcat - #define _tcschr strchr - #define _tcsrchr strrchr - #define _tcsstr strstr - #define _tprintf printf - #define _stprintf sprintf - #define _tremove remove - - #define _stricmp strcasecmp - #define _strnicmp strncasecmp - #define _tcsicmp strcasecmp - #define _tcsnicmp strncasecmp - -#endif // !PLATFORM_WINDOWS - -// 64-bit calls are supplied by "normal" calls on Mac -#if defined(PLATFORM_MAC) - #define stat64 stat - #define fstat64 fstat - #define lseek64 lseek - #define ftruncate64 ftruncate - #define off64_t off_t - #define O_LARGEFILE 0 -#endif - -// 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 - #define ERROR_INVALID_HANDLE EBADF - #define ERROR_NOT_ENOUGH_MEMORY ENOMEM - #define ERROR_NOT_SUPPORTED ENOTSUP - #define ERROR_INVALID_PARAMETER EINVAL - #define ERROR_DISK_FULL ENOSPC - #define ERROR_ALREADY_EXISTS EEXIST - #define ERROR_INSUFFICIENT_BUFFER ENOBUFS - #define ERROR_BAD_FORMAT 1000 // No such error code under Linux - #define ERROR_NO_MORE_FILES 1001 // No such error code under Linux - #define ERROR_HANDLE_EOF 1002 // No such error code under Linux - #define ERROR_CAN_NOT_COMPLETE 1003 // No such error code under Linux - #define ERROR_FILE_CORRUPT 1004 // No such error code under Linux -#endif - -//----------------------------------------------------------------------------- -// Swapping functions - -#ifdef PLATFORM_LITTLE_ENDIAN - #define BSWAP_INT16_UNSIGNED(a) (a) - #define BSWAP_INT16_SIGNED(a) (a) - #define BSWAP_INT32_UNSIGNED(a) (a) - #define BSWAP_INT32_SIGNED(a) (a) - #define BSWAP_INT64_SIGNED(a) (a) - #define BSWAP_INT64_UNSIGNED(a) (a) - #define BSWAP_ARRAY16_UNSIGNED(a,b) {} - #define BSWAP_ARRAY32_UNSIGNED(a,b) {} - #define BSWAP_ARRAY64_UNSIGNED(a,b) {} - #define BSWAP_PART_HEADER(a) {} - #define BSWAP_TMPQHEADER(a,b) {} - #define BSWAP_TMPKHEADER(a) {} -#else - -#ifdef __cplusplus - extern "C" { -#endif - int16_t SwapInt16(uint16_t); - uint16_t SwapUInt16(uint16_t); - int32_t SwapInt32(uint32_t); - uint32_t SwapUInt32(uint32_t); - int64_t SwapInt64(uint64_t); - uint64_t SwapUInt64(uint64_t); - void ConvertUInt16Buffer(void * ptr, size_t length); - void ConvertUInt32Buffer(void * ptr, size_t length); - void ConvertUInt64Buffer(void * ptr, size_t length); - void ConvertTMPQUserData(void *userData); - void ConvertTMPQHeader(void *header, uint16_t wPart); - void ConvertTMPKHeader(void *header); -#ifdef __cplusplus - } -#endif - #define BSWAP_INT16_SIGNED(a) SwapInt16((a)) - #define BSWAP_INT16_UNSIGNED(a) SwapUInt16((a)) - #define BSWAP_INT32_SIGNED(a) SwapInt32((a)) - #define BSWAP_INT32_UNSIGNED(a) SwapUInt32((a)) - #define BSWAP_INT64_SIGNED(a) SwapInt64((a)) - #define BSWAP_INT64_UNSIGNED(a) SwapUInt64((a)) - #define BSWAP_ARRAY16_UNSIGNED(a,b) ConvertUInt16Buffer((a),(b)) - #define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b)) - #define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b)) - #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b)) - #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a)) -#endif - -//----------------------------------------------------------------------------- -// Macro for deprecated symbols - -/* -#ifdef _MSC_VER - #if _MSC_FULL_VER >= 140050320 - #define STORMLIB_DEPRECATED(_Text) __declspec(deprecated(_Text)) - #else - #define STORMLIB_DEPRECATED(_Text) __declspec(deprecated) - #endif -#else - #ifdef __GNUC__ - #define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated)) - #else - #define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated(_Text))) - #endif -#endif - -// When a flag is deprecated, use this macro -#ifndef _STORMLIB_NO_DEPRECATE - #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) \ - const STORMLIB_DEPRECATED(#oldflag " is deprecated. Use " #newflag ". To supress this warning, define _STORMLIB_NO_DEPRECATE") static type oldflag = (type)newflag; -#else -#define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) static type oldflag = (type)newflag; -#endif -*/ - -#endif // __STORMPORT_H__ +/*****************************************************************************/
+/* StormPort.h Copyright (c) Marko Friedemann 2001 */
+/*---------------------------------------------------------------------------*/
+/* Portability module for the StormLib library. Contains a wrapper symbols */
+/* to make the compilation under Linux work */
+/* */
+/* Author: Marko Friedemann <marko.friedemann@bmx-chemnitz.de> */
+/* Created at: Mon Jan 29 18:26:01 CEST 2001 */
+/* Computer: whiplash.flachland-chemnitz.de */
+/* System: Linux 2.4.0 on i686 */
+/* */
+/* Author: Sam Wilkins <swilkins1337@gmail.com> */
+/* System: Mac OS X and port to big endian processor */
+/* */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.01.01 1.00 Mar Created */
+/* 24.03.03 1.01 Lad Some cosmetic changes */
+/* 12.11.03 1.02 Dan Macintosh compatibility */
+/* 24.07.04 1.03 Sam Mac OS X compatibility */
+/* 22.11.06 1.04 Sam Mac OS X compatibility (for StormLib 6.0) */
+/* 31.12.06 1.05 XPinguin Full GNU/Linux compatibility */
+/* 17.10.12 1.05 Lad Moved error codes so they don't overlap with errno.h */
+/*****************************************************************************/
+
+#ifndef __STORMPORT_H__
+#define __STORMPORT_H__
+
+#ifndef __cplusplus
+ #define bool char
+ #define true 1
+ #define false 0
+#endif
+
+//-----------------------------------------------------------------------------
+// Defines for Windows
+
+#if !defined(PLATFORM_DEFINED) && defined(_WIN32)
+
+ // In MSVC 8.0, there are some functions declared as deprecated.
+ #if _MSC_VER >= 1400
+ #define _CRT_SECURE_NO_DEPRECATE
+ #define _CRT_NON_CONFORMING_SWPRINTFS
+ #endif
+
+ #include <tchar.h>
+ #include <assert.h>
+ #include <ctype.h>
+ #include <stdio.h>
+ #include <windows.h>
+ #include <wininet.h>
+ #define PLATFORM_LITTLE_ENDIAN
+
+ #ifdef _WIN64
+ #define PLATFORM_64BIT
+ #else
+ #define PLATFORM_32BIT
+ #endif
+
+ #define PLATFORM_WINDOWS
+ #define PLATFORM_DEFINED // The platform is known now
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Defines for Mac
+
+#if !defined(PLATFORM_DEFINED) && defined(__APPLE__) // Mac BSD API
+
+ // 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>
+
+ // Support for PowerPC on Max OS X
+ #if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1)
+ #include <stdint.h>
+ #include <CoreFoundation/CFByteOrder.h>
+ #endif
+
+ #define PKEXPORT
+ #define __SYS_ZLIB
+ #define __SYS_BZLIB
+
+ #ifndef __BIG_ENDIAN__
+ #define PLATFORM_LITTLE_ENDIAN
+ #endif
+
+ #define PLATFORM_MAC
+ #define PLATFORM_DEFINED // The platform is known now
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Assumption: we are not on Windows nor Macintosh, so this must be linux *grin*
+
+#if !defined(PLATFORM_DEFINED)
+
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <sys/mman.h>
+ #include <fcntl.h>
+ #include <unistd.h>
+ #include <stdint.h>
+ #include <stdlib.h>
+ #include <stdio.h>
+ #include <stdarg.h>
+ #include <string.h>
+ #include <ctype.h>
+ #include <assert.h>
+ #include <errno.h>
+
+ #define PLATFORM_LITTLE_ENDIAN
+ #define PLATFORM_LINUX
+ #define PLATFORM_DEFINED
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Definition of Windows-specific types for non-Windows platforms
+
+#ifndef PLATFORM_WINDOWS
+ #if __LP64__
+ #define PLATFORM_64BIT
+ #else
+ #define PLATFORM_32BIT
+ #endif
+
+ // Typedefs for ANSI C
+ typedef unsigned char BYTE;
+ typedef unsigned short USHORT;
+ typedef int LONG;
+ typedef unsigned int DWORD;
+ typedef unsigned long DWORD_PTR;
+ typedef long LONG_PTR;
+ typedef long INT_PTR;
+ typedef long long LONGLONG;
+ typedef unsigned long long ULONGLONG;
+ typedef void * HANDLE;
+ typedef void * LPOVERLAPPED; // Unsupported on Linux and Mac
+ typedef char TCHAR;
+ typedef unsigned int LCID;
+ typedef LONG * PLONG;
+ typedef DWORD * LPDWORD;
+ typedef BYTE * LPBYTE;
+
+ #ifdef PLATFORM_32BIT
+ #define _LZMA_UINT32_IS_ULONG
+ #endif
+
+ // Some Windows-specific defines
+ #ifndef MAX_PATH
+ #define MAX_PATH 1024
+ #endif
+
+ #define WINAPI
+
+ #define FILE_BEGIN SEEK_SET
+ #define FILE_CURRENT SEEK_CUR
+ #define FILE_END SEEK_END
+
+ #define _T(x) x
+ #define _tcslen strlen
+ #define _tcscpy strcpy
+ #define _tcscat strcat
+ #define _tcschr strchr
+ #define _tcsrchr strrchr
+ #define _tcsstr strstr
+ #define _tprintf printf
+ #define _stprintf sprintf
+ #define _tremove remove
+
+ #define _stricmp strcasecmp
+ #define _strnicmp strncasecmp
+ #define _tcsicmp strcasecmp
+ #define _tcsnicmp strncasecmp
+
+#endif // !PLATFORM_WINDOWS
+
+// 64-bit calls are supplied by "normal" calls on Mac
+#if defined(PLATFORM_MAC)
+ #define stat64 stat
+ #define fstat64 fstat
+ #define lseek64 lseek
+ #define ftruncate64 ftruncate
+ #define off64_t off_t
+ #define O_LARGEFILE 0
+#endif
+
+// 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
+ #define ERROR_INVALID_HANDLE EBADF
+ #define ERROR_NOT_ENOUGH_MEMORY ENOMEM
+ #define ERROR_NOT_SUPPORTED ENOTSUP
+ #define ERROR_INVALID_PARAMETER EINVAL
+ #define ERROR_DISK_FULL ENOSPC
+ #define ERROR_ALREADY_EXISTS EEXIST
+ #define ERROR_INSUFFICIENT_BUFFER ENOBUFS
+ #define ERROR_BAD_FORMAT 1000 // No such error code under Linux
+ #define ERROR_NO_MORE_FILES 1001 // No such error code under Linux
+ #define ERROR_HANDLE_EOF 1002 // No such error code under Linux
+ #define ERROR_CAN_NOT_COMPLETE 1003 // No such error code under Linux
+ #define ERROR_FILE_CORRUPT 1004 // No such error code under Linux
+#endif
+
+//-----------------------------------------------------------------------------
+// Swapping functions
+
+#ifdef PLATFORM_LITTLE_ENDIAN
+ #define BSWAP_INT16_UNSIGNED(a) (a)
+ #define BSWAP_INT16_SIGNED(a) (a)
+ #define BSWAP_INT32_UNSIGNED(a) (a)
+ #define BSWAP_INT32_SIGNED(a) (a)
+ #define BSWAP_INT64_SIGNED(a) (a)
+ #define BSWAP_INT64_UNSIGNED(a) (a)
+ #define BSWAP_ARRAY16_UNSIGNED(a,b) {}
+ #define BSWAP_ARRAY32_UNSIGNED(a,b) {}
+ #define BSWAP_ARRAY64_UNSIGNED(a,b) {}
+ #define BSWAP_PART_HEADER(a) {}
+ #define BSWAP_TMPQHEADER(a,b) {}
+ #define BSWAP_TMPKHEADER(a) {}
+#else
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+ int16_t SwapInt16(uint16_t);
+ uint16_t SwapUInt16(uint16_t);
+ int32_t SwapInt32(uint32_t);
+ uint32_t SwapUInt32(uint32_t);
+ int64_t SwapInt64(uint64_t);
+ uint64_t SwapUInt64(uint64_t);
+ void ConvertUInt16Buffer(void * ptr, size_t length);
+ void ConvertUInt32Buffer(void * ptr, size_t length);
+ void ConvertUInt64Buffer(void * ptr, size_t length);
+ void ConvertTMPQUserData(void *userData);
+ void ConvertTMPQHeader(void *header, uint16_t wPart);
+ void ConvertTMPKHeader(void *header);
+#ifdef __cplusplus
+ }
+#endif
+ #define BSWAP_INT16_SIGNED(a) SwapInt16((a))
+ #define BSWAP_INT16_UNSIGNED(a) SwapUInt16((a))
+ #define BSWAP_INT32_SIGNED(a) SwapInt32((a))
+ #define BSWAP_INT32_UNSIGNED(a) SwapUInt32((a))
+ #define BSWAP_INT64_SIGNED(a) SwapInt64((a))
+ #define BSWAP_INT64_UNSIGNED(a) SwapUInt64((a))
+ #define BSWAP_ARRAY16_UNSIGNED(a,b) ConvertUInt16Buffer((a),(b))
+ #define BSWAP_ARRAY32_UNSIGNED(a,b) ConvertUInt32Buffer((a),(b))
+ #define BSWAP_ARRAY64_UNSIGNED(a,b) ConvertUInt64Buffer((a),(b))
+ #define BSWAP_TMPQHEADER(a,b) ConvertTMPQHeader((a),(b))
+ #define BSWAP_TMPKHEADER(a) ConvertTMPKHeader((a))
+#endif
+
+//-----------------------------------------------------------------------------
+// Macro for deprecated symbols
+
+/*
+#ifdef _MSC_VER
+ #if _MSC_FULL_VER >= 140050320
+ #define STORMLIB_DEPRECATED(_Text) __declspec(deprecated(_Text))
+ #else
+ #define STORMLIB_DEPRECATED(_Text) __declspec(deprecated)
+ #endif
+#else
+ #ifdef __GNUC__
+ #define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated))
+ #else
+ #define STORMLIB_DEPRECATED(_Text) __attribute__((deprecated(_Text)))
+ #endif
+#endif
+
+// When a flag is deprecated, use this macro
+#ifndef _STORMLIB_NO_DEPRECATE
+ #define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) \
+ const STORMLIB_DEPRECATED(#oldflag " is deprecated. Use " #newflag ". To supress this warning, define _STORMLIB_NO_DEPRECATE") static type oldflag = (type)newflag;
+#else
+#define STORMLIB_DEPRECATED_FLAG(type, oldflag, newflag) static type oldflag = (type)newflag;
+#endif
+*/
+
+#endif // __STORMPORT_H__
|