aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE42
-rw-r--r--src/FileStream.cpp5780
-rw-r--r--src/SBaseCommon.cpp3548
-rw-r--r--src/SFileAddFile.cpp2626
-rw-r--r--src/SFileAttributes.cpp1140
-rw-r--r--src/SFileCompactArchive.cpp1308
-rw-r--r--src/SFileFindFile.cpp960
-rw-r--r--src/SFileGetFileInfo.cpp1984
-rw-r--r--src/SFileListFile.cpp1284
-rw-r--r--src/SFilePatchArchives.cpp1874
-rw-r--r--src/SFileReadFile.cpp4
-rw-r--r--src/SFileVerify.cpp2108
-rw-r--r--src/StormCommon.h744
-rw-r--r--src/StormPort.h580
-rw-r--r--test/StormTest.cpp8878
-rw-r--r--test/TLogHelper.cpp842
16 files changed, 16847 insertions, 16855 deletions
diff --git a/LICENSE b/LICENSE
index 136cae4..2cb432d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
-The MIT License (MIT)
-
-Copyright (c) 1999-2013 Ladislav Zezula
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+The MIT License (MIT)
+
+Copyright (c) 1999-2013 Ladislav Zezula
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
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__
diff --git a/test/StormTest.cpp b/test/StormTest.cpp
index 192c5d4..932458e 100644
--- a/test/StormTest.cpp
+++ b/test/StormTest.cpp
@@ -1,4439 +1,4439 @@
-/*****************************************************************************/
-/* StormTest.cpp Copyright (c) Ladislav Zezula 2003 */
-/*---------------------------------------------------------------------------*/
-/* Test module for StormLib */
-/*---------------------------------------------------------------------------*/
-/* Date Ver Who Comment */
-/* -------- ---- --- ------- */
-/* 25.03.03 1.00 Lad The first version of StormTest.cpp */
-/*****************************************************************************/
-
-#define _CRT_NON_CONFORMING_SWPRINTFS
-#define _CRT_SECURE_NO_DEPRECATE
-#define __INCLUDE_CRYPTOGRAPHY__
-#define __STORMLIB_SELF__ // Don't use StormLib.lib
-#include <stdio.h>
-
-#ifdef _MSC_VER
-#include <crtdbg.h>
-#endif
-
-#include "../src/StormLib.h"
-#include "../src/StormCommon.h"
-
-#include "TLogHelper.cpp" // Helper class for showing test results
-
-#ifdef _MSC_VER
-#pragma warning(disable: 4505) // 'XXX' : unreferenced local function has been removed
-#pragma comment(lib, "winmm.lib")
-#endif
-
-#ifdef PLATFORM_LINUX
-#include <dirent.h>
-#endif
-
-//------------------------------------------------------------------------------
-// Defines
-
-#ifdef PLATFORM_WINDOWS
-#define WORK_PATH_ROOT "E:\\Multimedia\\MPQs"
-#endif
-
-#ifdef PLATFORM_LINUX
-#define WORK_PATH_ROOT "/home/ladik/MPQs"
-#endif
-
-#ifdef PLATFORM_MAC
-#define WORK_PATH_ROOT "/Users/sam/StormLib/test"
-#endif
-
-// Global for the work MPQ
-static const char * szMpqSubDir = "1995 - Test MPQs";
-static const char * szMpqPatchDir = "1995 - Test MPQs\\patches";
-
-typedef int (*FIND_FILE_CALLBACK)(const char * szFullPath);
-typedef int (*FIND_PAIR_CALLBACK)(const char * szFullPath1, const char * szFullPath2);
-
-#define ERROR_UNDETERMINED_RESULT 0xC000FFFF
-
-//-----------------------------------------------------------------------------
-// Testing data
-
-static DWORD AddFlags[] =
-{
-// Compression Encryption Fixed key Single Unit Sector CRC
- 0 | 0 | 0 | 0 | 0,
- 0 | MPQ_FILE_ENCRYPTED | 0 | 0 | 0,
- 0 | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0,
- 0 | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0,
- 0 | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0,
- 0 | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_IMPLODE | 0 | 0 | 0 | 0,
- MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | 0 | 0,
- MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0,
- MPQ_FILE_IMPLODE | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_IMPLODE | 0 | 0 | 0 | MPQ_FILE_SECTOR_CRC,
- MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | 0 | MPQ_FILE_SECTOR_CRC,
- MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | MPQ_FILE_SECTOR_CRC,
- MPQ_FILE_COMPRESS | 0 | 0 | 0 | 0,
- MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | 0 | 0,
- MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0,
- MPQ_FILE_COMPRESS | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0,
- MPQ_FILE_COMPRESS | 0 | 0 | 0 | MPQ_FILE_SECTOR_CRC,
- MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | 0 | MPQ_FILE_SECTOR_CRC,
- MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | MPQ_FILE_SECTOR_CRC,
- 0xFFFFFFFF
-};
-
-static DWORD WaveCompressions[] =
-{
- MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN,
- MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN,
- MPQ_COMPRESSION_PKWARE,
- MPQ_COMPRESSION_ZLIB,
- MPQ_COMPRESSION_BZIP2
-};
-
-static const wchar_t szUnicodeName1[] = { // Czech
- 0x010C, 0x0065, 0x0073, 0x006B, 0x00FD, _T('.'), _T('m'), _T('p'), _T('q'), 0
-};
-
-static const wchar_t szUnicodeName2[] = { // Russian
- 0x0420, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, _T('.'), _T('m'), _T('p'), _T('q'), 0
-};
-
-static const wchar_t szUnicodeName3[] = { // Greek
- 0x03B5, 0x03BB, 0x03BB, 0x03B7, 0x03BD, 0x03B9, 0x03BA, 0x03AC, _T('.'), _T('m'), _T('p'), _T('q'), 0
-};
-
-static const wchar_t szUnicodeName4[] = { // Chinese
- 0x65E5, 0x672C, 0x8A9E, _T('.'), _T('m'), _T('p'), _T('q'), 0
-};
-
-static const wchar_t szUnicodeName5[] = { // Japanese
- 0x7B80, 0x4F53, 0x4E2D, 0x6587, _T('.'), _T('m'), _T('p'), _T('q'), 0
-};
-
-static const wchar_t szUnicodeName6[] = { // Arabic
- 0x0627, 0x0644, 0x0639, 0x0639, 0x0631, 0x0628, 0x064A, 0x0629, _T('.'), _T('m'), _T('p'), _T('q'), 0
-};
-
-static const char * PatchList_WoW_OldWorld13286[] =
-{
- "MPQ_2012_v4_OldWorld.MPQ",
- "wow-update-oldworld-13154.MPQ",
- "wow-update-oldworld-13286.MPQ",
- NULL
-};
-
-static const char * PatchList_WoW_15050[] =
-{
- "MPQ_2013_v4_world.MPQ",
- "wow-update-13164.MPQ",
- "wow-update-13205.MPQ",
- "wow-update-13287.MPQ",
- "wow-update-13329.MPQ",
- "wow-update-13596.MPQ",
- "wow-update-13623.MPQ",
- "wow-update-base-13914.MPQ",
- "wow-update-base-14007.MPQ",
- "wow-update-base-14333.MPQ",
- "wow-update-base-14480.MPQ",
- "wow-update-base-14545.MPQ",
- "wow-update-base-14946.MPQ",
- "wow-update-base-15005.MPQ",
- "wow-update-base-15050.MPQ",
- NULL
-};
-
-static const char * PatchList_WoW_16965[] =
-{
- "MPQ_2013_v4_locale-enGB.MPQ",
- "wow-update-enGB-16016.MPQ",
- "wow-update-enGB-16048.MPQ",
- "wow-update-enGB-16057.MPQ",
- "wow-update-enGB-16309.MPQ",
- "wow-update-enGB-16357.MPQ",
- "wow-update-enGB-16516.MPQ",
- "wow-update-enGB-16650.MPQ",
- "wow-update-enGB-16844.MPQ",
- "wow-update-enGB-16965.MPQ",
- NULL
-};
-
-static const char * PatchList_SC2_32283[] =
-{
- "MPQ_2013_v4_Base1.SC2Data",
- "s2-update-base-23258.MPQ",
- "s2-update-base-24540.MPQ",
- "s2-update-base-26147.MPQ",
- "s2-update-base-28522.MPQ",
- "s2-update-base-30508.MPQ",
- "s2-update-base-32283.MPQ",
- NULL
-};
-
-static const char * PatchList_SC2_34644[] =
-{
- "MPQ_2013_v4_Base1.SC2Data",
- "s2-update-base-23258.MPQ",
- "s2-update-base-24540.MPQ",
- "s2-update-base-26147.MPQ",
- "s2-update-base-28522.MPQ",
- "s2-update-base-32384.MPQ",
- "s2-update-base-34644.MPQ",
- NULL
-};
-
-static const char * PatchList_SC2_34644_Maps[] =
-{
- "MPQ_2013_v4_Base3.SC2Maps",
- "s2-update-base-23258.MPQ",
- "s2-update-base-24540.MPQ",
- "s2-update-base-26147.MPQ",
- "s2-update-base-28522.MPQ",
- "s2-update-base-32384.MPQ",
- "s2-update-base-34644.MPQ",
- NULL
-};
-
-static const char * PatchList_SC2_32283_enGB[] =
-{
- "MPQ_2013_v4_enGB.SC2Data",
- "s2-update-enGB-23258.MPQ",
- "s2-update-enGB-24540.MPQ",
- "s2-update-enGB-26147.MPQ",
- "s2-update-enGB-28522.MPQ",
- "s2-update-enGB-30508.MPQ",
- "s2-update-enGB-32283.MPQ",
- NULL
-};
-
-static const char * PatchList_HS_6898_enGB[] =
-{
- "MPQ_2014_v4_base-Win.MPQ",
- "hs-0-5314-Win-final.MPQ",
- "hs-5314-5435-Win-final.MPQ",
- "hs-5435-5506-Win-final.MPQ",
- "hs-5506-5834-Win-final.MPQ",
- "hs-5834-6024-Win-final.MPQ",
- "hs-6024-6141-Win-final.MPQ",
- "hs-6141-6187-Win-final.MPQ",
- "hs-6187-6284-Win-final.MPQ",
- "hs-6284-6485-Win-final.MPQ",
- "hs-6485-6898-Win-final.MPQ",
- NULL
-};
-
-//-----------------------------------------------------------------------------
-// Local file functions
-
-// Definition of the path separator
-#ifdef PLATFORM_WINDOWS
-#define PATH_SEPARATOR '\\' // Path separator for Windows platforms
-#else
-#define PATH_SEPARATOR '/' // Path separator for Windows platforms
-#endif
-
-// This must be the directory where our test MPQs are stored.
-// We also expect a subdirectory named
-static char szMpqDirectory[MAX_PATH+1];
-size_t cchMpqDirectory = 0;
-
-static bool IsFullPath(const char * szFileName)
-{
-#ifdef PLATFORM_WINDOWS
- if(('A' <= szFileName[0] && szFileName[0] <= 'Z') || ('a' <= szFileName[0] && szFileName[0] <= 'z'))
- {
- return (szFileName[1] == ':' && szFileName[2] == PATH_SEPARATOR);
- }
-#endif
-
- szFileName = szFileName;
- return false;
-}
-
-static bool IsMpqExtension(const char * szFileName)
-{
- const char * szExtension = strrchr(szFileName, '.');
-
- if(szExtension != NULL)
- {
- if(!_stricmp(szExtension, ".mpq"))
- return true;
- if(!_stricmp(szExtension, ".w3m"))
- return true;
- if(!_stricmp(szExtension, ".w3x"))
- return true;
- if(!_stricmp(szExtension, ".mpqe"))
- return true;
- if(!_stricmp(szExtension, ".part"))
- return true;
- if(!_stricmp(szExtension, ".sv"))
- return true;
- if(!_stricmp(szExtension, ".s2ma"))
- return true;
- if(!_stricmp(szExtension, ".SC2Map"))
- return true;
- if(!_stricmp(szExtension, ".0")) // .MPQ.0
- return true;
-// if(!_stricmp(szExtension, ".link"))
-// return true;
- }
-
- return false;
-}
-
-static void AddStringBeforeExtension(char * szBuffer, const char * szFileName, const char * szExtraString)
-{
- const char * szExtension;
- size_t nLength;
-
- // Get the extension
- szExtension = strrchr(szFileName, '.');
- if(szExtension == NULL)
- szExtension = szFileName + strlen(szFileName);
- nLength = (size_t)(szExtension - szFileName);
-
- // Copy the part before extension
- memcpy(szBuffer, szFileName, nLength);
- szFileName += nLength;
- szBuffer += nLength;
-
- // Append the extra data
- if(szExtraString != NULL)
- strcpy(szBuffer, szExtraString);
-
- // Append the rest of the file name
- strcat(szBuffer, szFileName);
-}
-
-static bool CompareBlocks(LPBYTE pbBlock1, LPBYTE pbBlock2, DWORD dwLength, DWORD * pdwDifference)
-{
- for(DWORD i = 0; i < dwLength; i++)
- {
- if(pbBlock1[i] != pbBlock2[i])
- {
- pdwDifference[0] = i;
- return false;
- }
- }
-
- return true;
-}
-
-static size_t ConvertSha1ToText(const unsigned char * sha1_digest, char * szSha1Text)
-{
- const char * szTable = "0123456789abcdef";
-
- for(size_t i = 0; i < SHA1_DIGEST_SIZE; i++)
- {
- *szSha1Text++ = szTable[(sha1_digest[0] >> 0x04)];
- *szSha1Text++ = szTable[(sha1_digest[0] & 0x0F)];
- sha1_digest++;
- }
-
- *szSha1Text = 0;
- return (SHA1_DIGEST_SIZE * 2);
-}
-
-static int GetPathSeparatorCount(const char * szPath)
-{
- int nSeparatorCount = 0;
-
- while(szPath[0] != 0)
- {
- if(szPath[0] == '\\' || szPath[0] == '/')
- nSeparatorCount++;
- szPath++;
- }
-
- return nSeparatorCount;
-}
-
-static const char * FindNextPathPart(const char * szPath, size_t nPartCount)
-{
- const char * szPathPart = szPath;
-
- while(szPath[0] != 0 && nPartCount > 0)
- {
- // Is there path separator?
- if(szPath[0] == '\\' || szPath[0] == '/')
- {
- szPathPart = szPath + 1;
- nPartCount--;
- }
-
- // Move to the next letter
- szPath++;
- }
-
- return szPathPart;
-}
-
-static const char * GetShortPlainName(const char * szFileName)
-{
- const char * szPlainName = FindNextPathPart(szFileName, 1000);
- const char * szPlainEnd = szFileName + strlen(szFileName);
-
- // If the name is still too long, cut it
- if((szPlainEnd - szPlainName) > 50)
- szPlainName = szPlainEnd - 50;
-
- return szPlainName;
-}
-
-static void CopyPathPart(char * szBuffer, const char * szPath)
-{
- while(szPath[0] != 0)
- {
- szBuffer[0] = (szPath[0] == '\\' || szPath[0] == '/') ? '/' : szPath[0];
- szBuffer++;
- szPath++;
- }
-
- *szBuffer = 0;
-}
-
-static bool CopyStringAndVerifyConversion(
- const TCHAR * szFoundFile,
- TCHAR * szBufferT,
- char * szBufferA)
-{
- // Convert the TCHAR name to ANSI name
- CopyFileName(szBufferA, szFoundFile, _tcslen(szFoundFile));
- CopyFileName(szBufferT, szBufferA, strlen(szBufferA));
-
- // Compare both TCHAR strings
- return (_tcsicmp(szBufferT, szFoundFile) == 0) ? true : false;
-}
-
-static void CalculateRelativePath(const char * szFullPath1, const char * szFullPath2, char * szBuffer)
-{
- const char * szPathPart1 = szFullPath1;
- const char * szPathPart2 = szFullPath2;
- const char * szNextPart1;
- const char * szNextPart2;
- int nEqualParts = 0;
- int nStepsUp = 0;
-
- // Parse both paths and find all path parts that are equal
- for(;;)
- {
- // Find the next part of the first path
- szNextPart1 = FindNextPathPart(szPathPart1, 1);
- if(szNextPart1 == szPathPart1)
- break;
-
- szNextPart2 = FindNextPathPart(szPathPart2, 1);
- if(szNextPart2 == szPathPart2)
- break;
-
- // Are these equal?
- if((szNextPart2 - szPathPart2) != (szNextPart1 - szPathPart1))
- break;
- if(_strnicmp(szPathPart1, szPathPart2, (szNextPart1 - szPathPart1 - 1)))
- break;
-
- // Increment the number of path parts that are equal
- szPathPart1 = szNextPart1;
- szPathPart2 = szNextPart2;
- nEqualParts++;
- }
-
- // If we found at least one equal part, we can create relative path
- if(nEqualParts != 0)
- {
- // Calculate how many steps up we need to go
- nStepsUp = GetPathSeparatorCount(szPathPart2);
-
- // Append "../" nStepsUp-times
- for(int i = 0; i < nStepsUp; i++)
- {
- *szBuffer++ = '.';
- *szBuffer++ = '.';
- *szBuffer++ = '/';
- }
-
- // Append the rest of the path. Also change DOS backslashes to slashes
- CopyPathPart(szBuffer, szPathPart1);
- return;
- }
-
- // Failed. Just copy the source path as it is
- strcpy(szBuffer, szFullPath1);
-}
-
-static TFileStream * FileStream_OpenFileA(const char * szFileName, DWORD dwStreamFlags)
-{
- TCHAR szFileNameT[MAX_PATH];
-
- CopyFileName(szFileNameT, szFileName, strlen(szFileName));
- return FileStream_OpenFile(szFileNameT, dwStreamFlags);
-}
-
-static TFileStream * FileStream_CreateFileA(const char * szFileName, DWORD dwStreamFlags)
-{
- TCHAR szFileNameT[MAX_PATH];
-
- CopyFileName(szFileNameT, szFileName, strlen(szFileName));
- return FileStream_CreateFile(szFileNameT, dwStreamFlags);
-}
-
-static size_t FileStream_PrefixA(const char * szFileName, DWORD * pdwProvider)
-{
- TCHAR szFileNameT[MAX_PATH];
- size_t nPrefixLength = 0;
-
- if(szFileName != NULL)
- {
- CopyFileName(szFileNameT, szFileName, strlen(szFileName));
- nPrefixLength = FileStream_Prefix(szFileNameT, pdwProvider);
- }
-
- return nPrefixLength;
-}
-
-static void CreateFullPathName(char * szBuffer, const char * szSubDir, const char * szNamePart1, const char * szNamePart2 = NULL)
-{
- char * szSaveBuffer = szBuffer;
- size_t nPrefixLength = 0;
- size_t nLength;
- DWORD dwProvider = 0;
- bool bIsFullPath = false;
- char chSeparator = PATH_SEPARATOR;
-
- // Determine the path prefix
- if(szNamePart1 != NULL)
- {
- nPrefixLength = FileStream_PrefixA(szNamePart1, &dwProvider);
- if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
- {
- bIsFullPath = true;
- chSeparator = '/';
- }
- else
- bIsFullPath = IsFullPath(szNamePart1 + nPrefixLength);
- }
-
- // Copy the MPQ prefix, if any
- if(nPrefixLength > 0)
- {
- memcpy(szBuffer, szNamePart1, nPrefixLength);
- szSaveBuffer += nPrefixLength;
- szNamePart1 += nPrefixLength;
- szBuffer += nPrefixLength;
- }
-
- // If the given name is not a full path, copy the MPQ directory
- if(bIsFullPath == false)
- {
- // Copy the master MPQ directory
- memcpy(szBuffer, szMpqDirectory, cchMpqDirectory);
- szBuffer += cchMpqDirectory;
-
- // Append the subdirectory, if any
- if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0)
- {
- // No leading or trailing separator are allowed
- assert(szSubDir[0] != '/' && szSubDir[0] != '\\');
- assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\');
-
- // Append file path separator
- *szBuffer++ = PATH_SEPARATOR;
-
- // Append the subdirectory
- memcpy(szBuffer, szSubDir, nLength);
- szBuffer += nLength;
- }
- }
-
- // Copy the file name, if any
- if(szNamePart1 != NULL && (nLength = strlen(szNamePart1)) != 0)
- {
- // Path separators are not allowed in the name part
- assert(szNamePart1[0] != '\\' && szNamePart1[0] != '/');
- assert(szNamePart1[nLength - 1] != '/' && szNamePart1[nLength - 1] != '\\');
-
- // Append file path separator
- if(bIsFullPath == false)
- *szBuffer++ = PATH_SEPARATOR;
-
- // Copy the file name
- memcpy(szBuffer, szNamePart1, nLength);
- szBuffer += nLength;
- }
-
- // Append the second part of the name
- if(szNamePart2 != NULL && (nLength = strlen(szNamePart2)) != 0)
- {
- // Copy the file name
- memcpy(szBuffer, szNamePart2, nLength);
- szBuffer += nLength;
- }
-
- // Normalize the path separators
- while(szSaveBuffer < szBuffer)
- {
- szSaveBuffer[0] = (szSaveBuffer[0] != '/' && szSaveBuffer[0] != '\\') ? szSaveBuffer[0] : chSeparator;
- szSaveBuffer++;
- }
-
- // Terminate the buffer with zero
- *szBuffer = 0;
-}
-
-static int CalculateFileSha1(TLogHelper * pLogger, const char * szFullPath, char * szFileSha1)
-{
- TFileStream * pStream;
- unsigned char sha1_digest[SHA1_DIGEST_SIZE];
- const char * szShortPlainName = GetShortPlainName(szFullPath);
- hash_state sha1_state;
- ULONGLONG ByteOffset = 0;
- ULONGLONG FileSize = 0;
- BYTE * pbFileBlock;
- DWORD cbBytesToRead;
- DWORD cbFileBlock = 0x100000;
- int nError = ERROR_SUCCESS;
-
- // Notify the user
- pLogger->PrintProgress("Hashing file %s", szShortPlainName);
- szFileSha1[0] = 0;
-
- // Open the file to be verified
- pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY);
- if(pStream != NULL)
- {
- // Retrieve the size of the file
- FileStream_GetSize(pStream, &FileSize);
-
- // Allocate the buffer for loading file parts
- pbFileBlock = STORM_ALLOC(BYTE, cbFileBlock);
- if(pbFileBlock != NULL)
- {
- // Initialize SHA1 calculation
- sha1_init(&sha1_state);
-
- // Calculate the SHA1 of the file
- while(ByteOffset < FileSize)
- {
- // Notify the user
- pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize);
-
- // Load the file block
- cbBytesToRead = ((FileSize - ByteOffset) > cbFileBlock) ? cbFileBlock : (DWORD)(FileSize - ByteOffset);
- if(!FileStream_Read(pStream, &ByteOffset, pbFileBlock, cbBytesToRead))
- {
- nError = GetLastError();
- break;
- }
-
- // Add to SHA1
- sha1_process(&sha1_state, pbFileBlock, cbBytesToRead);
- ByteOffset += cbBytesToRead;
- }
-
- // Notify the user
- pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize);
-
- // Finalize SHA1
- sha1_done(&sha1_state, sha1_digest);
-
- // Convert the SHA1 to ANSI text
- ConvertSha1ToText(sha1_digest, szFileSha1);
- STORM_FREE(pbFileBlock);
- }
-
- FileStream_Close(pStream);
- }
-
- // If we calculated something, return OK
- if(nError == ERROR_SUCCESS && szFileSha1[0] == 0)
- nError = ERROR_CAN_NOT_COMPLETE;
- return nError;
-}
-
-//-----------------------------------------------------------------------------
-// Directory search
-
-static HANDLE InitDirectorySearch(const char * szDirectory)
-{
-#ifdef PLATFORM_WINDOWS
-
- WIN32_FIND_DATA wf;
- HANDLE hFind;
- TCHAR szSearchMask[MAX_PATH];
-
- // Keep compilers happy
- CopyFileName(szSearchMask, szDirectory, strlen(szDirectory));
- _tcscat(szSearchMask, _T("\\*"));
-
- // Construct the directory mask
- hFind = FindFirstFile(szSearchMask, &wf);
- return (hFind != INVALID_HANDLE_VALUE) ? hFind : NULL;
-
-#endif
-
-#ifdef PLATFORM_LINUX
-
- // Keep compilers happy
- return (HANDLE)opendir(szDirectory);
-
-#endif
-}
-
-static bool SearchDirectory(HANDLE hFind, char * szDirEntry, bool & IsDirectory)
-{
-#ifdef PLATFORM_WINDOWS
-
- WIN32_FIND_DATA wf;
- TCHAR szDirEntryT[MAX_PATH];
- char szDirEntryA[MAX_PATH];
-
- __SearchNextEntry:
-
- // Search for the hnext entry.
- if(FindNextFile(hFind, &wf))
- {
- // Verify if the directory entry is an UNICODE name that would be destroyed
- // by Unicode->ANSI->Unicode conversion
- if(CopyStringAndVerifyConversion(wf.cFileName, szDirEntryT, szDirEntryA) == false)
- goto __SearchNextEntry;
-
- IsDirectory = (wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false;
- CopyFileName(szDirEntry, wf.cFileName, _tcslen(wf.cFileName));
- return true;
- }
-
- return false;
-
-#endif
-
-#ifdef PLATFORM_LINUX
-
- struct dirent * directory_entry;
-
- directory_entry = readdir((DIR *)hFind);
- if(directory_entry != NULL)
- {
- IsDirectory = (directory_entry->d_type == DT_DIR) ? true : false;
- strcpy(szDirEntry, directory_entry->d_name);
- return true;
- }
-
- return false;
-
-#endif
-}
-
-static void FreeDirectorySearch(HANDLE hFind)
-{
-#ifdef PLATFORM_WINDOWS
- FindClose(hFind);
-#endif
-
-#ifdef PLATFORM_LINUX
- closedir((DIR *)hFind);
-#endif
-}
-
-static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, char * szDirectory)
-{
- HANDLE hFind;
- char * szPlainName;
- size_t nLength;
- char szDirEntry[MAX_PATH];
- bool IsDirectory = false;
- int nError = ERROR_SUCCESS;
-
- if(szDirectory != NULL)
- {
- // Initiate directory search
- hFind = InitDirectorySearch(szDirectory);
- if(hFind != NULL)
- {
- // Append slash at the end of the directory name
- nLength = strlen(szDirectory);
- szDirectory[nLength++] = PATH_SEPARATOR;
- szPlainName = szDirectory + nLength;
-
- // Skip the first entry, since it's always "." or ".."
- while(SearchDirectory(hFind, szDirEntry, IsDirectory) && nError == ERROR_SUCCESS)
- {
- // Copy the directory entry name to both names
- strcpy(szPlainName, szDirEntry);
-
- // Found a directory?
- if(IsDirectory)
- {
- if(szDirEntry[0] != '.')
- {
- nError = FindFilesInternal(pfnTest, szDirectory);
- }
- }
- else
- {
- if(pfnTest != NULL)
- {
- nError = pfnTest(szDirectory);
- }
- }
- }
-
- FreeDirectorySearch(hFind);
- }
- }
-
- // Free the path buffer, if any
- return nError;
-}
-
-static int FindFiles(FIND_FILE_CALLBACK pfnFindFile, const char * szSubDirectory)
-{
- char szWorkBuff[MAX_PATH];
-
- CreateFullPathName(szWorkBuff, szSubDirectory, NULL);
- return FindFilesInternal(pfnFindFile, szWorkBuff);
-}
-
-static int FindFilePairsInternal(
- FIND_PAIR_CALLBACK pfnFilePair,
- char * szSource,
- char * szTarget)
-{
- char * szPlainName1;
- char * szPlainName2;
- int nError = ERROR_SUCCESS;
-
- // Setup the search masks
- strcat(szSource, "\\*");
- szPlainName1 = strrchr(szSource, '*');
- strcat(szTarget, "\\*");
- szPlainName2 = strrchr(szTarget, '*');
-
- // If both paths are OK, perform the search
- if(szPlainName1 != NULL && szPlainName2 != NULL)
- {
-#ifdef PLATFORM_WINDOWS
- WIN32_FIND_DATAA wf;
- HANDLE hFind;
-
- // Search the second directory
- hFind = FindFirstFileA(szTarget, &wf);
- if(hFind != INVALID_HANDLE_VALUE)
- {
- // Skip the first entry, since it's always "." or ".."
- while(FindNextFileA(hFind, &wf) && nError == ERROR_SUCCESS)
- {
- // Found a directory?
- if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- {
- if(wf.cFileName[0] != '.')
- {
- strcpy(szPlainName1, wf.cFileName);
- strcpy(szPlainName2, wf.cFileName);
- nError = FindFilePairsInternal(pfnFilePair, szSource, szTarget);
- }
- }
- else
- {
- if(pfnFilePair != NULL)
- {
- strcpy(szPlainName1, wf.cFileName);
- strcpy(szPlainName2, wf.cFileName);
- nError = pfnFilePair(szSource, szTarget);
- }
- }
- }
-
- FindClose(hFind);
- }
-#endif
- }
-
- return nError;
-}
-
-static int FindFilePairs(FIND_PAIR_CALLBACK pfnFindPair, const char * szSourceSubDir, const char * szTargetSubDir)
-{
- char szSource[MAX_PATH];
- char szTarget[MAX_PATH];
-
- // Create the source search mask
- CreateFullPathName(szSource, szSourceSubDir, NULL);
- CreateFullPathName(szTarget, szTargetSubDir, NULL);
- return FindFilePairsInternal(pfnFindPair, szSource, szTarget);
-}
-
-static int InitializeMpqDirectory(char * argv[], int argc)
-{
- TLogHelper Logger("InitWorkDir");
- TFileStream * pStream;
- const char * szWhereFrom = NULL;
- const char * szDirName;
- char szFullPath[MAX_PATH];
-
- // Retrieve the name of the MPQ directory
- if(argc > 1 && argv[1] != NULL)
- {
- szWhereFrom = "command line";
- szDirName = argv[1];
- }
- else
- {
- szWhereFrom = "default";
- szDirName = WORK_PATH_ROOT;
- }
-
- // Copy the name of the MPQ directory.
- StringCopyA(szMpqDirectory, szDirName, MAX_PATH);
- cchMpqDirectory = strlen(szMpqDirectory);
-
- // Cut trailing slashes and/or backslashes
- while((cchMpqDirectory > 0) && (szMpqDirectory[cchMpqDirectory - 1] == '/' || szMpqDirectory[cchMpqDirectory - 1] == '\\'))
- cchMpqDirectory--;
- szMpqDirectory[cchMpqDirectory] = 0;
-
- // Print the work directory info
- Logger.PrintMessage("Work directory %s (%s)", szMpqDirectory, szWhereFrom);
-
- // Verify if the work MPQ directory is writable
- CreateFullPathName(szFullPath, NULL, "TestFile.bin");
- pStream = FileStream_CreateFileA(szFullPath, 0);
- if(pStream == NULL)
- return Logger.PrintError("MPQ subdirectory doesn't exist or is not writable");
-
- // Close the stream
- FileStream_Close(pStream);
- remove(szFullPath);
-
- // Verify if the working directory exists and if there is a subdirectory with the file name
- CreateFullPathName(szFullPath, szMpqSubDir, "ListFile_Blizzard.txt");
- pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY);
- if(pStream == NULL)
- return Logger.PrintError("The main listfile (%s) was not found. Check your paths", GetShortPlainName(szFullPath));
-
- // Close the stream
- FileStream_Close(pStream);
- return ERROR_SUCCESS;
-}
-
-static int GetFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName)
-{
- TCHAR * szPatchName;
- HANDLE hFile;
- TCHAR szPatchChain[0x400];
- int nPatchCount = 0;
- int nError = ERROR_SUCCESS;
-
- // Open the MPQ file
- if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile))
- {
- // Notify the user
- pLogger->PrintProgress("Verifying patch chain for %s ...", GetShortPlainName(szFileName));
-
- // Query the patch chain
- if(!SFileGetFileInfo(hFile, SFileInfoPatchChain, szPatchChain, sizeof(szPatchChain), NULL))
- nError = pLogger->PrintError("Failed to retrieve the patch chain on %s", szFileName);
-
- // Is there anything at all in the patch chain?
- if(nError == ERROR_SUCCESS && szPatchChain[0] == 0)
- {
- pLogger->PrintError("The patch chain for %s is empty", szFileName);
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Now calculate the number of patches
- if(nError == ERROR_SUCCESS)
- {
- // Get the pointer to the patch
- szPatchName = szPatchChain;
-
- // Skip the base name
- for(;;)
- {
- // Skip the current name
- szPatchName = szPatchName + _tcslen(szPatchName) + 1;
- if(szPatchName[0] == 0)
- break;
-
- // Increment number of patches
- nPatchCount++;
- }
- }
-
- SFileCloseFile(hFile);
- }
- else
- {
- pLogger->PrintError("Failed to open file %s", szFileName);
- }
-
- return nPatchCount;
-}
-
-static int VerifyFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, int nExpectedPatchCount)
-{
- int nPatchCount = 0;
-
- // Retrieve the patch count
- pLogger->PrintProgress("Verifying patch count for %s ...", szFileName);
- nPatchCount = GetFilePatchCount(pLogger, hMpq, szFileName);
-
- // Check if there are any patches at all
- if(nExpectedPatchCount != 0 && nPatchCount == 0)
- {
- pLogger->PrintMessage("There are no patches beyond %s", szFileName);
- return ERROR_FILE_CORRUPT;
- }
-
- // Check if the number of patches fits
- if(nPatchCount != nExpectedPatchCount)
- {
- pLogger->PrintMessage("Unexpected number of patches for %s", szFileName);
- return ERROR_FILE_CORRUPT;
- }
-
- return ERROR_SUCCESS;
-}
-
-static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONGLONG FileSize, char * szBuffer)
-{
- TFileStream * pStream;
- char szFullPath[MAX_PATH];
-
- // Notify the user
- pLogger->PrintProgress("Creating empty file %s ...", szPlainName);
-
- // Construct the full path and crete the file
- CreateFullPathName(szFullPath, NULL, szPlainName);
- pStream = FileStream_CreateFileA(szFullPath, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
- if(pStream == NULL)
- return pLogger->PrintError("Failed to create file %s", szBuffer);
-
- // Write the required size
- FileStream_SetSize(pStream, FileSize);
- FileStream_Close(pStream);
-
- // Give the caller the full file name
- if(szBuffer != NULL)
- strcpy(szBuffer, szFullPath);
- return ERROR_SUCCESS;
-}
-
-static int VerifyFilePosition(
- TLogHelper * pLogger,
- TFileStream * pStream,
- ULONGLONG ExpectedPosition)
-{
- ULONGLONG ByteOffset = 0;
- int nError = ERROR_SUCCESS;
-
- // Retrieve the file position
- if(FileStream_GetPos(pStream, &ByteOffset))
- {
- if(ByteOffset != ExpectedPosition)
- {
- pLogger->PrintMessage("The file position is different than expected (expected: " I64u_a ", current: " I64u_a, ExpectedPosition, ByteOffset);
- nError = ERROR_FILE_CORRUPT;
- }
- }
- else
- {
- nError = pLogger->PrintError("Failed to retrieve the file offset");
- }
-
- return nError;
-}
-
-static int VerifyFileMpqHeader(TLogHelper * pLogger, TFileStream * pStream, ULONGLONG * pByteOffset)
-{
- TMPQHeader Header;
- int nError = ERROR_SUCCESS;
-
- memset(&Header, 0xFE, sizeof(TMPQHeader));
- if(FileStream_Read(pStream, pByteOffset, &Header, sizeof(TMPQHeader)))
- {
- if(Header.dwID != ID_MPQ)
- {
- pLogger->PrintMessage("Read error - the data is not a MPQ header");
- nError = ERROR_FILE_CORRUPT;
- }
- }
- else
- {
- nError = pLogger->PrintError("Failed to read the MPQ header");
- }
-
- return nError;
-}
-
-static int WriteMpqUserDataHeader(
- TLogHelper * pLogger,
- TFileStream * pStream,
- ULONGLONG ByteOffset,
- DWORD dwByteCount)
-{
- TMPQUserData UserData;
- int nError = ERROR_SUCCESS;
-
- // Notify the user
- pLogger->PrintProgress("Writing user data header...");
-
- // Fill the user data header
- UserData.dwID = ID_MPQ_USERDATA;
- UserData.cbUserDataSize = dwByteCount;
- UserData.dwHeaderOffs = (dwByteCount + sizeof(TMPQUserData));
- UserData.cbUserDataHeader = dwByteCount / 2;
- if(!FileStream_Write(pStream, &ByteOffset, &UserData, sizeof(TMPQUserData)))
- nError = GetLastError();
- return nError;
-}
-
-static int WriteFileData(
- TLogHelper * pLogger,
- TFileStream * pStream,
- ULONGLONG ByteOffset,
- ULONGLONG ByteCount)
-{
- ULONGLONG SaveByteCount = ByteCount;
- ULONGLONG BytesWritten = 0;
- LPBYTE pbDataBuffer;
- DWORD cbDataBuffer = 0x10000;
- int nError = ERROR_SUCCESS;
-
- // Write some data
- pbDataBuffer = new BYTE[cbDataBuffer];
- if(pbDataBuffer != NULL)
- {
- memset(pbDataBuffer, 0, cbDataBuffer);
- strcpy((char *)pbDataBuffer, "This is a test data written to a file.");
-
- // Perform the write
- while(ByteCount > 0)
- {
- DWORD cbToWrite = (ByteCount > cbDataBuffer) ? cbDataBuffer : (DWORD)ByteCount;
-
- // Notify the user
- pLogger->PrintProgress("Writing file data (%I64u of %I64u) ...", BytesWritten, SaveByteCount);
-
- // Write the data
- if(!FileStream_Write(pStream, &ByteOffset, pbDataBuffer, cbToWrite))
- {
- nError = GetLastError();
- break;
- }
-
- BytesWritten += cbToWrite;
- ByteOffset += cbToWrite;
- ByteCount -= cbToWrite;
- }
-
- delete [] pbDataBuffer;
- }
- return nError;
-}
-
-static int CopyFileData(
- TLogHelper * pLogger,
- TFileStream * pStream1,
- TFileStream * pStream2,
- ULONGLONG ByteOffset,
- ULONGLONG ByteCount)
-{
- ULONGLONG BytesCopied = 0;
- ULONGLONG EndOffset = ByteOffset + ByteCount;
- LPBYTE pbCopyBuffer;
- DWORD BytesToRead;
- DWORD BlockLength = 0x100000;
- int nError = ERROR_SUCCESS;
-
- // Allocate copy buffer
- pbCopyBuffer = STORM_ALLOC(BYTE, BlockLength);
- if(pbCopyBuffer != NULL)
- {
- while(ByteOffset < EndOffset)
- {
- // Read source
- BytesToRead = ((EndOffset - ByteOffset) > BlockLength) ? BlockLength : (DWORD)(EndOffset - ByteOffset);
- if(!FileStream_Read(pStream1, &ByteOffset, pbCopyBuffer, BytesToRead))
- {
- nError = GetLastError();
- break;
- }
-
- // Write to the destination file
- if(!FileStream_Write(pStream2, NULL, pbCopyBuffer, BytesToRead))
- {
- nError = GetLastError();
- break;
- }
-
- // Increment the byte counts
- BytesCopied += BytesToRead;
- ByteOffset += BytesToRead;
-
- // Notify the user
- pLogger->PrintProgress("Copying (%I64u of %I64u complete) ...", BytesCopied, ByteCount);
- }
-
- STORM_FREE(pbCopyBuffer);
- }
-
- return nError;
-}
-
-// Support function for copying file
-static int CreateFileCopy(
- TLogHelper * pLogger,
- const char * szPlainName,
- const char * szFileCopy,
- char * szBuffer,
- ULONGLONG PreMpqDataSize = 0,
- ULONGLONG UserDataSize = 0)
-{
- TFileStream * pStream1; // Source file
- TFileStream * pStream2; // Target file
- ULONGLONG ByteOffset = 0;
- ULONGLONG FileSize = 0;
- char szFileName1[MAX_PATH];
- char szFileName2[MAX_PATH];
- int nError = ERROR_SUCCESS;
-
- // Notify the user
- szPlainName += FileStream_PrefixA(szPlainName, NULL);
- pLogger->PrintProgress("Creating copy of %s ...", szPlainName);
-
- // Construct both file names. Check if they are not the same
- CreateFullPathName(szFileName1, szMpqSubDir, szPlainName);
- CreateFullPathName(szFileName2, NULL, szFileCopy + FileStream_PrefixA(szFileCopy, NULL));
- if(!_stricmp(szFileName1, szFileName2))
- {
- pLogger->PrintError("Failed to create copy of MPQ (the copy name is the same like the original name)");
- return ERROR_CAN_NOT_COMPLETE;
- }
-
- // Open the source file
- pStream1 = FileStream_OpenFileA(szFileName1, STREAM_FLAG_READ_ONLY);
- if(pStream1 == NULL)
- {
- pLogger->PrintError("Failed to open the source file %s", szFileName1);
- return ERROR_CAN_NOT_COMPLETE;
- }
-
- // Create the destination file
- pStream2 = FileStream_CreateFileA(szFileName2, 0);
- if(pStream2 != NULL)
- {
- // If we should write some pre-MPQ data to the target file, do it
- if(PreMpqDataSize != 0)
- {
- nError = WriteFileData(pLogger, pStream2, ByteOffset, PreMpqDataSize);
- ByteOffset += PreMpqDataSize;
- }
-
- // If we should write some MPQ user data, write the header first
- if(UserDataSize != 0)
- {
- nError = WriteMpqUserDataHeader(pLogger, pStream2, ByteOffset, (DWORD)UserDataSize);
- ByteOffset += sizeof(TMPQUserData);
-
- nError = WriteFileData(pLogger, pStream2, ByteOffset, UserDataSize);
- ByteOffset += UserDataSize;
- }
-
- // Copy the file data from the source file to the destination file
- FileStream_GetSize(pStream1, &FileSize);
- if(FileSize != 0)
- {
- nError = CopyFileData(pLogger, pStream1, pStream2, 0, FileSize);
- ByteOffset += FileSize;
- }
- FileStream_Close(pStream2);
- }
-
- // Close the source file
- FileStream_Close(pStream1);
-
- // Create the full file name of the target file, including prefix
- if(szBuffer != NULL)
- CreateFullPathName(szBuffer, NULL, szFileCopy);
-
- // Report error, if any
- if(nError != ERROR_SUCCESS)
- pLogger->PrintError("Failed to create copy of MPQ");
- return nError;
-}
-
-static int CreateMasterAndMirrorPaths(
- TLogHelper * pLogger,
- char * szMirrorPath,
- char * szMasterPath,
- const char * szMirrorName,
- const char * szMasterName,
- bool bCopyMirrorFile)
-{
- char szCopyPath[MAX_PATH];
- int nError = ERROR_SUCCESS;
-
- // Always delete the mirror file
- CreateFullPathName(szMasterPath, szMpqSubDir, szMasterName);
- CreateFullPathName(szCopyPath, NULL, szMirrorName);
- remove(szCopyPath + FileStream_PrefixA(szCopyPath, NULL));
-
- // Copy the mirrored file from the source to the work directory
- if(bCopyMirrorFile)
- nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, NULL);
-
- // Create the mirror*master path
- if(nError == ERROR_SUCCESS)
- sprintf(szMirrorPath, "%s*%s", szCopyPath, szMasterPath);
-
- return nError;
-}
-
-static void WINAPI AddFileCallback(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall)
-{
- TLogHelper * pLogger = (TLogHelper *)pvUserData;
-
- // Keep compiler happy
- bFinalCall = bFinalCall;
-
- pLogger->PrintProgress("Adding file (%s) (%u of %u) (%u of %u) ...", pLogger->UserString,
- pLogger->UserCount,
- pLogger->UserTotal,
- dwBytesWritten,
- dwTotalBytes);
-}
-
-static void WINAPI CompactCallback(void * pvUserData, DWORD dwWork, ULONGLONG BytesDone, ULONGLONG TotalBytes)
-{
- TLogHelper * pLogger = (TLogHelper *)pvUserData;
- const char * szWork = NULL;
-
- switch(dwWork)
- {
- case CCB_CHECKING_FILES:
- szWork = "Checking files in archive";
- break;
-
- case CCB_CHECKING_HASH_TABLE:
- szWork = "Checking hash table";
- break;
-
- case CCB_COPYING_NON_MPQ_DATA:
- szWork = "Copying non-MPQ data";
- break;
-
- case CCB_COMPACTING_FILES:
- szWork = "Compacting files";
- break;
-
- case CCB_CLOSING_ARCHIVE:
- szWork = "Closing archive";
- break;
- }
-
- if(szWork != NULL)
- {
- if(pLogger != NULL)
- pLogger->PrintProgress("%s (%I64u of %I64u) ...", szWork, BytesDone, TotalBytes);
- else
- printf("%s (" I64u_a " of " I64u_a ") ... \r", szWork, BytesDone, TotalBytes);
- }
-}
-
-//-----------------------------------------------------------------------------
-// MPQ file utilities
-
-#define TEST_FLAG_LOAD_FILES 0x00000001 // Test function should load all files in the MPQ
-#define TEST_FLAG_HASH_FILES 0x00000002 // Test function should load all files in the MPQ
-#define TEST_FLAG_PLAY_WAVES 0x00000004 // Play extracted WAVE files
-#define TEST_FLAG_MOST_PATCHED 0x00000008 // Find the most patched file
-
-struct TFileData
-{
- DWORD dwBlockIndex;
- DWORD dwFileSize;
- DWORD dwFlags;
- DWORD dwReserved; // Alignment
- BYTE FileData[1];
-};
-
-static bool CheckIfFileIsPresent(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, bool bShouldExist)
-{
- HANDLE hFile = NULL;
-
- if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile))
- {
- if(bShouldExist == false)
- pLogger->PrintMessage("The file %s is present, but it should not be", szFileName);
- SFileCloseFile(hFile);
- return true;
- }
- else
- {
- if(bShouldExist)
- pLogger->PrintMessage("The file %s is not present, but it should be", szFileName);
- return false;
- }
-}
-
-static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName, bool bMustSucceed)
-{
- TFileStream * pStream;
- TFileData * pFileData = NULL;
- ULONGLONG FileSize = 0;
- size_t nAllocateBytes;
-
- // Notify the user
- if(pLogger != NULL)
- pLogger->PrintProgress("Loading local file ...");
-
- // Attempt to open the file
- pStream = FileStream_OpenFileA(szFileName, STREAM_FLAG_READ_ONLY);
- if(pStream == NULL)
- {
- if(pLogger != NULL && bMustSucceed == true)
- pLogger->PrintError("Failed to open the file %s", szFileName);
- return NULL;
- }
-
- // Verify the size
- FileStream_GetSize(pStream, &FileSize);
- if((FileSize >> 0x20) == 0)
- {
- // Allocate space for the file
- nAllocateBytes = sizeof(TFileData) + (size_t)FileSize;
- pFileData = (TFileData *)STORM_ALLOC(BYTE, nAllocateBytes);
- if(pFileData != NULL)
- {
- // Make sure it;s properly zeroed
- memset(pFileData, 0, nAllocateBytes);
- pFileData->dwFileSize = (DWORD)FileSize;
-
- // Load to memory
- if(!FileStream_Read(pStream, NULL, pFileData->FileData, pFileData->dwFileSize))
- {
- STORM_FREE(pFileData);
- pFileData = NULL;
- }
- }
- }
-
- FileStream_Close(pStream);
- return pFileData;
-}
-
-static int CompareTwoLocalFilesRR(
- TLogHelper * pLogger,
- TFileStream * pStream1, // Master file
- TFileStream * pStream2, // Mirror file
- int nIterations) // Number of iterations
-{
- ULONGLONG RandomNumber = 0x12345678; // We need pseudo-random number that will repeat each run of the program
- ULONGLONG RandomSeed;
- ULONGLONG ByteOffset;
- ULONGLONG FileSize1 = 1;
- ULONGLONG FileSize2 = 2;
- DWORD BytesToRead;
- DWORD Difference;
- LPBYTE pbBuffer1;
- LPBYTE pbBuffer2;
- DWORD cbBuffer = 0x100000;
- int nError = ERROR_SUCCESS;
-
- // Compare file sizes
- FileStream_GetSize(pStream1, &FileSize1);
- FileStream_GetSize(pStream2, &FileSize2);
- if(FileSize1 != FileSize2)
- {
- pLogger->PrintMessage("The files have different size");
- return ERROR_CAN_NOT_COMPLETE;
- }
-
- // Allocate both buffers
- pbBuffer1 = STORM_ALLOC(BYTE, cbBuffer);
- pbBuffer2 = STORM_ALLOC(BYTE, cbBuffer);
- if(pbBuffer1 && pbBuffer2)
- {
- // Perform many random reads
- for(int i = 0; i < nIterations; i++)
- {
- // Generate psudo-random offsrt and data size
- ByteOffset = (RandomNumber % FileSize1);
- BytesToRead = (DWORD)(RandomNumber % cbBuffer);
-
- // Show the progress message
- pLogger->PrintProgress("Comparing file: Offset: " I64u_a ", Length: %u", ByteOffset, BytesToRead);
-
- // Only perform read if the byte offset is below
- if(ByteOffset < FileSize1)
- {
- if((ByteOffset + BytesToRead) > FileSize1)
- BytesToRead = (DWORD)(FileSize1 - ByteOffset);
-
- memset(pbBuffer1, 0xEE, cbBuffer);
- memset(pbBuffer2, 0xAA, cbBuffer);
-
- FileStream_Read(pStream1, &ByteOffset, pbBuffer1, BytesToRead);
- FileStream_Read(pStream2, &ByteOffset, pbBuffer2, BytesToRead);
-
- if(!CompareBlocks(pbBuffer1, pbBuffer2, BytesToRead, &Difference))
- {
- pLogger->PrintMessage("Difference at %u (Offset " I64X_a ", Length %X)", Difference, ByteOffset, BytesToRead);
- nError = ERROR_FILE_CORRUPT;
- break;
- }
-
- // Shuffle the random number
- memcpy(&RandomSeed, pbBuffer1, sizeof(RandomSeed));
- RandomNumber = ((RandomNumber >> 0x11) | (RandomNumber << 0x29)) ^ (RandomNumber + RandomSeed);
- }
- }
- }
-
- // Free both buffers
- if(pbBuffer2 != NULL)
- STORM_FREE(pbBuffer2);
- if(pbBuffer1 != NULL)
- STORM_FREE(pbBuffer1);
- return nError;
-}
-
-static TFileData * LoadMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName)
-{
- TFileData * pFileData = NULL;
- HANDLE hFile;
- DWORD dwFileSizeHi = 0xCCCCCCCC;
- DWORD dwFileSizeLo = 0;
- DWORD dwBytesRead;
- int nError = ERROR_SUCCESS;
-
- // Notify the user that we are loading a file from MPQ
- pLogger->PrintProgress("Loading file %s ...", GetShortPlainName(szFileName));
-
-#if defined(_MSC_VER) && defined(_DEBUG)
-// if(!_stricmp(szFileName, "manifest-cards.csv"))
-// DebugBreak();
-#endif
-
- // Open the file from MPQ
- if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile))
- nError = pLogger->PrintError("Failed to open the file %s", szFileName);
-
- // Get the size of the file
- if(nError == ERROR_SUCCESS)
- {
- dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi);
- if(dwFileSizeLo == SFILE_INVALID_SIZE || dwFileSizeHi != 0)
- nError = pLogger->PrintError("Failed to query the file size");
- }
-
- // Spazzler protector: Creates fake files with size of 0x7FFFE7CA
- if(nError == ERROR_SUCCESS)
- {
- if(dwFileSizeLo > 0x1FFFFFFF)
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Allocate buffer for the file content
- if(nError == ERROR_SUCCESS)
- {
- pFileData = (TFileData *)STORM_ALLOC(BYTE, sizeof(TFileData) + dwFileSizeLo);
- if(pFileData == NULL)
- {
- pLogger->PrintError("Failed to allocate buffer for the file content");
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
- }
-
- // get the file index of the MPQ file
- if(nError == ERROR_SUCCESS)
- {
- // Store the file size
- memset(pFileData, 0, sizeof(TFileData) + dwFileSizeLo);
- pFileData->dwFileSize = dwFileSizeLo;
-
- // Retrieve the block index and file flags
- if(!SFileGetFileInfo(hFile, SFileInfoFileIndex, &pFileData->dwBlockIndex, sizeof(DWORD), NULL))
- nError = pLogger->PrintError("Failed retrieve the file index of %s", szFileName);
- if(!SFileGetFileInfo(hFile, SFileInfoFlags, &pFileData->dwFlags, sizeof(DWORD), NULL))
- nError = pLogger->PrintError("Failed retrieve the file flags of %s", szFileName);
- }
-
- // Load the entire file
- if(nError == ERROR_SUCCESS)
- {
- // Read the file data
- SFileReadFile(hFile, pFileData->FileData, dwFileSizeLo, &dwBytesRead, NULL);
- if(dwBytesRead != dwFileSizeLo)
- nError = pLogger->PrintError("Failed to read the content of the file %s", szFileName);
- }
-
- // If failed, free the buffer
- if(nError != ERROR_SUCCESS)
- {
- STORM_FREE(pFileData);
- SetLastError(nError);
- pFileData = NULL;
- }
-
- // Close the file and return what we got
- if(hFile != NULL)
- SFileCloseFile(hFile);
- return pFileData;
-}
-
-static bool CompareTwoFiles(TLogHelper * pLogger, TFileData * pFileData1, TFileData * pFileData2)
-{
- // Compare the file size
- if(pFileData1->dwFileSize != pFileData2->dwFileSize)
- {
- pLogger->PrintErrorVa(_T("The files have different size (%u vs %u)"), pFileData1->dwFileSize, pFileData2->dwFileSize);
- SetLastError(ERROR_FILE_CORRUPT);
- return false;
- }
-
- // Compare the files
- for(DWORD i = 0; i < pFileData1->dwFileSize; i++)
- {
- if(pFileData1->FileData[i] != pFileData2->FileData[i])
- {
- pLogger->PrintErrorVa(_T("Files are different at offset %08X"), i);
- SetLastError(ERROR_FILE_CORRUPT);
- return false;
- }
- }
-
- // The files are identical
- return true;
-}
-
-static int SearchArchive(
- TLogHelper * pLogger,
- HANDLE hMpq,
- DWORD dwTestFlags = 0,
- DWORD * pdwFileCount = NULL,
- LPBYTE pbFileHash = NULL)
-{
- SFILE_FIND_DATA sf;
- TFileData * pFileData;
- HANDLE hFind;
- DWORD dwFileCount = 0;
- hash_state md5state;
- char szMostPatched[MAX_PATH] = "";
- char szListFile[MAX_PATH];
- bool bFound = true;
- int nMaxPatchCount = 0;
- int nPatchCount = 0;
- int nError = ERROR_SUCCESS;
-
- // Construct the full name of the listfile
- CreateFullPathName(szListFile, szMpqSubDir, "ListFile_Blizzard.txt");
-
- // Prepare hashing
- md5_init(&md5state);
-
- // Initiate the MPQ search
- pLogger->PrintProgress("Searching the archive ...");
- hFind = SFileFindFirstFile(hMpq, "*", &sf, szListFile);
- if(hFind == NULL)
- {
- nError = GetLastError();
- nError = (nError == ERROR_NO_MORE_FILES) ? ERROR_SUCCESS : nError;
- return nError;
- }
-
- // Perform the search
- while(bFound == true)
- {
- // Increment number of files
- dwFileCount++;
-
-// if(!_stricmp(sf.cFileName, "Interface\\Glues\\CREDITS\\1024px-Blade3_final2.blp"))
-// DebugBreak();
-
- if(dwTestFlags & TEST_FLAG_MOST_PATCHED)
- {
- // Load the patch count
- nPatchCount = GetFilePatchCount(pLogger, hMpq, sf.cFileName);
-
- // Check if it's greater than maximum
- if(nPatchCount > nMaxPatchCount)
- {
- strcpy(szMostPatched, sf.cFileName);
- nMaxPatchCount = nPatchCount;
- }
- }
-
- // Load the file to memory, if required
- if(dwTestFlags & TEST_FLAG_LOAD_FILES)
- {
- // Load the entire file to the MPQ
- pFileData = LoadMpqFile(pLogger, hMpq, sf.cFileName);
- if(pFileData != NULL)
- {
- // Hash the file data, if needed
- if((dwTestFlags & TEST_FLAG_HASH_FILES) && !IsInternalMpqFileName(sf.cFileName))
- md5_process(&md5state, pFileData->FileData, pFileData->dwFileSize);
-
- // Play sound files, if required
- if((dwTestFlags & TEST_FLAG_PLAY_WAVES) && strstr(sf.cFileName, ".wav") != NULL)
- {
-#ifdef _MSC_VER
- pLogger->PrintProgress("Playing sound %s", sf.cFileName);
- PlaySound((LPCTSTR)pFileData->FileData, NULL, SND_MEMORY);
-#endif
- }
-
- STORM_FREE(pFileData);
- }
- }
-
- bFound = SFileFindNextFile(hFind, &sf);
- }
- SFileFindClose(hFind);
-
- // Give the file count, if required
- if(pdwFileCount != NULL)
- pdwFileCount[0] = dwFileCount;
-
- // Give the hash, if required
- if(pbFileHash != NULL && (dwTestFlags & TEST_FLAG_HASH_FILES))
- md5_done(&md5state, pbFileHash);
-
- return nError;
-}
-
-static int CreateNewArchive(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
-{
- HANDLE hMpq = NULL;
- TCHAR szMpqName[MAX_PATH];
- char szFullPath[MAX_PATH];
-
- // Make sure that the MPQ is deleted
- CreateFullPathName(szFullPath, NULL, szPlainName);
- remove(szFullPath);
-
- // Create the new MPQ
- CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
- if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq))
- return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName);
-
- // Shall we close it right away?
- if(phMpq == NULL)
- SFileCloseArchive(hMpq);
- else
- *phMpq = hMpq;
-
- return ERROR_SUCCESS;
-}
-
-static int CreateNewArchive_V2(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
-{
- SFILE_CREATE_MPQ CreateInfo;
- HANDLE hMpq = NULL;
- TCHAR szMpqName[MAX_PATH];
- char szFullPath[MAX_PATH];
-
- // Make sure that the MPQ is deleted
- CreateFullPathName(szFullPath, NULL, szPlainName);
- CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
- remove(szFullPath);
-
- // Fill the create structure
- memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ));
- CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ);
- CreateInfo.dwMpqVersion = (dwCreateFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT;
- CreateInfo.dwStreamFlags = STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE;
- CreateInfo.dwFileFlags1 = (dwCreateFlags & MPQ_CREATE_LISTFILE) ? MPQ_FILE_EXISTS : 0;
- CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_EXISTS : 0;
- CreateInfo.dwFileFlags3 = (dwCreateFlags & MPQ_CREATE_SIGNATURE) ? MPQ_FILE_EXISTS : 0;
- CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? (MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5) : 0;
- CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000;
- CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0;
- CreateInfo.dwMaxFileCount = dwMaxFileCount;
-
- // Create the new MPQ
- if(!SFileCreateArchive2(szMpqName, &CreateInfo, &hMpq))
- return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName);
-
- // Shall we close it right away?
- if(phMpq == NULL)
- SFileCloseArchive(hMpq);
- else
- *phMpq = hMpq;
-
- return ERROR_SUCCESS;
-}
-
-// Creates new archive with UNICODE name. Adds prefix to the name
-static int CreateNewArchiveU(TLogHelper * pLogger, const wchar_t * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount)
-{
-#ifdef _UNICODE
- HANDLE hMpq = NULL;
- TCHAR szMpqName[MAX_PATH+1];
- char szFullPath[MAX_PATH];
-
- // Construct the full UNICODE name
- CreateFullPathName(szFullPath, NULL, "StormLibTest_");
- CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
- StringCatT(szMpqName, szPlainName, MAX_PATH);
-
- // Make sure that the MPQ is deleted
- _tremove(szMpqName);
-
- // Create the archive
- pLogger->PrintProgress("Creating new archive with UNICODE name ...");
- if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq))
- return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName);
-
- SFileCloseArchive(hMpq);
-#else
- pLogger = pLogger;
- szPlainName = szPlainName;
- dwCreateFlags = dwCreateFlags;
- dwMaxFileCount = dwMaxFileCount;
-#endif
- return ERROR_SUCCESS;
-}
-
-static int OpenExistingArchive(TLogHelper * pLogger, const char * szFullPath, DWORD dwFlags, HANDLE * phMpq)
-{
- HANDLE hMpq = NULL;
- TCHAR szMpqName[MAX_PATH];
-// bool bReopenResult;
- int nError = ERROR_SUCCESS;
-
- // Is it an encrypted MPQ ?
- if(strstr(szFullPath, ".MPQE") != NULL)
- dwFlags |= STREAM_PROVIDER_MPQE;
- if(strstr(szFullPath, ".MPQ.part") != NULL)
- dwFlags |= STREAM_PROVIDER_PARTIAL;
- if(strstr(szFullPath, ".mpq.part") != NULL)
- dwFlags |= STREAM_PROVIDER_PARTIAL;
- if(strstr(szFullPath, ".MPQ.0") != NULL)
- dwFlags |= STREAM_PROVIDER_BLOCK4;
-
- // Open the copied archive
- pLogger->PrintProgress("Opening archive %s ...", GetShortPlainName(szFullPath));
- CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
- if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq))
- {
- switch(nError = GetLastError())
- {
-// case ERROR_BAD_FORMAT: // If the error is ERROR_BAD_FORMAT, try to open with MPQ_OPEN_FORCE_MPQ_V1
-// bReopenResult = SFileOpenArchive(szMpqName, 0, dwFlags | MPQ_OPEN_FORCE_MPQ_V1, &hMpq);
-// nError = (bReopenResult == false) ? GetLastError() : ERROR_SUCCESS;
-// break;
-
- case ERROR_AVI_FILE: // Ignore the error if it's an AVI file or if the file is incomplete
- case ERROR_FILE_INCOMPLETE:
- return nError;
- }
-
- // Show the open error to the user
- return pLogger->PrintError("Failed to open archive %s", szFullPath);
- }
-
- // Store the archive handle or close the archive
- if(phMpq == NULL)
- SFileCloseArchive(hMpq);
- else
- *phMpq = hMpq;
- return nError;
-}
-
-static int OpenPatchArchive(TLogHelper * pLogger, HANDLE hMpq, const char * szFullPath)
-{
- TCHAR szPatchName[MAX_PATH];
- int nError = ERROR_SUCCESS;
-
- pLogger->PrintProgress("Adding patch %s ...", GetShortPlainName(szFullPath));
- CopyFileName(szPatchName, szFullPath, strlen(szFullPath));
- if(!SFileOpenPatchArchive(hMpq, szPatchName, NULL, 0))
- nError = pLogger->PrintError("Failed to add patch %s ...", szFullPath);
-
- return nError;
-}
-
-static int OpenExistingArchiveWithCopy(TLogHelper * pLogger, const char * szFileName, const char * szCopyName, HANDLE * phMpq)
-{
- DWORD dwFlags = 0;
- char szFullPath[MAX_PATH];
- int nError = ERROR_SUCCESS;
-
- // We expect MPQ directory to be already prepared by InitializeMpqDirectory
- assert(szMpqDirectory[0] != 0);
-
- // At least one name must be entered
- assert(szFileName != NULL || szCopyName != NULL);
-
- // If both names entered, create a copy
- if(szFileName != NULL && szCopyName != NULL)
- {
- nError = CreateFileCopy(pLogger, szFileName, szCopyName, szFullPath);
- if(nError != ERROR_SUCCESS)
- return nError;
- }
-
- // If only source name entered, open it for read-only access
- else if(szFileName != NULL && szCopyName == NULL)
- {
- CreateFullPathName(szFullPath, szMpqSubDir, szFileName);
- dwFlags |= MPQ_OPEN_READ_ONLY;
- }
-
- // If only target name entered, open it directly
- else if(szFileName == NULL && szCopyName != NULL)
- {
- CreateFullPathName(szFullPath, NULL, szCopyName);
- }
-
- // Open the archive
- return OpenExistingArchive(pLogger, szFullPath, dwFlags, phMpq);
-}
-
-static int OpenPatchedArchive(TLogHelper * pLogger, HANDLE * phMpq, const char * PatchList[])
-{
- HANDLE hMpq = NULL;
- char szFullPath[MAX_PATH];
- int nError = ERROR_SUCCESS;
-
- // The first file is expected to be valid
- assert(PatchList[0] != NULL);
-
- // Open the primary MPQ
- CreateFullPathName(szFullPath, szMpqSubDir, PatchList[0]);
- nError = OpenExistingArchive(pLogger, szFullPath, MPQ_OPEN_READ_ONLY, &hMpq);
-
- // Add all patches
- if(nError == ERROR_SUCCESS)
- {
- for(size_t i = 1; PatchList[i] != NULL; i++)
- {
- CreateFullPathName(szFullPath, szMpqPatchDir, PatchList[i]);
- nError = OpenPatchArchive(pLogger, hMpq, szFullPath);
- if(nError != ERROR_SUCCESS)
- break;
- }
- }
-
- // Store the archive handle or close the archive
- if(phMpq == NULL)
- SFileCloseArchive(hMpq);
- else
- *phMpq = hMpq;
- return nError;
-}
-
-static int AddFileToMpq(
- TLogHelper * pLogger,
- HANDLE hMpq,
- const char * szFileName,
- const char * szFileData,
- DWORD dwFlags = 0,
- DWORD dwCompression = 0,
- int nExpectedError = ERROR_SUCCESS)
-{
- HANDLE hFile = NULL;
- DWORD dwFileSize = (DWORD)strlen(szFileData);
- int nError = ERROR_SUCCESS;
-
- // Notify the user
- pLogger->PrintProgress("Adding file %s ...", szFileName);
-
- // Get the default flags
- if(dwFlags == 0)
- dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED;
- if(dwCompression == 0)
- dwCompression = MPQ_COMPRESSION_ZLIB;
-
- // Create the file within the MPQ
- if(SFileCreateFile(hMpq, szFileName, 0, dwFileSize, 0, dwFlags, &hFile))
- {
- // Write the file
- if(!SFileWriteFile(hFile, szFileData, dwFileSize, dwCompression))
- nError = pLogger->PrintError("Failed to write data to the MPQ");
- SFileCloseFile(hFile);
- }
- else
- {
- nError = GetLastError();
- }
-
- // Check the expected error code
- if(nExpectedError != ERROR_UNDETERMINED_RESULT && nError != nExpectedError)
- return pLogger->PrintError("Unexpected result from SFileCreateFile(%s)", szFileName);
- return nError;
-}
-
-static int AddLocalFileToMpq(
- TLogHelper * pLogger,
- HANDLE hMpq,
- const char * szArchivedName,
- const char * szLocalFileName,
- DWORD dwFlags = 0,
- DWORD dwCompression = 0,
- bool bMustSucceed = false)
-{
- TCHAR szFileName[MAX_PATH];
- DWORD dwVerifyResult;
-
- // Notify the user
- pLogger->PrintProgress("Adding file %s (%u of %u)...", GetShortPlainName(szLocalFileName), pLogger->UserCount, pLogger->UserTotal);
- pLogger->UserString = szArchivedName;
-
- // Get the default flags
- if(dwFlags == 0)
- dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED;
- if(dwCompression == 0)
- dwCompression = MPQ_COMPRESSION_ZLIB;
-
- // Set the notification callback
- SFileSetAddFileCallback(hMpq, AddFileCallback, pLogger);
-
- // Add the file to the MPQ
- CopyFileName(szFileName, szLocalFileName, strlen(szLocalFileName));
- if(!SFileAddFileEx(hMpq, szFileName, szArchivedName, dwFlags, dwCompression, MPQ_COMPRESSION_NEXT_SAME))
- {
- if(bMustSucceed)
- return pLogger->PrintError("Failed to add the file %s", szArchivedName);
- return GetLastError();
- }
-
- // Verify the file unless it was lossy compression
- if((dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) == 0)
- {
- // Notify the user
- pLogger->PrintProgress("Verifying file %s (%u of %u) ...", szArchivedName, pLogger->UserCount, pLogger->UserTotal);
-
- // Perform the verification
- dwVerifyResult = SFileVerifyFile(hMpq, szArchivedName, MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_MD5);
- if(dwVerifyResult & (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR))
- return pLogger->PrintError("CRC error on %s", szArchivedName);
- }
-
- return ERROR_SUCCESS;
-}
-
-static int RenameMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szOldFileName, const char * szNewFileName, int nExpectedError)
-{
- int nError = ERROR_SUCCESS;
-
- // Notify the user
- pLogger->PrintProgress("Renaming %s to %s ...", szOldFileName, szNewFileName);
-
- // Perform the deletion
- if(!SFileRenameFile(hMpq, szOldFileName, szNewFileName))
- nError = GetLastError();
-
- if(nError != nExpectedError)
- return pLogger->PrintErrorVa("Unexpected result from SFileRenameFile(%s -> %s)", szOldFileName, szNewFileName);
- return ERROR_SUCCESS;
-}
-
-static int RemoveMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, int nExpectedError)
-{
- int nError = ERROR_SUCCESS;
-
- // Notify the user
- pLogger->PrintProgress("Removing file %s ...", szFileName);
-
- // Perform the deletion
- if(!SFileRemoveFile(hMpq, szFileName, 0))
- nError = GetLastError();
-
- if(nError != nExpectedError)
- return pLogger->PrintError("Unexpected result from SFileRemoveFile(%s)", szFileName);
- return ERROR_SUCCESS;
-}
-
-//-----------------------------------------------------------------------------
-// Tests
-
-static void TestGetFileInfo(
- TLogHelper * pLogger,
- HANDLE hMpqOrFile,
- SFileInfoClass InfoClass,
- void * pvFileInfo,
- DWORD cbFileInfo,
- DWORD * pcbLengthNeeded,
- bool bExpectedResult,
- int nExpectedError)
-{
- bool bResult;
- int nError = ERROR_SUCCESS;
-
- // Call the get file info
- bResult = SFileGetFileInfo(hMpqOrFile, InfoClass, pvFileInfo, cbFileInfo, pcbLengthNeeded);
- if(!bResult)
- nError = GetLastError();
-
- if(bResult != bExpectedResult)
- pLogger->PrintMessage("Different result of SFileGetFileInfo.");
- if(nError != nExpectedError)
- pLogger->PrintMessage("Different error from SFileGetFileInfo (expected %u, returned %u)", nExpectedError, nError);
-}
-
-// StormLib is able to open local files (as well as the original Storm.dll)
-// I want to keep this for occasional use
-static int TestOpenLocalFile(const char * szPlainName)
-{
- TLogHelper Logger("OpenLocalFile", szPlainName);
- HANDLE hFile;
- DWORD dwFileSizeHi = 0;
- DWORD dwFileSizeLo = 0;
- char szFileName1[MAX_PATH];
- char szFileName2[MAX_PATH];
- char szFileLine[0x40];
-
- CreateFullPathName(szFileName1, szMpqSubDir, szPlainName);
- if(SFileOpenFileEx(NULL, szFileName1, SFILE_OPEN_LOCAL_FILE, &hFile))
- {
- // Retrieve the file name. It must match the name under which the file was open
- SFileGetFileName(hFile, szFileName2);
- if(strcmp(szFileName2, szFileName1))
- Logger.PrintMessage("The retrieved name does not match the open name");
-
- // Retrieve the file size
- dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi);
- if(dwFileSizeHi != 0 || dwFileSizeLo != 3904784)
- Logger.PrintMessage("Local file size mismatch");
-
- // Read the first line
- memset(szFileLine, 0, sizeof(szFileLine));
- SFileReadFile(hFile, szFileLine, 18, NULL, NULL);
- if(strcmp(szFileLine, "(1)Enslavers01.scm"))
- Logger.PrintMessage("Content of the listfile does not match");
-
- SFileCloseFile(hFile);
- }
-
- return ERROR_SUCCESS;
-}
-
-static int TestSearchListFile(const char * szPlainName)
-{
- SFILE_FIND_DATA sf;
- TLogHelper Logger("SearchListFile", szPlainName);
- HANDLE hFind;
- char szFullPath[MAX_PATH];
- int nFileCount = 0;
-
- CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
- hFind = SListFileFindFirstFile(NULL, szFullPath, "*", &sf);
- if(hFind != NULL)
- {
- for(;;)
- {
- Logger.PrintProgress("Found file (%04u): %s", nFileCount++, GetShortPlainName(sf.cFileName));
- if(!SListFileFindNextFile(hFind, &sf))
- break;
- }
-
- SListFileFindClose(hFind);
- }
- return ERROR_SUCCESS;
-}
-
-static void WINAPI TestReadFile_DownloadCallback(
- void * UserData,
- ULONGLONG ByteOffset,
- DWORD DataLength)
-{
- TLogHelper * pLogger = (TLogHelper *)UserData;
-
- if(ByteOffset != 0 && DataLength != 0)
- pLogger->PrintProgress("Downloading data (offset: " I64X_a ", length: %X)", ByteOffset, DataLength);
- else
- pLogger->PrintProgress("Download complete.");
-}
-
-// Open a file stream with mirroring a master file
-static int TestReadFile_MasterMirror(const char * szMirrorName, const char * szMasterName, bool bCopyMirrorFile)
-{
- TFileStream * pStream1; // Master file
- TFileStream * pStream2; // Mirror file
- TLogHelper Logger("OpenMirrorFile", szMirrorName);
- DWORD dwProvider = 0;
- char szMirrorPath[MAX_PATH + MAX_PATH];
- char szMasterPath[MAX_PATH];
- int nIterations = 0x10000;
- int nError;
-
- // Retrieve the provider
- FileStream_PrefixA(szMasterName, &dwProvider);
-
-#ifndef PLATFORM_WINDOWS
- if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
- return ERROR_SUCCESS;
-#endif
-
- // Create copy of the file to serve as mirror, keep master there
- nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName, bCopyMirrorFile);
- if(nError == ERROR_SUCCESS)
- {
- // Open both master and mirror file
- pStream1 = FileStream_OpenFileA(szMasterPath, STREAM_FLAG_READ_ONLY);
- pStream2 = FileStream_OpenFileA(szMirrorPath, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP);
- if(pStream1 && pStream2)
- {
- // For internet based files, we limit the number of operations
- if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
- nIterations = 0x80;
-
- FileStream_SetCallback(pStream2, TestReadFile_DownloadCallback, &Logger);
- nError = CompareTwoLocalFilesRR(&Logger, pStream1, pStream2, nIterations);
- }
-
- if(pStream2 != NULL)
- FileStream_Close(pStream2);
- if(pStream1 != NULL)
- FileStream_Close(pStream1);
- }
-
- return nError;
-}
-
-// Test of the TFileStream object
-static int TestSparseCompression()
-{
- BYTE InpBuffer[0x1000];
- BYTE Compressed[0x1000];
- BYTE Decompressed[0x1000];
- int cbCompressed = sizeof(Compressed);
- int cbDecompressed = sizeof(Compressed);
-
- // Prepare compressed buffer
- memset(InpBuffer, 0, sizeof(InpBuffer));
-
- // Compress and decompress
- CompressSparse(Compressed, &cbCompressed, InpBuffer, sizeof(InpBuffer));
- DecompressSparse(Decompressed, &cbDecompressed, Compressed, cbCompressed);
-
- // Check the result of decompression
- if(cbDecompressed != sizeof(InpBuffer))
- return ERROR_FILE_CORRUPT;
- if(memcmp(Decompressed, InpBuffer, sizeof(InpBuffer)))
- return ERROR_FILE_CORRUPT;
-
- return ERROR_SUCCESS;
-}
-
-// Test of the TFileStream object
-static int TestFileStreamOperations(const char * szPlainName, DWORD dwStreamFlags)
-{
- TFileStream * pStream = NULL;
- TLogHelper Logger("FileStreamTest", szPlainName);
- ULONGLONG ByteOffset;
- ULONGLONG FileSize = 0;
- DWORD dwRequiredFlags = 0;
- char szFullPath[MAX_PATH];
- BYTE Buffer[0x10];
- int nError = ERROR_SUCCESS;
-
- // Copy the file so we won't screw up
- if((dwStreamFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_BLOCK4)
- CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
- else
- nError = CreateFileCopy(&Logger, szPlainName, szPlainName, szFullPath);
-
- // Open the file stream
- if(nError == ERROR_SUCCESS)
- {
- pStream = FileStream_OpenFileA(szFullPath, dwStreamFlags);
- if(pStream == NULL)
- return Logger.PrintError("Failed to open %s", szFullPath);
- }
-
- // Get the size of the file stream
- if(nError == ERROR_SUCCESS)
- {
- if(!FileStream_GetFlags(pStream, &dwStreamFlags))
- nError = Logger.PrintError("Failed to retrieve the stream flags");
-
- if(!FileStream_GetSize(pStream, &FileSize))
- nError = Logger.PrintError("Failed to retrieve the file size");
-
- // Any other stream except STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE should be read-only
- if((dwStreamFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
- dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
-// if(pStream->BlockPresent)
-// dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
-
- // Check the flags there
- if((dwStreamFlags & dwRequiredFlags) != dwRequiredFlags)
- {
- Logger.PrintMessage("The stream should be read-only but it isn't");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // After successful open, the stream position must be zero
- if(nError == ERROR_SUCCESS)
- nError = VerifyFilePosition(&Logger, pStream, 0);
-
- // Read the MPQ header from the current file offset.
- if(nError == ERROR_SUCCESS)
- nError = VerifyFileMpqHeader(&Logger, pStream, NULL);
-
- // After successful open, the stream position must sizeof(TMPQHeader)
- if(nError == ERROR_SUCCESS)
- nError = VerifyFilePosition(&Logger, pStream, sizeof(TMPQHeader));
-
- // Now try to read the MPQ header from the offset 0
- if(nError == ERROR_SUCCESS)
- {
- ByteOffset = 0;
- nError = VerifyFileMpqHeader(&Logger, pStream, &ByteOffset);
- }
-
- // After successful open, the stream position must sizeof(TMPQHeader)
- if(nError == ERROR_SUCCESS)
- nError = VerifyFilePosition(&Logger, pStream, sizeof(TMPQHeader));
-
- // Try a write operation
- if(nError == ERROR_SUCCESS)
- {
- bool bExpectedResult = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? false : true;
- bool bResult;
-
- // Attempt to write to the file
- ByteOffset = 0;
- bResult = FileStream_Write(pStream, &ByteOffset, Buffer, sizeof(Buffer));
-
- // If the result is not expected
- if(bResult != bExpectedResult)
- {
- Logger.PrintMessage("FileStream_Write result is different than expected");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Move the position 9 bytes from the end and try to read 10 bytes.
- // This must fail, because stream reading functions are "all or nothing"
- if(nError == ERROR_SUCCESS)
- {
- ByteOffset = FileSize - 9;
- if(FileStream_Read(pStream, &ByteOffset, Buffer, 10))
- {
- Logger.PrintMessage("FileStream_Read succeeded, but it shouldn't");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Try again with 9 bytes. This must succeed, unless the file block is not available
- if(nError == ERROR_SUCCESS)
- {
- ByteOffset = FileSize - 9;
- if(!FileStream_Read(pStream, &ByteOffset, Buffer, 9))
- {
- Logger.PrintMessage("FileStream_Read from the end of the file failed");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Verify file position - it must be at the end of the file
- if(nError == ERROR_SUCCESS)
- nError = VerifyFilePosition(&Logger, pStream, FileSize);
-
- // Close the stream
- if(pStream != NULL)
- FileStream_Close(pStream);
- return nError;
-}
-
-static int TestOpenFile_OpenById(const char * szPlainName)
-{
- TLogHelper Logger("OpenFileById", szPlainName);
- TFileData * pFileData1 = NULL;
- TFileData * pFileData2 = NULL;
- HANDLE hMpq;
- int nError;
-
- // Copy the archive so we won't fuck up the original one
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq);
-
- // Now try to open a file without knowing the file name
- if(nError == ERROR_SUCCESS)
- {
- // File00000023.xxx = music\dintro.wav
- pFileData1 = LoadMpqFile(&Logger, hMpq, "File00000023.xxx");
- if(pFileData1 == NULL)
- nError = Logger.PrintError("Failed to load the file %s", "File00000023.xxx");
- }
-
- // Now try to open the file again with its original name
- if(nError == ERROR_SUCCESS)
- {
- // File00000023.xxx = music\dintro.wav
- pFileData2 = LoadMpqFile(&Logger, hMpq, "music\\dintro.wav");
- if(pFileData2 == NULL)
- nError = Logger.PrintError("Failed to load the file %s", "music\\dintro.wav");
- }
-
- // Now compare both files
- if(nError == ERROR_SUCCESS)
- {
- if(!CompareTwoFiles(&Logger, pFileData1, pFileData1))
- nError = Logger.PrintError("The file has different size/content when open without name");
- }
-
- // Close the archive
- if(pFileData2 != NULL)
- STORM_FREE(pFileData2);
- if(pFileData1 != NULL)
- STORM_FREE(pFileData1);
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-static int TestOpenArchive(const char * szPlainName, const char * szListFile = NULL, bool bDontCopyArchive = false)
-{
- TLogHelper Logger("OpenMpqTest", szPlainName);
- TFileData * pFileData;
- const char * szCopyName = (bDontCopyArchive) ? NULL : szPlainName;
- HANDLE hMpq;
- DWORD dwFileCount = 0;
- DWORD dwTestFlags;
- char szListFileBuff[MAX_PATH];
- bool bIsPartialMpq = false;
- int nError;
-
- // If the file is a partial MPQ, don't load all files
- bIsPartialMpq = (strstr(szPlainName, ".MPQ.part") != NULL);
-
- // Copy the archive so we won't fuck up the original one
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szCopyName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // If the listfile was given, add it to the MPQ
- if(szListFile != NULL)
- {
- Logger.PrintProgress("Adding listfile %s ...", szListFile);
- CreateFullPathName(szListFileBuff, szMpqSubDir, szListFile);
- nError = SFileAddListFile(hMpq, szListFileBuff);
- if(nError != ERROR_SUCCESS)
- Logger.PrintMessage("Failed to add the listfile to the MPQ");
- }
-
- // Attempt to open the listfile and attributes
- if(SFileHasFile(hMpq, LISTFILE_NAME))
- {
- pFileData = LoadMpqFile(&Logger, hMpq, LISTFILE_NAME);
- if(pFileData != NULL)
- STORM_FREE(pFileData);
- }
-
- // Attempt to open the listfile and attributes
- if(SFileHasFile(hMpq, ATTRIBUTES_NAME))
- {
- pFileData = LoadMpqFile(&Logger, hMpq, ATTRIBUTES_NAME);
- if(pFileData != NULL)
- STORM_FREE(pFileData);
- }
-
- // Search the archive and load every file
- dwTestFlags = bIsPartialMpq ? 0 : TEST_FLAG_LOAD_FILES;
- nError = SearchArchive(&Logger, hMpq, dwTestFlags, &dwFileCount);
- SFileCloseArchive(hMpq);
- }
-
- return nError;
-}
-
-
-// Open an empty archive (found in WoW cache - it's just a header)
-static int TestOpenArchive_WillFail(const char * szPlainName)
-{
- TLogHelper Logger("FailMpqTest", szPlainName);
- HANDLE hMpq = NULL;
- TCHAR szMpqName[MAX_PATH];
- char szFullPath[MAX_PATH];
-
- // Create the full path name for the archive
- CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
- CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
-
- // Try to open the archive. It is expected to fail
- Logger.PrintProgress("Opening archive %s", szPlainName);
- if(!SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq))
- return ERROR_SUCCESS;
-
- Logger.PrintError("Archive %s succeeded to open, even if it should not.", szPlainName);
- SFileCloseArchive(hMpq);
- return ERROR_CAN_NOT_COMPLETE;
-}
-
-static int TestOpenArchive_Corrupt(const char * szPlainName)
-{
- TLogHelper Logger("OpenCorruptMpqTest", szPlainName);
- HANDLE hMpq = NULL;
- TCHAR szFullPathT[MAX_PATH];
- char szFullPath[MAX_PATH];
-
- // Copy the archive so we won't fuck up the original one
- CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
- CopyFileName(szFullPathT, szFullPath, strlen(szFullPath));
- if(SFileOpenArchive(szFullPathT, 0, STREAM_FLAG_READ_ONLY, &hMpq))
- {
- SFileCloseArchive(hMpq);
- Logger.PrintMessage("Opening archive %s succeeded, but it shouldn't", szFullPath);
- return ERROR_CAN_NOT_COMPLETE;
- }
-
- return ERROR_SUCCESS;
-}
-
-
-// Opens a patched MPQ archive
-static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatchedFile, int nExpectedPatchCount)
-{
- TLogHelper Logger("OpenPatchedMpqTest", PatchList[0]);
- HANDLE hMpq;
- HANDLE hFile;
- BYTE Buffer[0x100];
- DWORD dwFileCount = 0;
- DWORD BytesRead = 0;
- int nError;
-
- // Open a patched MPQ archive
- nError = OpenPatchedArchive(&Logger, &hMpq, PatchList);
- if(nError == ERROR_SUCCESS)
- {
- // Check patch count
- if(szPatchedFile != NULL)
- nError = VerifyFilePatchCount(&Logger, hMpq, szPatchedFile, nExpectedPatchCount);
-
- // Try to open and read the file
- if(nError == ERROR_SUCCESS)
- {
- if(SFileOpenFileEx(hMpq, szPatchedFile, 0, &hFile))
- {
- SFileReadFile(hFile, Buffer, sizeof(Buffer), &BytesRead, NULL);
- SFileCloseFile(hFile);
- }
- }
-
- // Search the archive and load every file
- if(nError == ERROR_SUCCESS)
- nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES, &dwFileCount);
-
- // Close the archive
- SFileCloseArchive(hMpq);
- }
-
- return nError;
-}
-
-// Open an archive for read-only access
-static int TestOpenArchive_ReadOnly(const char * szPlainName, bool bReadOnly)
-{
- const char * szCopyName;
- TLogHelper Logger("ReadOnlyTest", szPlainName);
- HANDLE hMpq = NULL;
- char szFullPathName[MAX_PATH];
- DWORD dwFlags = bReadOnly ? MPQ_OPEN_READ_ONLY : 0;;
- int nExpectedError;
- int nError;
-
- // Copy the fiel so we wont screw up something
- szCopyName = bReadOnly ? "StormLibTest_ReadOnly.mpq" : "StormLibTest_ReadWrite.mpq";
- nError = CreateFileCopy(&Logger, szPlainName, szCopyName, szFullPathName);
-
- // Now open the archive for read-only access
- if(nError == ERROR_SUCCESS)
- nError = OpenExistingArchive(&Logger, szFullPathName, dwFlags, &hMpq);
-
- // Now try to add a file. This must fail if the MPQ is read only
- if(nError == ERROR_SUCCESS)
- {
- nExpectedError = (bReadOnly) ? ERROR_ACCESS_DENIED : ERROR_SUCCESS;
- AddFileToMpq(&Logger, hMpq, "AddedFile.txt", "This is an added file.", MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, 0, nExpectedError);
- }
-
- // Now try to rename a file in the MPQ. This must only succeed if the MPQ is not read only
- if(nError == ERROR_SUCCESS)
- {
- nExpectedError = (bReadOnly) ? ERROR_ACCESS_DENIED : ERROR_SUCCESS;
- RenameMpqFile(&Logger, hMpq, "spawn.mpq", "spawn-renamed.mpq", nExpectedError);
- }
-
- // Now try to delete a file in the MPQ. This must only succeed if the MPQ is not read only
- if(nError == ERROR_SUCCESS)
- {
- nExpectedError = (bReadOnly) ? ERROR_ACCESS_DENIED : ERROR_SUCCESS;
- RemoveMpqFile(&Logger, hMpq, "spawn-renamed.mpq", nExpectedError);
- }
-
- // Close the archive
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-static int TestOpenArchive_GetFileInfo(const char * szPlainName1, const char * szPlainName4)
-{
- TLogHelper Logger("GetFileInfoTest", szPlainName1, szPlainName4);
- HANDLE hFile;
- HANDLE hMpq4;
- HANDLE hMpq1;
- DWORD cbLength;
- BYTE DataBuff[0x400];
- int nError1;
- int nError4;
-
- // Copy the archive so we won't fuck up the original one
- nError1 = OpenExistingArchiveWithCopy(&Logger, szPlainName1, NULL, &hMpq1);
- nError4 = OpenExistingArchiveWithCopy(&Logger, szPlainName4, NULL, &hMpq4);
- if(nError1 == ERROR_SUCCESS && nError4 == ERROR_SUCCESS)
- {
- // Invalid handle - expected (false, ERROR_INVALID_HANDLE)
- TestGetFileInfo(&Logger, NULL, SFileMpqBetHeader, NULL, 0, NULL, false, ERROR_INVALID_HANDLE);
-
- // Valid handle but invalid value of file info class (false, ERROR_INVALID_PARAMETER)
- TestGetFileInfo(&Logger, NULL, (SFileInfoClass)0xFFF, NULL, 0, NULL, false, ERROR_INVALID_PARAMETER);
-
- // Valid archive handle but file info class is for file (false, ERROR_INVALID_HANDLE)
- TestGetFileInfo(&Logger, NULL, SFileInfoNameHash1, NULL, 0, NULL, false, ERROR_INVALID_HANDLE);
-
- // Valid handle and all parameters NULL
- // Returns (true, ERROR_SUCCESS), if BET table is present, otherwise (false, ERROR_CAN_NOT_COMPLETE)
- TestGetFileInfo(&Logger, hMpq1, SFileMpqBetHeader, NULL, 0, NULL, false, ERROR_FILE_NOT_FOUND);
- TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 0, NULL, true, ERROR_SUCCESS);
-
- // Now try to retrieve the required size of the BET table header
- TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 0, &cbLength, true, ERROR_SUCCESS);
-
- // When we call SFileInfo with buffer = NULL and nonzero buffer size, it is ignored
- TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 3, &cbLength, true, ERROR_SUCCESS);
-
- // When we call SFileInfo with buffer != NULL and nonzero buffer size, it should return error
- TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, 3, &cbLength, false, ERROR_INSUFFICIENT_BUFFER);
-
- // Request for bet table header should also succeed if we want header only
- TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, sizeof(TMPQBetHeader), &cbLength, true, ERROR_SUCCESS);
-
- // Request for bet table header should also succeed if we want header+flag table only
- TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS);
-
- // Try to retrieve strong signature from the MPQ
- TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, NULL, 0, NULL, true, ERROR_SUCCESS);
- TestGetFileInfo(&Logger, hMpq4, SFileMpqStrongSignature, NULL, 0, NULL, false, ERROR_FILE_NOT_FOUND);
-
- // Strong signature is returned including the signature ID
- TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, NULL, 0, &cbLength, true, ERROR_SUCCESS);
- assert(cbLength == MPQ_STRONG_SIGNATURE_SIZE + 4);
-
- // Retrieve the signature
- TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS);
- assert(memcmp(DataBuff, "NGIS", 4) == 0);
-
- // Check SFileGetFileInfo on
- if(SFileOpenFileEx(hMpq4, LISTFILE_NAME, 0, &hFile))
- {
- // Valid parameters but the handle should be file handle
- TestGetFileInfo(&Logger, hMpq4, SFileInfoFileTime, DataBuff, sizeof(DataBuff), &cbLength, false, ERROR_INVALID_HANDLE);
-
- // Valid parameters
- TestGetFileInfo(&Logger, hFile, SFileInfoFileTime, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS);
-
- SFileCloseFile(hFile);
- }
- }
-
- if(hMpq4 != NULL)
- SFileCloseArchive(hMpq4);
- if(hMpq1 != NULL)
- SFileCloseArchive(hMpq1);
- return ERROR_SUCCESS;
-}
-
-static int TestOpenArchive_MasterMirror(const char * szMirrorName, const char * szMasterName, const char * szFileToExtract, bool bCopyMirrorFile)
-{
- TFileData * pFileData;
- TLogHelper Logger("OpenServerMirror", szMirrorName);
- HANDLE hFile = NULL;
- HANDLE hMpq = NULL;
- DWORD dwVerifyResult;
- char szMirrorPath[MAX_PATH + MAX_PATH]; // Combined name
- char szMasterPath[MAX_PATH]; // Original (server) name
- int nError;
-
- // Create both paths
- nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName, bCopyMirrorFile);
-
- // Now open both archives as local-server pair
- if(nError == ERROR_SUCCESS)
- {
- nError = OpenExistingArchive(&Logger, szMirrorPath, 0, &hMpq);
- }
-
- // The MPQ must be read-only. Writing to mirrored MPQ is not allowed
- if(nError == ERROR_SUCCESS)
- {
- if(SFileCreateFile(hMpq, "AddedFile.bin", 0, 0x10, 0, MPQ_FILE_COMPRESS, &hFile))
- {
- SFileCloseFile(hFile);
- Logger.PrintMessage("The archive is writable, although it should not be");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Verify the file
- if(nError == ERROR_SUCCESS && szFileToExtract != NULL)
- {
- dwVerifyResult = SFileVerifyFile(hMpq, szFileToExtract, SFILE_VERIFY_ALL);
- if(dwVerifyResult & VERIFY_FILE_ERROR_MASK)
- {
- Logger.PrintMessage("File verification failed");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Load the file to memory
- if(nError == ERROR_SUCCESS && szFileToExtract)
- {
- pFileData = LoadMpqFile(&Logger, hMpq, szFileToExtract);
- if(pFileData != NULL)
- STORM_FREE(pFileData);
- }
-
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-
-static int TestOpenArchive_VerifySignature(const char * szPlainName, const char * szOriginalName)
-{
- TLogHelper Logger("VerifySignatureTest", szPlainName);
- HANDLE hMpq;
- DWORD dwSignatures = 0;
- int nVerifyError;
- int nError = ERROR_SUCCESS;
-
- // We need original name for the signature check
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szOriginalName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Query the signature types
- Logger.PrintProgress("Retrieving signatures ...");
- TestGetFileInfo(&Logger, hMpq, SFileMpqSignatures, &dwSignatures, sizeof(DWORD), NULL, true, ERROR_SUCCESS);
-
- // Verify any of the present signatures
- Logger.PrintProgress("Verifying archive signature ...");
- nVerifyError = SFileVerifyArchive(hMpq);
-
- // Verify the result
- if((dwSignatures & SIGNATURE_TYPE_STRONG) && (nVerifyError != ERROR_STRONG_SIGNATURE_OK))
- {
- Logger.PrintMessage("Strong signature verification error");
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Verify the result
- if((dwSignatures & SIGNATURE_TYPE_WEAK) && (nVerifyError != ERROR_WEAK_SIGNATURE_OK))
- {
- Logger.PrintMessage("Weak signature verification error");
- nError = ERROR_FILE_CORRUPT;
- }
-
- SFileCloseArchive(hMpq);
- }
- return nError;
-}
-
-static int TestOpenArchive_ModifySigned(const char * szPlainName, const char * szOriginalName)
-{
- TLogHelper Logger("ModifySignedTest", szPlainName);
- HANDLE hMpq = NULL;
- int nVerifyError;
- int nError = ERROR_SUCCESS;
-
- // We need original name for the signature check
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szOriginalName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Verify the weak signature
- Logger.PrintProgress("Verifying archive signature ...");
- nVerifyError = SFileVerifyArchive(hMpq);
-
- // Check the result signature
- if(nVerifyError != ERROR_WEAK_SIGNATURE_OK)
- {
- Logger.PrintMessage("Weak signature verification error");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Add a file and verify the signature again
- if(nError == ERROR_SUCCESS)
- {
- // Verify any of the present signatures
- Logger.PrintProgress("Modifying signed archive ...");
- nError = AddFileToMpq(&Logger, hMpq, "AddedFile01.txt", "This is a file added to signed MPQ", 0, 0, ERROR_SUCCESS);
- }
-
- // Verify the signature again
- if(nError == ERROR_SUCCESS)
- {
- // Verify the weak signature
- Logger.PrintProgress("Verifying archive signature ...");
- nVerifyError = SFileVerifyArchive(hMpq);
-
- // Check the result signature
- if(nVerifyError != ERROR_WEAK_SIGNATURE_OK)
- {
- Logger.PrintMessage("Weak signature verification error");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Close the MPQ
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-static int TestOpenArchive_SignExisting(const char * szPlainName)
-{
- TLogHelper Logger("SignExistingMpq", szPlainName);
- HANDLE hMpq = NULL;
- int nVerifyError;
- int nError = ERROR_SUCCESS;
-
- // We need original name for the signature check
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szPlainName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Verify the weak signature
- Logger.PrintProgress("Verifying archive signature ...");
- nVerifyError = SFileVerifyArchive(hMpq);
-
- // Check the result signature
- if(nVerifyError != ERROR_NO_SIGNATURE)
- {
- Logger.PrintMessage("There already is a signature in the MPQ");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Add a file and verify the signature again
- if(nError == ERROR_SUCCESS)
- {
- // Verify any of the present signatures
- Logger.PrintProgress("Signing the MPQ ...");
- if(!SFileSignArchive(hMpq, SIGNATURE_TYPE_WEAK))
- {
- Logger.PrintMessage("Failed to create archive signature");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Verify the signature again
- if(nError == ERROR_SUCCESS)
- {
- // Verify the weak signature
- Logger.PrintProgress("Verifying archive signature ...");
- nVerifyError = SFileVerifyArchive(hMpq);
-
- // Check the result signature
- if(nVerifyError != ERROR_WEAK_SIGNATURE_OK)
- {
- Logger.PrintMessage("Weak signature verification error");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Close the MPQ
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-// Open an empty archive (found in WoW cache - it's just a header)
-static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char * szCopyName)
-{
- TLogHelper Logger("CraftedMpqTest", szPlainName);
- HANDLE hMpq;
- DWORD dwFileCount1 = 0;
- DWORD dwFileCount2 = 0;
- BYTE FileHash1[MD5_DIGEST_SIZE];
- BYTE FileHash2[MD5_DIGEST_SIZE];
- char szFullPath[MAX_PATH];
- int nError;
-
- // Create copy of the archive, with interleaving some user data
- nError = CreateFileCopy(&Logger, szPlainName, szCopyName, szFullPath, 0x400, 0x531);
-
- // Open the archive and load some files
- if(nError == ERROR_SUCCESS)
- {
- // Open the archive
- nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Verify presence of (listfile) and (attributes)
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true);
-
- // Search the archive and load every file
- nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES, &dwFileCount1, FileHash1);
- SFileCloseArchive(hMpq);
- }
-
- // Try to compact the MPQ
- if(nError == ERROR_SUCCESS)
- {
- // Open the archive again
- nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Compact the archive
- Logger.PrintProgress("Compacting archive %s ...", GetShortPlainName(szFullPath));
- if(!SFileSetCompactCallback(hMpq, CompactCallback, &Logger))
- nError = Logger.PrintError("Failed to compact archive %s", szFullPath);
-
- SFileCompactArchive(hMpq, NULL, false);
- SFileCloseArchive(hMpq);
- }
-
- // Open the archive and load some files
- if(nError == ERROR_SUCCESS)
- {
- // Open the archive
- nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- // Verify presence of (listfile) and (attributes)
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true);
-
- // Search the archive and load every file
- nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES, &dwFileCount2, FileHash2);
- SFileCloseArchive(hMpq);
- }
-
- // Compare the file counts and their hashes
- if(nError == ERROR_SUCCESS)
- {
- if(dwFileCount2 != dwFileCount1)
- Logger.PrintMessage("Different file count after compacting archive: %u vs %u", dwFileCount2, dwFileCount1);
-
- if(memcmp(FileHash2, FileHash1, MD5_DIGEST_SIZE))
- Logger.PrintMessage("Different file hash after compacting archive");
- }
-
- return nError;
-}
-
-static int ForEachFile_VerifyFileChecksum(const char * szFullPath)
-{
- const char * szShortPlainName = GetShortPlainName(szFullPath);
- TFileData * pFileData;
- char * szExtension;
- char szShaFileName[MAX_PATH+1];
- char szSha1Text[0x40];
- int nError = ERROR_SUCCESS;
-
- // Try to load the file with the SHA extension
- StringCopyA(szShaFileName, szFullPath, MAX_PATH);
- szExtension = strrchr(szShaFileName, '.');
- if(szExtension == NULL)
- return ERROR_SUCCESS;
-
- // Skip .SHA and .TXT files
- if(!_stricmp(szExtension, ".sha") || !_stricmp(szExtension, ".txt"))
- return ERROR_SUCCESS;
-
- // Load the local file to memory
- strcpy(szExtension, ".sha");
- pFileData = LoadLocalFile(NULL, szShaFileName, false);
- if(pFileData != NULL)
- {
- TLogHelper Logger("VerifyFileHash", szShortPlainName);
-
- // Calculate SHA1 of the entire file
- nError = CalculateFileSha1(&Logger, szFullPath, szSha1Text);
- if(nError == ERROR_SUCCESS)
- {
- // Compare with what we loaded from the file
- if(pFileData->dwFileSize >= (SHA1_DIGEST_SIZE * 2))
- {
- // Compare the SHA1
- if(_strnicmp(szSha1Text, (char *)pFileData->FileData, (SHA1_DIGEST_SIZE * 2)))
- {
- Logger.PrintError("File CRC check failed: %s", szFullPath);
- nError = ERROR_FILE_CORRUPT;
- }
- }
- }
-
- STORM_FREE(pFileData);
- }
-
- return nError;
-}
-
-// Opens a found archive
-static int ForEachFile_OpenArchive(const char * szFullPath)
-{
- HANDLE hMpq = NULL;
- DWORD dwFileCount = 0;
- int nError = ERROR_SUCCESS;
-
- // Check if it's a MPQ file type
- if(IsMpqExtension(szFullPath))
- {
- TLogHelper Logger("OpenEachMpqTest", GetShortPlainName(szFullPath));
-
- // Open the MPQ name
- nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
- if(nError == ERROR_AVI_FILE || nError == ERROR_FILE_CORRUPT || nError == ERROR_BAD_FORMAT)
- return ERROR_SUCCESS;
-
- // Search the archive and load every file
- if(nError == ERROR_SUCCESS)
- {
- nError = SearchArchive(&Logger, hMpq, 0, &dwFileCount);
- SFileCloseArchive(hMpq);
- }
- }
-
- // Correct some errors
- if(nError == ERROR_FILE_CORRUPT || nError == ERROR_FILE_INCOMPLETE)
- return ERROR_SUCCESS;
- return nError;
-}
-
-// Adding a file to MPQ that had size of the file table equal
-// or greater than hash table, but has free entries
-static int TestAddFile_FullTable(const char * szFullMpqName)
-{
- TLogHelper Logger("FullMpqTest", szFullMpqName);
- const char * szFileName = "AddedFile001.txt";
- const char * szFileData = "0123456789ABCDEF";
- HANDLE hMpq = NULL;
- int nError = ERROR_SUCCESS;
-
- // Copy the archive so we won't fuck up the original one
- nError = OpenExistingArchiveWithCopy(&Logger, szFullMpqName, szFullMpqName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Attempt to add a file
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, MPQ_FILE_IMPLODE, MPQ_COMPRESSION_PKWARE, ERROR_SUCCESS);
- SFileCloseArchive(hMpq);
- }
-
- return nError;
-}
-
-// Adding a file to MPQ that had no (listfile) and no (attributes).
-// We expect that neither of these will be present after the archive is closed
-static int TestAddFile_ListFileTest(const char * szSourceMpq, bool bShouldHaveListFile, bool bShouldHaveAttributes)
-{
- TLogHelper Logger("ListFileTest", szSourceMpq);
- TFileData * pFileData = NULL;
- const char * szBackupMpq = bShouldHaveListFile ? "StormLibTest_HasListFile.mpq" : "StormLibTest_NoListFile.mpq";
- const char * szFileName = "AddedFile001.txt";
- const char * szFileData = "0123456789ABCDEF";
- HANDLE hMpq = NULL;
- DWORD dwFileSize = (DWORD)strlen(szFileData);
- int nError = ERROR_SUCCESS;
-
- // Copy the archive so we won't fuck up the original one
- nError = OpenExistingArchiveWithCopy(&Logger, szSourceMpq, szBackupMpq, &hMpq);
-
- // Add a file
- if(nError == ERROR_SUCCESS)
- {
- // Now add a file
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, MPQ_FILE_IMPLODE, MPQ_COMPRESSION_PKWARE);
- SFileCloseArchive(hMpq);
- hMpq = NULL;
- }
-
- // Now reopen the archive
- if(nError == ERROR_SUCCESS)
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szBackupMpq, &hMpq);
-
- // Now the file has been written and the MPQ has been saved.
- // We Reopen the MPQ and check if there is no (listfile) nor (attributes).
- if(nError == ERROR_SUCCESS)
- {
- // Verify presence of (listfile) and (attributes)
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, bShouldHaveListFile);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, bShouldHaveAttributes);
-
- // Try to open the file that we recently added
- pFileData = LoadMpqFile(&Logger, hMpq, szFileName);
- if(pFileData != NULL)
- {
- // Verify if the file size matches
- if(pFileData->dwFileSize == dwFileSize)
- {
- // Verify if the file data match
- if(memcmp(pFileData->FileData, szFileData, dwFileSize))
- {
- Logger.PrintError("The data of the added file does not match");
- nError = ERROR_FILE_CORRUPT;
- }
- }
- else
- {
- Logger.PrintError("The size of the added file does not match");
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Delete the file data
- STORM_FREE(pFileData);
- }
- else
- {
- nError = Logger.PrintError("Failed to open the file previously added");
- }
- }
-
- // Close the MPQ archive
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-/*
-static int TestCreateArchive_Deprotect(const char * szPlainName)
-{
- TLogHelper Logger("DeprotectTest", szPlainName);
- HANDLE hMpq1 = NULL;
- HANDLE hMpq2 = NULL;
- char szMpqName1[MAX_PATH];
- char szMpqName2[MAX_PATH];
- BYTE FileHash1[MD5_DIGEST_SIZE];
- BYTE FileHash2[MD5_DIGEST_SIZE];
- DWORD dwFileCount1 = 0;
- DWORD dwFileCount2 = 0;
- DWORD dwTestFlags = TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES;
- int nError = ERROR_SUCCESS;
-
- // First copy: The original (untouched) file
- if(nError == ERROR_SUCCESS)
- {
- AddStringBeforeExtension(szMpqName1, szPlainName, "_original");
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szMpqName1, &hMpq1);
- if(nError != ERROR_SUCCESS)
- Logger.PrintMessage("Failed to open %s", szMpqName1);
- }
-
- // Second copy: Will be deprotected
- if(nError == ERROR_SUCCESS)
- {
- AddStringBeforeExtension(szMpqName2, szPlainName, "_deprotected");
- nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szMpqName2, &hMpq2);
- if(nError != ERROR_SUCCESS)
- Logger.PrintMessage("Failed to open %s", szMpqName2);
- }
-
- // Deprotect the second map
- if(nError == ERROR_SUCCESS)
- {
- SFileSetCompactCallback(hMpq2, CompactCallback, &Logger);
- if(!SFileCompactArchive(hMpq2, NULL, false))
- nError = Logger.PrintError("Failed to deprotect archive %s", szMpqName2);
- }
-
- // Calculate number of files and compare their hash (archive 1)
- if(nError == ERROR_SUCCESS)
- {
- memset(FileHash1, 0, sizeof(FileHash1));
- nError = SearchArchive(&Logger, hMpq1, dwTestFlags, &dwFileCount1, FileHash1);
- }
-
- // Calculate number of files and compare their hash (archive 2)
- if(nError == ERROR_SUCCESS)
- {
- memset(FileHash1, 0, sizeof(FileHash2));
- nError = SearchArchive(&Logger, hMpq2, dwTestFlags, &dwFileCount2, FileHash2);
- }
-
- if(nError == ERROR_SUCCESS)
- {
- if(dwFileCount1 != dwFileCount2)
- Logger.PrintMessage("Different file count (%u in %s; %u in %s)", dwFileCount1, szMpqName1, dwFileCount2, szMpqName2);
- if(memcmp(FileHash1, FileHash2, MD5_DIGEST_SIZE))
- Logger.PrintMessage("Different file hash (%s vs %s)", szMpqName1, szMpqName2);
- }
-
- // Close both MPQs
- if(hMpq2 != NULL)
- SFileCloseArchive(hMpq2);
- if(hMpq1 != NULL)
- SFileCloseArchive(hMpq1);
- return nError;
-}
-*/
-static int TestCreateArchive_EmptyMpq(const char * szPlainName, DWORD dwCreateFlags)
-{
- TLogHelper Logger("CreateEmptyMpq", szPlainName);
- HANDLE hMpq = NULL;
- DWORD dwFileCount = 0;
- int nError;
-
- // Create the full path name
- nError = CreateNewArchive(&Logger, szPlainName, dwCreateFlags, 0, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- SearchArchive(&Logger, hMpq);
- SFileCloseArchive(hMpq);
- }
-
- // Reopen the empty MPQ
- if(nError == ERROR_SUCCESS)
- {
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- SFileGetFileInfo(hMpq, SFileMpqNumberOfFiles, &dwFileCount, sizeof(dwFileCount), NULL);
-
- CheckIfFileIsPresent(&Logger, hMpq, "File00000000.xxx", false);
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, false);
- SearchArchive(&Logger, hMpq);
- SFileCloseArchive(hMpq);
- }
- }
-
- return nError;
-}
-
-static int TestCreateArchive_TestGaps(const char * szPlainName)
-{
- TLogHelper Logger("CreateGapsTest", szPlainName);
- ULONGLONG ByteOffset1 = 0xFFFFFFFF;
- ULONGLONG ByteOffset2 = 0xEEEEEEEE;
- HANDLE hMpq = NULL;
- HANDLE hFile = NULL;
- char szFullPath[MAX_PATH];
- int nError = ERROR_SUCCESS;
-
- // Create new MPQ
- nError = CreateNewArchive_V2(&Logger, szPlainName, MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES | MPQ_FORMAT_VERSION_4, 4000, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Add one file and flush the archive
- nError = AddFileToMpq(&Logger, hMpq, "AddedFile01.txt", "This is the file data.", MPQ_FILE_COMPRESS);
- SFileCloseArchive(hMpq);
- hMpq = NULL;
- }
-
- // Reopen the MPQ and add another file.
- // The new file must be added to the position of the (listfile)
- if(nError == ERROR_SUCCESS)
- {
- CreateFullPathName(szFullPath, NULL, szPlainName);
- nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Retrieve the position of the (listfile)
- if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hFile))
- {
- SFileGetFileInfo(hFile, SFileInfoByteOffset, &ByteOffset1, sizeof(ULONGLONG), NULL);
- SFileCloseFile(hFile);
- }
- else
- nError = GetLastError();
- }
- }
-
- // Add another file and check its position. It must be at the position of the former listfile
- if(nError == ERROR_SUCCESS)
- {
- const char * szAddedFile = "AddedFile02.txt";
-
- // Add another file
- nError = AddFileToMpq(&Logger, hMpq, szAddedFile, "This is the second added file.", MPQ_FILE_COMPRESS);
-
- // Retrieve the position of the (listfile)
- if(SFileOpenFileEx(hMpq, szAddedFile, 0, &hFile))
- {
- SFileGetFileInfo(hFile, SFileInfoByteOffset, &ByteOffset2, sizeof(ULONGLONG), NULL);
- SFileCloseFile(hFile);
- }
- else
- nError = GetLastError();
- }
-
- // Now check the positions
- if(nError == ERROR_SUCCESS)
- {
- if(ByteOffset1 != ByteOffset2)
- {
- Logger.PrintError("The added file was not written to the position of (listfile)");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Close the archive if needed
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-static int TestCreateArchive_Signed(const char * szPlainName, bool bSignAtCreate)
-{
- TLogHelper Logger("CreateSignedMpq", szPlainName);
- HANDLE hMpq = NULL;
- DWORD dwCreateFlags = MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES | MPQ_FORMAT_VERSION_1;
- DWORD dwSignatures = 0;
- DWORD nVerifyError = 0;
- int nError = ERROR_SUCCESS;
-
- // Method 1: Create the archive as signed
- if(bSignAtCreate)
- dwCreateFlags |= MPQ_CREATE_SIGNATURE;
-
- // Create new MPQ
- nError = CreateNewArchive_V2(&Logger, szPlainName, dwCreateFlags, 4000, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Add one file and flush the archive
- nError = AddFileToMpq(&Logger, hMpq, "AddedFile01.txt", "This is the file data.", MPQ_FILE_COMPRESS);
- }
-
- // Sign the archive with weak signature
- if(nError == ERROR_SUCCESS)
- {
- if(!SFileSignArchive(hMpq, SIGNATURE_TYPE_WEAK))
- nError = ERROR_SUCCESS;
- }
-
- // Reopen the MPQ and add another file.
- // The new file must be added to the position of the (listfile)
- if(nError == ERROR_SUCCESS)
- {
- // Query the signature types
- Logger.PrintProgress("Retrieving signatures ...");
- TestGetFileInfo(&Logger, hMpq, SFileMpqSignatures, &dwSignatures, sizeof(DWORD), NULL, true, ERROR_SUCCESS);
-
- // Verify any of the present signatures
- Logger.PrintProgress("Verifying archive signature ...");
- nVerifyError = SFileVerifyArchive(hMpq);
-
- // Verify the result
- if((dwSignatures != SIGNATURE_TYPE_WEAK) && (nVerifyError != ERROR_WEAK_SIGNATURE_OK))
- {
- Logger.PrintMessage("Weak signature verification error");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Close the archive
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- return nError;
-}
-
-static int TestCreateArchive_MpqEditor(const char * szPlainName, const char * szFileName)
-{
- TLogHelper Logger("CreateMpqEditor", szPlainName);
- HANDLE hMpq = NULL;
- int nError = ERROR_SUCCESS;
-
- // Create new MPQ
- nError = CreateNewArchive_V2(&Logger, szPlainName, MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, 4000, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Flush the archive first
- SFileFlushArchive(hMpq);
-
- // Add one file
- nError = AddFileToMpq(&Logger, hMpq, szFileName, "This is the file data.", MPQ_FILE_COMPRESS);
-
- // Flush the archive again
- SFileFlushArchive(hMpq);
- SFileCloseArchive(hMpq);
- }
- else
- {
- nError = GetLastError();
- }
-
- return nError;
-}
-
-static int TestCreateArchive_FillArchive(const char * szPlainName, DWORD dwCreateFlags)
-{
- TLogHelper Logger("CreateFullMpq", szPlainName);
- const char * szFileData = "TestCreateArchive_FillArchive: Testing file data";
- char szFileName[MAX_PATH];
- HANDLE hMpq = NULL;
- DWORD dwMaxFileCount = 6;
- DWORD dwCompression = MPQ_COMPRESSION_ZLIB;
- DWORD dwFlags = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS;
- int nError;
-
- //
- // Note that StormLib will round the maxfile count
- // up to hash table size (nearest power of two)
- //
- if((dwCreateFlags & MPQ_CREATE_LISTFILE) == 0)
- dwMaxFileCount++;
- if((dwCreateFlags & MPQ_CREATE_ATTRIBUTES) == 0)
- dwMaxFileCount++;
-
- // Create the new MPQ archive
- nError = CreateNewArchive_V2(&Logger, szPlainName, dwCreateFlags, dwMaxFileCount, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Flush the archive first
- SFileFlushArchive(hMpq);
-
- // Add all files
- for(unsigned int i = 0; i < dwMaxFileCount; i++)
- {
- sprintf(szFileName, "AddedFile%03u.txt", i);
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression);
- if(nError != ERROR_SUCCESS)
- break;
- }
-
- // Flush the archive again
- SFileFlushArchive(hMpq);
- }
-
- // Now the MPQ should be full. It must not be possible to add another file
- if(nError == ERROR_SUCCESS)
- {
- nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, ERROR_DISK_FULL);
- assert(nError != ERROR_SUCCESS);
- nError = ERROR_SUCCESS;
- }
-
- // Close the archive to enforce saving all tables
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- hMpq = NULL;
-
- // Reopen the archive again
- if(nError == ERROR_SUCCESS)
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
-
- // The archive should still be full
- if(nError == ERROR_SUCCESS)
- {
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, (dwCreateFlags & MPQ_CREATE_LISTFILE) ? true : false);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? true : false);
- nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, ERROR_DISK_FULL);
- assert(nError != ERROR_SUCCESS);
- nError = ERROR_SUCCESS;
- }
-
- // The (listfile) and (attributes) must be present
- if(nError == ERROR_SUCCESS)
- {
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, (dwCreateFlags & MPQ_CREATE_LISTFILE) ? true : false);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? true : false);
- nError = RemoveMpqFile(&Logger, hMpq, szFileName, ERROR_SUCCESS);
- }
-
- // Now add the file again. This time, it should be possible OK
- if(nError == ERROR_SUCCESS)
- {
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression, ERROR_SUCCESS);
- assert(nError == ERROR_SUCCESS);
- }
-
- // Now add the file again. This time, it should fail
- if(nError == ERROR_SUCCESS)
- {
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression, ERROR_ALREADY_EXISTS);
- assert(nError != ERROR_SUCCESS);
- nError = ERROR_SUCCESS;
- }
-
- // Now add the file again. This time, it should fail
- if(nError == ERROR_SUCCESS)
- {
- nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, dwFlags, dwCompression, ERROR_DISK_FULL);
- assert(nError != ERROR_SUCCESS);
- nError = ERROR_SUCCESS;
- }
-
- // Close the archive and return
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- hMpq = NULL;
-
- // Reopen the archive for the third time to verify that both internal files are there
- if(nError == ERROR_SUCCESS)
- {
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, (dwCreateFlags & MPQ_CREATE_LISTFILE) ? true : false);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? true : false);
- SFileCloseArchive(hMpq);
- }
- }
-
- return nError;
-}
-
-static int TestCreateArchive_IncMaxFileCount(const char * szPlainName)
-{
- TLogHelper Logger("IncMaxFileCount", szPlainName);
- const char * szFileData = "TestCreateArchive_IncMaxFileCount: Testing file data";
- char szFileName[MAX_PATH];
- HANDLE hMpq = NULL;
- DWORD dwMaxFileCount = 1;
- int nError;
-
- // Create the new MPQ
- nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, dwMaxFileCount, &hMpq);
-
- // Now add exactly one file
- if(nError == ERROR_SUCCESS)
- {
- nError = AddFileToMpq(&Logger, hMpq, "AddFile_base.txt", szFileData);
- SFileFlushArchive(hMpq);
- SFileCloseArchive(hMpq);
- }
-
- // Now add 10 files. Each time we cannot add the file due to archive being full,
- // we increment the max file count
- if(nError == ERROR_SUCCESS)
- {
- for(unsigned int i = 0; i < 10; i++)
- {
- // Open the archive again
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
- if(nError != ERROR_SUCCESS)
- break;
-
- // Add one file
- sprintf(szFileName, "AddFile_%04u.txt", i);
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, 0, 0, ERROR_UNDETERMINED_RESULT);
- if(nError != ERROR_SUCCESS)
- {
- // Increment the max file count by one
- dwMaxFileCount = SFileGetMaxFileCount(hMpq) + 1;
- Logger.PrintProgress("Increasing max file count to %u ...", dwMaxFileCount);
- SFileSetMaxFileCount(hMpq, dwMaxFileCount);
-
- // Attempt to create the file again
- nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, 0, 0, ERROR_SUCCESS);
- }
-
- // Compact the archive and close it
- SFileSetCompactCallback(hMpq, CompactCallback, &Logger);
- SFileCompactArchive(hMpq, NULL, false);
- SFileCloseArchive(hMpq);
- if(nError != ERROR_SUCCESS)
- break;
- }
- }
-
- return nError;
-}
-
-static int TestCreateArchive_UnicodeNames()
-{
- TLogHelper Logger("MpqUnicodeName");
- DWORD dwCreateFlags = MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES;
- int nError = ERROR_SUCCESS;
-
- nError = CreateNewArchiveU(&Logger, szUnicodeName1, dwCreateFlags | MPQ_CREATE_ARCHIVE_V1, 15);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- nError = CreateNewArchiveU(&Logger, szUnicodeName2, dwCreateFlags | MPQ_CREATE_ARCHIVE_V2, 58);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- nError = CreateNewArchiveU(&Logger, szUnicodeName3, dwCreateFlags | MPQ_CREATE_ARCHIVE_V3, 15874);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- nError = CreateNewArchiveU(&Logger, szUnicodeName4, dwCreateFlags | MPQ_CREATE_ARCHIVE_V4, 87541);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- nError = CreateNewArchiveU(&Logger, szUnicodeName5, dwCreateFlags | MPQ_CREATE_ARCHIVE_V3, 87541);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- nError = CreateNewArchiveU(&Logger, szUnicodeName5, dwCreateFlags | MPQ_CREATE_ARCHIVE_V2, 87541);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- return nError;
-}
-
-static int TestCreateArchive_FileFlagTest(const char * szPlainName)
-{
- TLogHelper Logger("FileFlagTest", szPlainName);
- HANDLE hMpq = NULL; // Handle of created archive
- char szFileName1[MAX_PATH];
- char szFileName2[MAX_PATH];
- char szFullPath[MAX_PATH];
- const char * szMiddleFile = "FileTest_10.exe";
- LCID LocaleIDs[] = {0x000, 0x405, 0x406, 0x407, 0xFFFF};
- char szArchivedName[MAX_PATH];
- DWORD dwMaxFileCount = 0;
- DWORD dwFileCount = 0;
- size_t i;
- int nError;
-
- // Create paths for local file to be added
- CreateFullPathName(szFileName1, szMpqSubDir, "AddFile.exe");
- CreateFullPathName(szFileName2, szMpqSubDir, "AddFile.bin");
-
- // Create an empty file that will serve as holder for the MPQ
- nError = CreateEmptyFile(&Logger, szPlainName, 0x100000, szFullPath);
-
- // Create new MPQ archive over that file
- if(nError == ERROR_SUCCESS)
- nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V1 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, 17, &hMpq);
-
- // Add the same file multiple times
- if(nError == ERROR_SUCCESS)
- {
- dwMaxFileCount = SFileGetMaxFileCount(hMpq);
- for(i = 0; AddFlags[i] != 0xFFFFFFFF; i++)
- {
- sprintf(szArchivedName, "FileTest_%02u.exe", (unsigned int)i);
- nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName1, AddFlags[i], 0);
- if(nError != ERROR_SUCCESS)
- break;
-
- dwFileCount++;
- }
- }
-
- // Delete a file in the middle of the file table
- if(nError == ERROR_SUCCESS)
- {
- Logger.PrintProgress("Removing file %s ...", szMiddleFile);
- nError = RemoveMpqFile(&Logger, hMpq, szMiddleFile, ERROR_SUCCESS);
- dwFileCount--;
- }
-
- // Add one more file
- if(nError == ERROR_SUCCESS)
- {
- nError = AddLocalFileToMpq(&Logger, hMpq, "FileTest_xx.exe", szFileName1);
- dwFileCount++;
- }
-
- // Try to decrement max file count. This must succeed
- if(nError == ERROR_SUCCESS)
- {
- Logger.PrintProgress("Attempting to decrement max file count ...");
- if(SFileSetMaxFileCount(hMpq, 5))
- nError = Logger.PrintError("Max file count decremented, even if it should fail");
- }
-
- // Add ZeroSize.txt several times under a different locale
- if(nError == ERROR_SUCCESS)
- {
- for(i = 0; LocaleIDs[i] != 0xFFFF; i++)
- {
- bool bMustSucceed = ((dwFileCount + 2) < dwMaxFileCount);
-
- SFileSetLocale(LocaleIDs[i]);
- nError = AddLocalFileToMpq(&Logger, hMpq, "ZeroSize_1.txt", szFileName2);
- if(nError != ERROR_SUCCESS)
- {
- if(bMustSucceed == false)
- nError = ERROR_SUCCESS;
- break;
- }
-
- dwFileCount++;
- }
- }
-
- // Add ZeroSize.txt again several times under a different locale
- if(nError == ERROR_SUCCESS)
- {
- for(i = 0; LocaleIDs[i] != 0xFFFF; i++)
- {
- bool bMustSucceed = ((dwFileCount + 2) < dwMaxFileCount);
-
- SFileSetLocale(LocaleIDs[i]);
- nError = AddLocalFileToMpq(&Logger, hMpq, "ZeroSize_2.txt", szFileName2, 0, 0, bMustSucceed);
- if(nError != ERROR_SUCCESS)
- {
- if(bMustSucceed == false)
- nError = ERROR_SUCCESS;
- break;
- }
-
- dwFileCount++;
- }
- }
-
- // Verify how many files did we add to the MPQ
- if(nError == ERROR_SUCCESS)
- {
- if(dwFileCount + 2 != dwMaxFileCount)
- {
- Logger.PrintErrorVa("Number of files added to MPQ was unexpected (expected %u, added %u)", dwFileCount, dwMaxFileCount - 2);
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Test rename function
- if(nError == ERROR_SUCCESS)
- {
- Logger.PrintProgress("Testing rename files ...");
- SFileSetLocale(LANG_NEUTRAL);
- if(!SFileRenameFile(hMpq, "FileTest_08.exe", "FileTest_08a.exe"))
- nError = Logger.PrintError("Failed to rename the file");
- }
-
- if(nError == ERROR_SUCCESS)
- {
- if(!SFileRenameFile(hMpq, "FileTest_08a.exe", "FileTest_08.exe"))
- nError = Logger.PrintError("Failed to rename the file");
- }
-
- if(nError == ERROR_SUCCESS)
- {
- if(SFileRenameFile(hMpq, "FileTest_10.exe", "FileTest_10a.exe"))
- {
- Logger.PrintError("Rename test succeeded even if it shouldn't");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- if(nError == ERROR_SUCCESS)
- {
- if(SFileRenameFile(hMpq, "FileTest_10a.exe", "FileTest_10.exe"))
- {
- Logger.PrintError("Rename test succeeded even if it shouldn't");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- // Close the archive
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- hMpq = NULL;
-
- // Try to reopen the archive
- if(nError == ERROR_SUCCESS)
- nError = OpenExistingArchive(&Logger, szFullPath, 0, NULL);
- return nError;
-}
-
-static int TestCreateArchive_WaveCompressionsTest(const char * szPlainName, const char * szWaveFile)
-{
- TLogHelper Logger("CompressionsTest", szPlainName);
- HANDLE hMpq = NULL; // Handle of created archive
- char szFileName[MAX_PATH]; // Source file to be added
- char szArchivedName[MAX_PATH];
- DWORD dwCmprCount = sizeof(WaveCompressions) / sizeof(DWORD);
- DWORD dwAddedFiles = 0;
- DWORD dwFoundFiles = 0;
- int nError;
-
- // Create paths for local file to be added
- CreateFullPathName(szFileName, szMpqSubDir, szWaveFile);
-
- // Create new archive
- nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V1 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, 0x40, &hMpq);
-
- // Add the same file multiple times
- if(nError == ERROR_SUCCESS)
- {
- Logger.UserTotal = dwCmprCount;
- for(unsigned int i = 0; i < dwCmprCount; i++)
- {
- sprintf(szArchivedName, "WaveFile_%02u.wav", i + 1);
- nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_SECTOR_CRC, WaveCompressions[i]);
- if(nError != ERROR_SUCCESS)
- break;
-
- Logger.UserCount++;
- dwAddedFiles++;
- }
-
- SFileCloseArchive(hMpq);
- }
-
- // Reopen the archive extract each WAVE file and try to play it
- if(nError == ERROR_SUCCESS)
- {
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_PLAY_WAVES, &dwFoundFiles, NULL);
- SFileCloseArchive(hMpq);
- }
-
- // Check if the number of found files is the same like the number of added files
- // DOn;t forget that there will be (listfile) and (attributes)
- if(dwFoundFiles != (dwAddedFiles + 2))
- {
- Logger.PrintError("Number of found files does not match number of added files.");
- nError = ERROR_FILE_CORRUPT;
- }
- }
-
- return nError;
-}
-
-static int TestCreateArchive_ListFilePos(const char * szPlainName)
-{
- TFileData * pFileData;
- const char * szReaddedFile = "AddedFile_##.txt";
- const char * szFileMask = "AddedFile_%02u.txt";
- TLogHelper Logger("ListFilePos", szPlainName);
- HANDLE hMpq = NULL; // Handle of created archive
- char szArchivedName[MAX_PATH];
- DWORD dwMaxFileCount = 0x0E;
- DWORD dwFileCount = 0;
- size_t i;
- int nError;
-
- // Create a new archive with the limit of 0x20 files
- nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, dwMaxFileCount, &hMpq);
-
- // Add maximum files files
- if(nError == ERROR_SUCCESS)
- {
- for(i = 0; i < dwMaxFileCount; i++)
- {
- sprintf(szArchivedName, szFileMask, i);
- nError = AddFileToMpq(&Logger, hMpq, szArchivedName, "This is a text data.", 0, 0, ERROR_SUCCESS);
- if(nError != ERROR_SUCCESS)
- break;
-
- dwFileCount++;
- }
- }
-
- // Delete few middle files
- if(nError == ERROR_SUCCESS)
- {
- for(i = 0; i < (dwMaxFileCount / 2); i++)
- {
- sprintf(szArchivedName, szFileMask, i);
- nError = RemoveMpqFile(&Logger, hMpq, szArchivedName, ERROR_SUCCESS);
- if(nError != ERROR_SUCCESS)
- break;
-
- dwFileCount--;
- }
- }
-
- // Close the archive
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- hMpq = NULL;
-
- // Reopen the archive to catch any asserts
- if(nError == ERROR_SUCCESS)
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
-
- // Check that (listfile) is at the end
- if(nError == ERROR_SUCCESS)
- {
- pFileData = LoadMpqFile(&Logger, hMpq, LISTFILE_NAME);
- if(pFileData != NULL)
- {
- if(pFileData->dwBlockIndex < dwFileCount)
- Logger.PrintMessage("Unexpected file index of %s", LISTFILE_NAME);
- STORM_FREE(pFileData);
- }
-
- pFileData = LoadMpqFile(&Logger, hMpq, ATTRIBUTES_NAME);
- if(pFileData != NULL)
- {
- if(pFileData->dwBlockIndex <= dwFileCount)
- Logger.PrintMessage("Unexpected file index of %s", ATTRIBUTES_NAME);
- STORM_FREE(pFileData);
- }
-
- // Add new file to the archive. It should be added to the last position
- nError = AddFileToMpq(&Logger, hMpq, szReaddedFile, "This is a re-added file.", 0, 0, ERROR_SUCCESS);
- if(nError == ERROR_SUCCESS)
- {
- // Force update of the tables
- SFileFlushArchive(hMpq);
-
- // Load the file
- pFileData = LoadMpqFile(&Logger, hMpq, szReaddedFile);
- if(pFileData != NULL)
- {
- if(pFileData->dwBlockIndex != dwFileCount)
- Logger.PrintMessage("Unexpected file index of %s", szReaddedFile);
- STORM_FREE(pFileData);
- }
- }
-
- SFileCloseArchive(hMpq);
- }
-
- return nError;
-}
-
-static int TestCreateArchive_BigArchive(const char * szPlainName)
-{
- const char * szFileMask = "AddedFile_%02u.txt";
- TLogHelper Logger("BigMpqTest");
- HANDLE hMpq = NULL; // Handle of created archive
- char szLocalFileName[MAX_PATH];
- char szArchivedName[MAX_PATH];
- DWORD dwMaxFileCount = 0x20;
- DWORD dwAddedCount = 0;
- size_t i;
- int nError;
-
- // Create a new archive with the limit of 0x20 files
- nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V3 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, dwMaxFileCount, &hMpq);
- if(nError == ERROR_SUCCESS)
- {
- // Now add few really big files
- CreateFullPathName(szLocalFileName, szMpqSubDir, "MPQ_1997_v1_Diablo1_DIABDAT.MPQ");
- Logger.UserTotal = (dwMaxFileCount / 2);
-
- for(i = 0; i < dwMaxFileCount / 2; i++)
- {
- sprintf(szArchivedName, szFileMask, i + 1);
- nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szLocalFileName, 0, 0, true);
- if(nError != ERROR_SUCCESS)
- break;
-
- Logger.UserCount++;
- dwAddedCount++;
- }
- }
-
- // Close the archive
- if(hMpq != NULL)
- SFileCloseArchive(hMpq);
- hMpq = NULL;
-
- // Reopen the archive to catch any asserts
- if(nError == ERROR_SUCCESS)
- nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
-
- // Check that (listfile) is at the end
- if(nError == ERROR_SUCCESS)
- {
- CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true);
- CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true);
-
- SFileCloseArchive(hMpq);
- }
-
- return nError;
-}
-
-// "MPQ_2014_v4_Heroes_Replay.MPQ", "AddFile-replay.message.events"
-static int TestModifyArchive_ReplaceFile(const char * szMpqPlainName, const char * szFileName)
-{
- TLogHelper Logger("ModifyTest");
- HANDLE hMpq = NULL;
- const char * szArchivedName;
- char szLocalFileName[MAX_PATH];
- size_t nOffset = 0;
- int nError;
-
- // Open an existing archive
- nError = OpenExistingArchiveWithCopy(&Logger, szMpqPlainName, szMpqPlainName, &hMpq);
-
- // Add the given file
- if(nError == ERROR_SUCCESS)
- {
- // Get the name of archived file
- if(!_strnicmp(szFileName, "AddFile-", 8))
- nOffset = 8;
- szArchivedName = szFileName + nOffset;
-
- // Create the local file name
- CreateFullPathName(szLocalFileName, szMpqSubDir, szFileName);
-
- // Add the file to MPQ
- nError = AddLocalFileToMpq(&Logger, hMpq,
- szArchivedName,
- szLocalFileName,
- MPQ_FILE_REPLACEEXISTING | MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT,
- MPQ_COMPRESSION_ZLIB,
- true);
- }
-
- // Reopen the MPQ and compact it
- if(nError == ERROR_SUCCESS)
- {
- // Compact the archive
- Logger.PrintProgress("Compacting archive %s ...", szMpqPlainName);
- if(!SFileSetCompactCallback(hMpq, CompactCallback, &Logger))
- nError = Logger.PrintError("Failed to compact archive %s", szMpqPlainName);
-
- if(!SFileCompactArchive(hMpq, NULL, 0))
- nError = GetLastError();
-
- SFileCloseArchive(hMpq);
- }
-
- // Try to open the archive again
- if(nError == ERROR_SUCCESS)
- {
- CreateFullPathName(szLocalFileName, NULL, szMpqPlainName);
- nError = OpenExistingArchive(&Logger, szLocalFileName, 0, &hMpq);
- if(nError == ERROR_SUCCESS)
- SFileCloseArchive(hMpq);
- }
-
- return nError;
-}
-
-//-----------------------------------------------------------------------------
-// Comparing two directories, creating links
-
-#define LINK_COMPARE_BLOCK_SIZE 0x200
-
-static int CreateArchiveLinkFile(const char * szFullPath1, const char * szFullPath2, const char * szFileHash)
-{
- TFileStream * pStream;
- char szLinkData[MAX_PATH + 0x80];
- char szLinkFile[MAX_PATH];
- char szLinkPath[MAX_PATH];
- int nLength;
- int nError = ERROR_SUCCESS;
-
- // Construct the link file name
- CalculateRelativePath(szFullPath1, szFullPath2, szLinkPath);
- sprintf(szLinkFile, "%s.link", szFullPath2);
-
- // Format the content of the link file
- nLength = sprintf(szLinkData, "LINK:%s\x0D\x0ASHA1:%s", szLinkPath, szFileHash);
-
- // Create the link file
- pStream = FileStream_CreateFileA(szLinkFile, 0);
- if(pStream == NULL)
- return GetLastError();
-
- // Write the content of the link file
- if(!FileStream_Write(pStream, NULL, szLinkData, (DWORD)nLength))
- nError = GetLastError();
-
- FileStream_Close(pStream);
- return ERROR_SUCCESS;
-}
-
-static int ForEachFile_CreateArchiveLink(const char * szFullPath1, const char * szFullPath2)
-{
- TLogHelper Logger("CreateMpqLink", GetShortPlainName(szFullPath2));
- char szFileHash1[0x40];
- char szFileHash2[0x40];
- int nError;
-
- // Prevent logger from witing any result messages
- Logger.bDontPrintResult = true;
-
- // Create SHA1 of both files
- nError = CalculateFileSha1(&Logger, szFullPath1, szFileHash1);
- if(nError == ERROR_SUCCESS)
- {
- nError = CalculateFileSha1(&Logger, szFullPath2, szFileHash2);
- if(nError == ERROR_SUCCESS)
- {
- // If the hashes are identical, we can create link
- if(!strcmp(szFileHash1, szFileHash2))
- {
- nError = CreateArchiveLinkFile(szFullPath1, szFullPath2, szFileHash1);
- if(nError == ERROR_SUCCESS)
- {
- Logger.PrintMessage("Created link to %s", szFullPath2);
- }
- }
- }
- }
-
- return ERROR_SUCCESS;
-}
-
-//-----------------------------------------------------------------------------
-// Main
-
-int main(int argc, char * argv[])
-{
- int nError = ERROR_SUCCESS;
-
-#if defined(_MSC_VER) && defined(_DEBUG)
- _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
-#endif // defined(_MSC_VER) && defined(_DEBUG)
-
- // Initialize storage and mix the random number generator
- printf("==== Test Suite for StormLib version %s ====\n", STORMLIB_VERSION_STRING);
- nError = InitializeMpqDirectory(argv, argc);
-
- // Not a test, but rather a tool for creating links to duplicated files
-// if(nError == ERROR_SUCCESS)
-// nError = FindFilePairs(ForEachFile_CreateArchiveLink, "2004 - WoW\\06080", "2004 - WoW\\06299");
-/*
- // Search all testing archives and verify their SHA1 hash
- if(nError == ERROR_SUCCESS)
- nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqSubDir);
-
- // Test sparse compression
- if(nError == ERROR_SUCCESS)
- nError = TestSparseCompression();
-
- // Test reading linear file without bitmap
- if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("MPQ_2013_v4_alternate-original.MPQ", 0);
-
- // Test reading linear file without bitmap (read only)
- if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("MPQ_2013_v4_alternate-original.MPQ", STREAM_FLAG_READ_ONLY);
-
- // Test reading linear file with bitmap
- if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("MPQ_2013_v4_alternate-complete.MPQ", STREAM_FLAG_USE_BITMAP);
-
- // Test reading partial file
- if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("part-file://MPQ_2009_v2_WoW_patch.MPQ.part", 0);
-
- // Test reading Block4K file
- if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("blk4-file://streaming/model.MPQ.0", STREAM_PROVIDER_BLOCK4);
-
- // Test reading encrypted file
- if(nError == ERROR_SUCCESS)
- nError = TestFileStreamOperations("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE", STREAM_PROVIDER_MPQE);
-
- // Open a stream, paired with local master. The mirror file is created new
- if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-created.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", false);
-
- // Open a stream, paired with local master. Only part of the mirror exists
- if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-partial.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", true);
-
- // Open a stream, paired with local master. Only part of the mirror exists
- if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-complete.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", true);
-
- // Open a stream, paired with local master
- if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-created.MPQ", "MPQ_2013_v4_alternate-original.MPQ", false);
-
- // Open a stream, paired with local master
- if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-incomplete.MPQ", "MPQ_2013_v4_alternate-incomplete.MPQ", true);
-
- // Open a stream, paired with local master
- if(nError == ERROR_SUCCESS)
- nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-complete.MPQ", "MPQ_2013_v4_alternate-original.MPQ", true);
-
- // Open a stream, paired with remote master (takes hell lot of time!)
-// if(nError == ERROR_SUCCESS)
-// nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "http://www.zezula.net\\mpqs\\alternate.zip", false);
-
- // Search in listfile
- if(nError == ERROR_SUCCESS)
- nError = TestSearchListFile("ListFile_Blizzard.txt");
-
- // Test opening local file with SFileOpenFileEx
- if(nError == ERROR_SUCCESS)
- nError = TestOpenLocalFile("ListFile_Blizzard.txt");
-
- // Test working with an archive that has no listfile
- if(nError == ERROR_SUCCESS)
- nError = TestOpenFile_OpenById("MPQ_1997_v1_Diablo1_DIABDAT.MPQ");
-
- // Open an empty archive (found in WoW cache - it's just a header)
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2012_v2_EmptyMpq.MPQ");
-
- // Open an empty archive (created artificially - it's just a header)
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2013_v4_EmptyMpq.MPQ");
-
- // Open an empty archive (found in WoW cache - it's just a header)
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2013_v4_patch-base-16357.MPQ");
-
- // Open an empty archive (A buggy MPQ with invalid HET entry count)
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2011_v4_InvalidHetEntryCount.MPQ");
-
- // Open a truncated archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2002_v1_BlockTableCut.MPQ");
-
- // Open a MPQ that actually has user data
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2010_v2_HasUserData.s2ma");
-
- // Open an Warcraft III map whose "(attributes)" file has (BlockTableSize-1) entries
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2014_v1_AttributesOneEntryLess.w3x");
-
- // Open a MPQ archive v 3.0
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2010_v3_expansion-locale-frFR.MPQ");
-
- // Open an encrypted archive from Starcraft II installer
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE");
-
- // Open a MPK archive from Longwu online
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPx_2013_v1_LongwuOnline.mpk");
-
- // Open a SQP archive from War of the Immortals
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPx_2013_v1_WarOfTheImmortals.sqp", "ListFile_WarOfTheImmortals.txt");
-
- // Open a partial MPQ with compressed hash table
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("part-file://MPQ_2010_v2_HashTableCompressed.MPQ.part");
-
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.w3x");
-
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_InvalidUserData.w3x");
-
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_InvalidMpqFormat.w3x");
-
- // Open an Warcraft III map locked by the Spazzler protector
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_Spazzler.w3x");
-
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2014_v1_ProtectedMap_Spazzler2.w3x");
-
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2014_v1_ProtectedMap_Spazzler3.w3x");
-
- // Open an Warcraft III map locked by the BOBA protector
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_BOBA.w3m");
-
- // Open an Warcraft III map locked by a protector
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2015_v1_ProtectedMap_KangTooJee.w3x");
-
- // Open an Warcraft III map locked by a protector
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2015_v1_ProtectedMap_Somj2hM16.w3x");
-
- // Open an Warcraft III map locked by Spazy protector
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2015_v1_ProtectedMap_Spazy.w3x");
-
- // Open an protected map
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("MPQ_2015_v1_flem1.w3x");
-
- // Open the multi-file archive with wrong prefix to see how StormLib deals with it
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_WillFail("flat-file://streaming/model.MPQ.0");
-
- // Open an archive that is merged with multiple files
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive("blk4-file://streaming/model.MPQ.0", NULL, true);
-
- // Open every MPQ that we have in the storage
- if(nError == ERROR_SUCCESS)
- nError = FindFiles(ForEachFile_OpenArchive, NULL);
-
- // Test on an archive that has been invalidated by extending an old valid MPQ
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Corrupt("MPQ_2013_vX_Battle.net.MPQ");
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_WoW_OldWorld13286, "OldWorld\\World\\Model.blob", 2);
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_WoW_15050, "World\\Model.blob", 8);
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_WoW_16965, "DBFilesClient\\BattlePetNPCTeamMember.db2", 0);
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_SC2_32283, "TriggerLibs\\natives.galaxy", 6);
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_SC2_34644, "TriggerLibs\\GameData\\GameData.galaxy", 2);
-
- // Open a patched archive with new format of BSDIFF patch
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_SC2_34644_Maps, "Maps\\Campaign\\THorner03.SC2Map\\BankList.xml", 3);
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_SC2_32283_enGB, "LocalizedData\\GameHotkeys.txt", 6);
-
- // Open a patched archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_Patched(PatchList_HS_6898_enGB, "Hearthstone_Data\\Managed\\Assembly-Csharp.dll", 10);
-
- // Check the opening archive for read-only
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_ReadOnly("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", true);
-
- // Check the opening archive for read-only
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_ReadOnly("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false);
-
- // Check the SFileGetFileInfo function
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_GetFileInfo("MPQ_2002_v1_StrongSignature.w3m", "MPQ_2013_v4_SC2_EmptyMap.SC2Map");
-
- // Downloadable MPQ archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_MasterMirror("part-file://MPQ_2009_v1_patch-partial.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", "world\\Azeroth\\DEADMINES\\PASSIVEDOODADS\\GOBLINMELTINGPOT\\DUST2.BLP", false);
-
- // Downloadable MPQ archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "MPQ_2013_v4_alternate-original.MPQ", "alternate\\DUNGEONS\\TEXTURES\\ICECROWN\\GATE\\jlo_IceC_Floor_Thrown.blp", false);
-
- // Check archive signature
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_VerifySignature("MPQ_1999_v1_WeakSignature.exe", "War2Patch_202.exe");
-
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_VerifySignature("MPQ_2003_v1_WeakSignatureEmpty.exe", "WoW-1.2.3.4211-enUS-patch.exe");
-
- // Check archive signature
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_VerifySignature("MPQ_2002_v1_StrongSignature.w3m", "(10)DustwallowKeys.w3m");
-
- // Compact the archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_CraftedUserData("MPQ_2010_v3_expansion-locale-frFR.MPQ", "StormLibTest_CraftedMpq1_v3.mpq");
-
- // Open a MPQ (add custom user data to it)
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_SC2_EmptyMap.SC2Map", "StormLibTest_CraftedMpq2_v4.mpq");
-
- // Open a MPQ (add custom user data to it)
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_expansion1.MPQ", "StormLibTest_CraftedMpq3_v4.mpq");
-
- if(nError == ERROR_SUCCESS)
- nError = TestAddFile_FullTable("MPQ_2014_v1_out1.w3x");
-
- if(nError == ERROR_SUCCESS)
- nError = TestAddFile_FullTable("MPQ_2014_v1_out2.w3x");
-
- // Test modifying file with no (listfile) and no (attributes)
- if(nError == ERROR_SUCCESS)
- nError = TestAddFile_ListFileTest("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false, false);
-
- // Test modifying an archive that contains (listfile) and (attributes)
- if(nError == ERROR_SUCCESS)
- nError = TestAddFile_ListFileTest("MPQ_2013_v4_SC2_EmptyMap.SC2Map", true, true);
-
- // Create an empty archive v2
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_EmptyMpq("StormLibTest_EmptyMpq_v2.mpq", MPQ_CREATE_ARCHIVE_V2 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES);
-
- // Create an empty archive v4
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_EmptyMpq("StormLibTest_EmptyMpq_v4.mpq", MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES);
-
- // Test creating of an archive the same way like MPQ Editor does
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_TestGaps("StormLibTest_GapsTest.mpq");
-
- // Sign an existing non-signed archive
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_SignExisting("MPQ_1998_v1_StarDat.mpq");
-
- // Open a signed archive, add a file and verify the signature
- if(nError == ERROR_SUCCESS)
- nError = TestOpenArchive_ModifySigned("MPQ_1999_v1_WeakSignature.exe", "War2Patch_202.exe");
-
- // Create new archive and sign it
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_Signed("MPQ_1999_v1_WeakSigned1.mpq", true);
-
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_Signed("MPQ_1999_v1_WeakSigned2.mpq", false);
-
- // Test creating of an archive the same way like MPQ Editor does
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_MpqEditor("StormLibTest_MpqEditorTest.mpq", "AddedFile.exe");
-
- // Create an archive and fill it with files up to the max file count
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", 0);
-
- // Create an archive and fill it with files up to the max file count
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", MPQ_CREATE_LISTFILE);
-
- // Create an archive and fill it with files up to the max file count
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", MPQ_CREATE_ATTRIBUTES);
-
- // Create an archive and fill it with files up to the max file count
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", MPQ_CREATE_ATTRIBUTES | MPQ_CREATE_LISTFILE);
-
- // Create an archive, and increment max file count several times
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_IncMaxFileCount("StormLibTest_IncMaxFileCount.mpq");
-
- // Create a MPQ archive with UNICODE names
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_UnicodeNames();
-
- // Create a MPQ file, add files with various flags
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_FileFlagTest("StormLibTest_FileFlagTest.mpq");
-
- // Create a MPQ file, add a mono-WAVE file with various compressions
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_WaveCompressionsTest("StormLibTest_AddWaveMonoTest.mpq", "AddFile-Mono.wav");
-
- // Create a MPQ file, add a mono-WAVE with 8 bits per sample file with various compressions
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_WaveCompressionsTest("StormLibTest_AddWaveMonoBadTest.mpq", "AddFile-MonoBad.wav");
-
- // Create a MPQ file, add a stereo-WAVE file with various compressions
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_WaveCompressionsTest("StormLibTest_AddWaveStereoTest.mpq", "AddFile-Stereo.wav");
-
- // Check if the listfile is always created at the end of the file table in the archive
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_ListFilePos("StormLibTest_ListFilePos.mpq");
-
- // Open a MPQ (add custom user data to it)
- if(nError == ERROR_SUCCESS)
- nError = TestCreateArchive_BigArchive("StormLibTest_BigArchive_v4.mpq");
-*/
- // Test replacing a file with zero size file
- if(nError == ERROR_SUCCESS)
- nError = TestModifyArchive_ReplaceFile("MPQ_2014_v4_Base.StormReplay", "AddFile-replay.message.events");
-
-#ifdef _MSC_VER
- _CrtDumpMemoryLeaks();
-#endif // _MSC_VER
-
- return nError;
-}
+/*****************************************************************************/
+/* StormTest.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Test module for StormLib */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 25.03.03 1.00 Lad The first version of StormTest.cpp */
+/*****************************************************************************/
+
+#define _CRT_NON_CONFORMING_SWPRINTFS
+#define _CRT_SECURE_NO_DEPRECATE
+#define __INCLUDE_CRYPTOGRAPHY__
+#define __STORMLIB_SELF__ // Don't use StormLib.lib
+#include <stdio.h>
+
+#ifdef _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include "../src/StormLib.h"
+#include "../src/StormCommon.h"
+
+#include "TLogHelper.cpp" // Helper class for showing test results
+
+#ifdef _MSC_VER
+#pragma warning(disable: 4505) // 'XXX' : unreferenced local function has been removed
+#pragma comment(lib, "winmm.lib")
+#endif
+
+#ifdef PLATFORM_LINUX
+#include <dirent.h>
+#endif
+
+//------------------------------------------------------------------------------
+// Defines
+
+#ifdef PLATFORM_WINDOWS
+#define WORK_PATH_ROOT "E:\\Multimedia\\MPQs"
+#endif
+
+#ifdef PLATFORM_LINUX
+#define WORK_PATH_ROOT "/home/ladik/MPQs"
+#endif
+
+#ifdef PLATFORM_MAC
+#define WORK_PATH_ROOT "/Users/sam/StormLib/test"
+#endif
+
+// Global for the work MPQ
+static const char * szMpqSubDir = "1995 - Test MPQs";
+static const char * szMpqPatchDir = "1995 - Test MPQs\\patches";
+
+typedef int (*FIND_FILE_CALLBACK)(const char * szFullPath);
+typedef int (*FIND_PAIR_CALLBACK)(const char * szFullPath1, const char * szFullPath2);
+
+#define ERROR_UNDETERMINED_RESULT 0xC000FFFF
+
+//-----------------------------------------------------------------------------
+// Testing data
+
+static DWORD AddFlags[] =
+{
+// Compression Encryption Fixed key Single Unit Sector CRC
+ 0 | 0 | 0 | 0 | 0,
+ 0 | MPQ_FILE_ENCRYPTED | 0 | 0 | 0,
+ 0 | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0,
+ 0 | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0,
+ 0 | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0,
+ 0 | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_IMPLODE | 0 | 0 | 0 | 0,
+ MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | 0 | 0,
+ MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0,
+ MPQ_FILE_IMPLODE | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_IMPLODE | 0 | 0 | 0 | MPQ_FILE_SECTOR_CRC,
+ MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | 0 | MPQ_FILE_SECTOR_CRC,
+ MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | MPQ_FILE_SECTOR_CRC,
+ MPQ_FILE_COMPRESS | 0 | 0 | 0 | 0,
+ MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | 0 | 0,
+ MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0,
+ MPQ_FILE_COMPRESS | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0,
+ MPQ_FILE_COMPRESS | 0 | 0 | 0 | MPQ_FILE_SECTOR_CRC,
+ MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | 0 | MPQ_FILE_SECTOR_CRC,
+ MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | MPQ_FILE_SECTOR_CRC,
+ 0xFFFFFFFF
+};
+
+static DWORD WaveCompressions[] =
+{
+ MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN,
+ MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN,
+ MPQ_COMPRESSION_PKWARE,
+ MPQ_COMPRESSION_ZLIB,
+ MPQ_COMPRESSION_BZIP2
+};
+
+static const wchar_t szUnicodeName1[] = { // Czech
+ 0x010C, 0x0065, 0x0073, 0x006B, 0x00FD, _T('.'), _T('m'), _T('p'), _T('q'), 0
+};
+
+static const wchar_t szUnicodeName2[] = { // Russian
+ 0x0420, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, _T('.'), _T('m'), _T('p'), _T('q'), 0
+};
+
+static const wchar_t szUnicodeName3[] = { // Greek
+ 0x03B5, 0x03BB, 0x03BB, 0x03B7, 0x03BD, 0x03B9, 0x03BA, 0x03AC, _T('.'), _T('m'), _T('p'), _T('q'), 0
+};
+
+static const wchar_t szUnicodeName4[] = { // Chinese
+ 0x65E5, 0x672C, 0x8A9E, _T('.'), _T('m'), _T('p'), _T('q'), 0
+};
+
+static const wchar_t szUnicodeName5[] = { // Japanese
+ 0x7B80, 0x4F53, 0x4E2D, 0x6587, _T('.'), _T('m'), _T('p'), _T('q'), 0
+};
+
+static const wchar_t szUnicodeName6[] = { // Arabic
+ 0x0627, 0x0644, 0x0639, 0x0639, 0x0631, 0x0628, 0x064A, 0x0629, _T('.'), _T('m'), _T('p'), _T('q'), 0
+};
+
+static const char * PatchList_WoW_OldWorld13286[] =
+{
+ "MPQ_2012_v4_OldWorld.MPQ",
+ "wow-update-oldworld-13154.MPQ",
+ "wow-update-oldworld-13286.MPQ",
+ NULL
+};
+
+static const char * PatchList_WoW_15050[] =
+{
+ "MPQ_2013_v4_world.MPQ",
+ "wow-update-13164.MPQ",
+ "wow-update-13205.MPQ",
+ "wow-update-13287.MPQ",
+ "wow-update-13329.MPQ",
+ "wow-update-13596.MPQ",
+ "wow-update-13623.MPQ",
+ "wow-update-base-13914.MPQ",
+ "wow-update-base-14007.MPQ",
+ "wow-update-base-14333.MPQ",
+ "wow-update-base-14480.MPQ",
+ "wow-update-base-14545.MPQ",
+ "wow-update-base-14946.MPQ",
+ "wow-update-base-15005.MPQ",
+ "wow-update-base-15050.MPQ",
+ NULL
+};
+
+static const char * PatchList_WoW_16965[] =
+{
+ "MPQ_2013_v4_locale-enGB.MPQ",
+ "wow-update-enGB-16016.MPQ",
+ "wow-update-enGB-16048.MPQ",
+ "wow-update-enGB-16057.MPQ",
+ "wow-update-enGB-16309.MPQ",
+ "wow-update-enGB-16357.MPQ",
+ "wow-update-enGB-16516.MPQ",
+ "wow-update-enGB-16650.MPQ",
+ "wow-update-enGB-16844.MPQ",
+ "wow-update-enGB-16965.MPQ",
+ NULL
+};
+
+static const char * PatchList_SC2_32283[] =
+{
+ "MPQ_2013_v4_Base1.SC2Data",
+ "s2-update-base-23258.MPQ",
+ "s2-update-base-24540.MPQ",
+ "s2-update-base-26147.MPQ",
+ "s2-update-base-28522.MPQ",
+ "s2-update-base-30508.MPQ",
+ "s2-update-base-32283.MPQ",
+ NULL
+};
+
+static const char * PatchList_SC2_34644[] =
+{
+ "MPQ_2013_v4_Base1.SC2Data",
+ "s2-update-base-23258.MPQ",
+ "s2-update-base-24540.MPQ",
+ "s2-update-base-26147.MPQ",
+ "s2-update-base-28522.MPQ",
+ "s2-update-base-32384.MPQ",
+ "s2-update-base-34644.MPQ",
+ NULL
+};
+
+static const char * PatchList_SC2_34644_Maps[] =
+{
+ "MPQ_2013_v4_Base3.SC2Maps",
+ "s2-update-base-23258.MPQ",
+ "s2-update-base-24540.MPQ",
+ "s2-update-base-26147.MPQ",
+ "s2-update-base-28522.MPQ",
+ "s2-update-base-32384.MPQ",
+ "s2-update-base-34644.MPQ",
+ NULL
+};
+
+static const char * PatchList_SC2_32283_enGB[] =
+{
+ "MPQ_2013_v4_enGB.SC2Data",
+ "s2-update-enGB-23258.MPQ",
+ "s2-update-enGB-24540.MPQ",
+ "s2-update-enGB-26147.MPQ",
+ "s2-update-enGB-28522.MPQ",
+ "s2-update-enGB-30508.MPQ",
+ "s2-update-enGB-32283.MPQ",
+ NULL
+};
+
+static const char * PatchList_HS_6898_enGB[] =
+{
+ "MPQ_2014_v4_base-Win.MPQ",
+ "hs-0-5314-Win-final.MPQ",
+ "hs-5314-5435-Win-final.MPQ",
+ "hs-5435-5506-Win-final.MPQ",
+ "hs-5506-5834-Win-final.MPQ",
+ "hs-5834-6024-Win-final.MPQ",
+ "hs-6024-6141-Win-final.MPQ",
+ "hs-6141-6187-Win-final.MPQ",
+ "hs-6187-6284-Win-final.MPQ",
+ "hs-6284-6485-Win-final.MPQ",
+ "hs-6485-6898-Win-final.MPQ",
+ NULL
+};
+
+//-----------------------------------------------------------------------------
+// Local file functions
+
+// Definition of the path separator
+#ifdef PLATFORM_WINDOWS
+#define PATH_SEPARATOR '\\' // Path separator for Windows platforms
+#else
+#define PATH_SEPARATOR '/' // Path separator for Windows platforms
+#endif
+
+// This must be the directory where our test MPQs are stored.
+// We also expect a subdirectory named
+static char szMpqDirectory[MAX_PATH+1];
+size_t cchMpqDirectory = 0;
+
+static bool IsFullPath(const char * szFileName)
+{
+#ifdef PLATFORM_WINDOWS
+ if(('A' <= szFileName[0] && szFileName[0] <= 'Z') || ('a' <= szFileName[0] && szFileName[0] <= 'z'))
+ {
+ return (szFileName[1] == ':' && szFileName[2] == PATH_SEPARATOR);
+ }
+#endif
+
+ szFileName = szFileName;
+ return false;
+}
+
+static bool IsMpqExtension(const char * szFileName)
+{
+ const char * szExtension = strrchr(szFileName, '.');
+
+ if(szExtension != NULL)
+ {
+ if(!_stricmp(szExtension, ".mpq"))
+ return true;
+ if(!_stricmp(szExtension, ".w3m"))
+ return true;
+ if(!_stricmp(szExtension, ".w3x"))
+ return true;
+ if(!_stricmp(szExtension, ".mpqe"))
+ return true;
+ if(!_stricmp(szExtension, ".part"))
+ return true;
+ if(!_stricmp(szExtension, ".sv"))
+ return true;
+ if(!_stricmp(szExtension, ".s2ma"))
+ return true;
+ if(!_stricmp(szExtension, ".SC2Map"))
+ return true;
+ if(!_stricmp(szExtension, ".0")) // .MPQ.0
+ return true;
+// if(!_stricmp(szExtension, ".link"))
+// return true;
+ }
+
+ return false;
+}
+
+static void AddStringBeforeExtension(char * szBuffer, const char * szFileName, const char * szExtraString)
+{
+ const char * szExtension;
+ size_t nLength;
+
+ // Get the extension
+ szExtension = strrchr(szFileName, '.');
+ if(szExtension == NULL)
+ szExtension = szFileName + strlen(szFileName);
+ nLength = (size_t)(szExtension - szFileName);
+
+ // Copy the part before extension
+ memcpy(szBuffer, szFileName, nLength);
+ szFileName += nLength;
+ szBuffer += nLength;
+
+ // Append the extra data
+ if(szExtraString != NULL)
+ strcpy(szBuffer, szExtraString);
+
+ // Append the rest of the file name
+ strcat(szBuffer, szFileName);
+}
+
+static bool CompareBlocks(LPBYTE pbBlock1, LPBYTE pbBlock2, DWORD dwLength, DWORD * pdwDifference)
+{
+ for(DWORD i = 0; i < dwLength; i++)
+ {
+ if(pbBlock1[i] != pbBlock2[i])
+ {
+ pdwDifference[0] = i;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static size_t ConvertSha1ToText(const unsigned char * sha1_digest, char * szSha1Text)
+{
+ const char * szTable = "0123456789abcdef";
+
+ for(size_t i = 0; i < SHA1_DIGEST_SIZE; i++)
+ {
+ *szSha1Text++ = szTable[(sha1_digest[0] >> 0x04)];
+ *szSha1Text++ = szTable[(sha1_digest[0] & 0x0F)];
+ sha1_digest++;
+ }
+
+ *szSha1Text = 0;
+ return (SHA1_DIGEST_SIZE * 2);
+}
+
+static int GetPathSeparatorCount(const char * szPath)
+{
+ int nSeparatorCount = 0;
+
+ while(szPath[0] != 0)
+ {
+ if(szPath[0] == '\\' || szPath[0] == '/')
+ nSeparatorCount++;
+ szPath++;
+ }
+
+ return nSeparatorCount;
+}
+
+static const char * FindNextPathPart(const char * szPath, size_t nPartCount)
+{
+ const char * szPathPart = szPath;
+
+ while(szPath[0] != 0 && nPartCount > 0)
+ {
+ // Is there path separator?
+ if(szPath[0] == '\\' || szPath[0] == '/')
+ {
+ szPathPart = szPath + 1;
+ nPartCount--;
+ }
+
+ // Move to the next letter
+ szPath++;
+ }
+
+ return szPathPart;
+}
+
+static const char * GetShortPlainName(const char * szFileName)
+{
+ const char * szPlainName = FindNextPathPart(szFileName, 1000);
+ const char * szPlainEnd = szFileName + strlen(szFileName);
+
+ // If the name is still too long, cut it
+ if((szPlainEnd - szPlainName) > 50)
+ szPlainName = szPlainEnd - 50;
+
+ return szPlainName;
+}
+
+static void CopyPathPart(char * szBuffer, const char * szPath)
+{
+ while(szPath[0] != 0)
+ {
+ szBuffer[0] = (szPath[0] == '\\' || szPath[0] == '/') ? '/' : szPath[0];
+ szBuffer++;
+ szPath++;
+ }
+
+ *szBuffer = 0;
+}
+
+static bool CopyStringAndVerifyConversion(
+ const TCHAR * szFoundFile,
+ TCHAR * szBufferT,
+ char * szBufferA)
+{
+ // Convert the TCHAR name to ANSI name
+ CopyFileName(szBufferA, szFoundFile, _tcslen(szFoundFile));
+ CopyFileName(szBufferT, szBufferA, strlen(szBufferA));
+
+ // Compare both TCHAR strings
+ return (_tcsicmp(szBufferT, szFoundFile) == 0) ? true : false;
+}
+
+static void CalculateRelativePath(const char * szFullPath1, const char * szFullPath2, char * szBuffer)
+{
+ const char * szPathPart1 = szFullPath1;
+ const char * szPathPart2 = szFullPath2;
+ const char * szNextPart1;
+ const char * szNextPart2;
+ int nEqualParts = 0;
+ int nStepsUp = 0;
+
+ // Parse both paths and find all path parts that are equal
+ for(;;)
+ {
+ // Find the next part of the first path
+ szNextPart1 = FindNextPathPart(szPathPart1, 1);
+ if(szNextPart1 == szPathPart1)
+ break;
+
+ szNextPart2 = FindNextPathPart(szPathPart2, 1);
+ if(szNextPart2 == szPathPart2)
+ break;
+
+ // Are these equal?
+ if((szNextPart2 - szPathPart2) != (szNextPart1 - szPathPart1))
+ break;
+ if(_strnicmp(szPathPart1, szPathPart2, (szNextPart1 - szPathPart1 - 1)))
+ break;
+
+ // Increment the number of path parts that are equal
+ szPathPart1 = szNextPart1;
+ szPathPart2 = szNextPart2;
+ nEqualParts++;
+ }
+
+ // If we found at least one equal part, we can create relative path
+ if(nEqualParts != 0)
+ {
+ // Calculate how many steps up we need to go
+ nStepsUp = GetPathSeparatorCount(szPathPart2);
+
+ // Append "../" nStepsUp-times
+ for(int i = 0; i < nStepsUp; i++)
+ {
+ *szBuffer++ = '.';
+ *szBuffer++ = '.';
+ *szBuffer++ = '/';
+ }
+
+ // Append the rest of the path. Also change DOS backslashes to slashes
+ CopyPathPart(szBuffer, szPathPart1);
+ return;
+ }
+
+ // Failed. Just copy the source path as it is
+ strcpy(szBuffer, szFullPath1);
+}
+
+static TFileStream * FileStream_OpenFileA(const char * szFileName, DWORD dwStreamFlags)
+{
+ TCHAR szFileNameT[MAX_PATH];
+
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ return FileStream_OpenFile(szFileNameT, dwStreamFlags);
+}
+
+static TFileStream * FileStream_CreateFileA(const char * szFileName, DWORD dwStreamFlags)
+{
+ TCHAR szFileNameT[MAX_PATH];
+
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ return FileStream_CreateFile(szFileNameT, dwStreamFlags);
+}
+
+static size_t FileStream_PrefixA(const char * szFileName, DWORD * pdwProvider)
+{
+ TCHAR szFileNameT[MAX_PATH];
+ size_t nPrefixLength = 0;
+
+ if(szFileName != NULL)
+ {
+ CopyFileName(szFileNameT, szFileName, strlen(szFileName));
+ nPrefixLength = FileStream_Prefix(szFileNameT, pdwProvider);
+ }
+
+ return nPrefixLength;
+}
+
+static void CreateFullPathName(char * szBuffer, const char * szSubDir, const char * szNamePart1, const char * szNamePart2 = NULL)
+{
+ char * szSaveBuffer = szBuffer;
+ size_t nPrefixLength = 0;
+ size_t nLength;
+ DWORD dwProvider = 0;
+ bool bIsFullPath = false;
+ char chSeparator = PATH_SEPARATOR;
+
+ // Determine the path prefix
+ if(szNamePart1 != NULL)
+ {
+ nPrefixLength = FileStream_PrefixA(szNamePart1, &dwProvider);
+ if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
+ {
+ bIsFullPath = true;
+ chSeparator = '/';
+ }
+ else
+ bIsFullPath = IsFullPath(szNamePart1 + nPrefixLength);
+ }
+
+ // Copy the MPQ prefix, if any
+ if(nPrefixLength > 0)
+ {
+ memcpy(szBuffer, szNamePart1, nPrefixLength);
+ szSaveBuffer += nPrefixLength;
+ szNamePart1 += nPrefixLength;
+ szBuffer += nPrefixLength;
+ }
+
+ // If the given name is not a full path, copy the MPQ directory
+ if(bIsFullPath == false)
+ {
+ // Copy the master MPQ directory
+ memcpy(szBuffer, szMpqDirectory, cchMpqDirectory);
+ szBuffer += cchMpqDirectory;
+
+ // Append the subdirectory, if any
+ if(szSubDir != NULL && (nLength = strlen(szSubDir)) != 0)
+ {
+ // No leading or trailing separator are allowed
+ assert(szSubDir[0] != '/' && szSubDir[0] != '\\');
+ assert(szSubDir[nLength - 1] != '/' && szSubDir[nLength - 1] != '\\');
+
+ // Append file path separator
+ *szBuffer++ = PATH_SEPARATOR;
+
+ // Append the subdirectory
+ memcpy(szBuffer, szSubDir, nLength);
+ szBuffer += nLength;
+ }
+ }
+
+ // Copy the file name, if any
+ if(szNamePart1 != NULL && (nLength = strlen(szNamePart1)) != 0)
+ {
+ // Path separators are not allowed in the name part
+ assert(szNamePart1[0] != '\\' && szNamePart1[0] != '/');
+ assert(szNamePart1[nLength - 1] != '/' && szNamePart1[nLength - 1] != '\\');
+
+ // Append file path separator
+ if(bIsFullPath == false)
+ *szBuffer++ = PATH_SEPARATOR;
+
+ // Copy the file name
+ memcpy(szBuffer, szNamePart1, nLength);
+ szBuffer += nLength;
+ }
+
+ // Append the second part of the name
+ if(szNamePart2 != NULL && (nLength = strlen(szNamePart2)) != 0)
+ {
+ // Copy the file name
+ memcpy(szBuffer, szNamePart2, nLength);
+ szBuffer += nLength;
+ }
+
+ // Normalize the path separators
+ while(szSaveBuffer < szBuffer)
+ {
+ szSaveBuffer[0] = (szSaveBuffer[0] != '/' && szSaveBuffer[0] != '\\') ? szSaveBuffer[0] : chSeparator;
+ szSaveBuffer++;
+ }
+
+ // Terminate the buffer with zero
+ *szBuffer = 0;
+}
+
+static int CalculateFileSha1(TLogHelper * pLogger, const char * szFullPath, char * szFileSha1)
+{
+ TFileStream * pStream;
+ unsigned char sha1_digest[SHA1_DIGEST_SIZE];
+ const char * szShortPlainName = GetShortPlainName(szFullPath);
+ hash_state sha1_state;
+ ULONGLONG ByteOffset = 0;
+ ULONGLONG FileSize = 0;
+ BYTE * pbFileBlock;
+ DWORD cbBytesToRead;
+ DWORD cbFileBlock = 0x100000;
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user
+ pLogger->PrintProgress("Hashing file %s", szShortPlainName);
+ szFileSha1[0] = 0;
+
+ // Open the file to be verified
+ pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY);
+ if(pStream != NULL)
+ {
+ // Retrieve the size of the file
+ FileStream_GetSize(pStream, &FileSize);
+
+ // Allocate the buffer for loading file parts
+ pbFileBlock = STORM_ALLOC(BYTE, cbFileBlock);
+ if(pbFileBlock != NULL)
+ {
+ // Initialize SHA1 calculation
+ sha1_init(&sha1_state);
+
+ // Calculate the SHA1 of the file
+ while(ByteOffset < FileSize)
+ {
+ // Notify the user
+ pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize);
+
+ // Load the file block
+ cbBytesToRead = ((FileSize - ByteOffset) > cbFileBlock) ? cbFileBlock : (DWORD)(FileSize - ByteOffset);
+ if(!FileStream_Read(pStream, &ByteOffset, pbFileBlock, cbBytesToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Add to SHA1
+ sha1_process(&sha1_state, pbFileBlock, cbBytesToRead);
+ ByteOffset += cbBytesToRead;
+ }
+
+ // Notify the user
+ pLogger->PrintProgress("Hashing file %s (%I64u of %I64u)", szShortPlainName, ByteOffset, FileSize);
+
+ // Finalize SHA1
+ sha1_done(&sha1_state, sha1_digest);
+
+ // Convert the SHA1 to ANSI text
+ ConvertSha1ToText(sha1_digest, szFileSha1);
+ STORM_FREE(pbFileBlock);
+ }
+
+ FileStream_Close(pStream);
+ }
+
+ // If we calculated something, return OK
+ if(nError == ERROR_SUCCESS && szFileSha1[0] == 0)
+ nError = ERROR_CAN_NOT_COMPLETE;
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Directory search
+
+static HANDLE InitDirectorySearch(const char * szDirectory)
+{
+#ifdef PLATFORM_WINDOWS
+
+ WIN32_FIND_DATA wf;
+ HANDLE hFind;
+ TCHAR szSearchMask[MAX_PATH];
+
+ // Keep compilers happy
+ CopyFileName(szSearchMask, szDirectory, strlen(szDirectory));
+ _tcscat(szSearchMask, _T("\\*"));
+
+ // Construct the directory mask
+ hFind = FindFirstFile(szSearchMask, &wf);
+ return (hFind != INVALID_HANDLE_VALUE) ? hFind : NULL;
+
+#endif
+
+#ifdef PLATFORM_LINUX
+
+ // Keep compilers happy
+ return (HANDLE)opendir(szDirectory);
+
+#endif
+}
+
+static bool SearchDirectory(HANDLE hFind, char * szDirEntry, bool & IsDirectory)
+{
+#ifdef PLATFORM_WINDOWS
+
+ WIN32_FIND_DATA wf;
+ TCHAR szDirEntryT[MAX_PATH];
+ char szDirEntryA[MAX_PATH];
+
+ __SearchNextEntry:
+
+ // Search for the hnext entry.
+ if(FindNextFile(hFind, &wf))
+ {
+ // Verify if the directory entry is an UNICODE name that would be destroyed
+ // by Unicode->ANSI->Unicode conversion
+ if(CopyStringAndVerifyConversion(wf.cFileName, szDirEntryT, szDirEntryA) == false)
+ goto __SearchNextEntry;
+
+ IsDirectory = (wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false;
+ CopyFileName(szDirEntry, wf.cFileName, _tcslen(wf.cFileName));
+ return true;
+ }
+
+ return false;
+
+#endif
+
+#ifdef PLATFORM_LINUX
+
+ struct dirent * directory_entry;
+
+ directory_entry = readdir((DIR *)hFind);
+ if(directory_entry != NULL)
+ {
+ IsDirectory = (directory_entry->d_type == DT_DIR) ? true : false;
+ strcpy(szDirEntry, directory_entry->d_name);
+ return true;
+ }
+
+ return false;
+
+#endif
+}
+
+static void FreeDirectorySearch(HANDLE hFind)
+{
+#ifdef PLATFORM_WINDOWS
+ FindClose(hFind);
+#endif
+
+#ifdef PLATFORM_LINUX
+ closedir((DIR *)hFind);
+#endif
+}
+
+static int FindFilesInternal(FIND_FILE_CALLBACK pfnTest, char * szDirectory)
+{
+ HANDLE hFind;
+ char * szPlainName;
+ size_t nLength;
+ char szDirEntry[MAX_PATH];
+ bool IsDirectory = false;
+ int nError = ERROR_SUCCESS;
+
+ if(szDirectory != NULL)
+ {
+ // Initiate directory search
+ hFind = InitDirectorySearch(szDirectory);
+ if(hFind != NULL)
+ {
+ // Append slash at the end of the directory name
+ nLength = strlen(szDirectory);
+ szDirectory[nLength++] = PATH_SEPARATOR;
+ szPlainName = szDirectory + nLength;
+
+ // Skip the first entry, since it's always "." or ".."
+ while(SearchDirectory(hFind, szDirEntry, IsDirectory) && nError == ERROR_SUCCESS)
+ {
+ // Copy the directory entry name to both names
+ strcpy(szPlainName, szDirEntry);
+
+ // Found a directory?
+ if(IsDirectory)
+ {
+ if(szDirEntry[0] != '.')
+ {
+ nError = FindFilesInternal(pfnTest, szDirectory);
+ }
+ }
+ else
+ {
+ if(pfnTest != NULL)
+ {
+ nError = pfnTest(szDirectory);
+ }
+ }
+ }
+
+ FreeDirectorySearch(hFind);
+ }
+ }
+
+ // Free the path buffer, if any
+ return nError;
+}
+
+static int FindFiles(FIND_FILE_CALLBACK pfnFindFile, const char * szSubDirectory)
+{
+ char szWorkBuff[MAX_PATH];
+
+ CreateFullPathName(szWorkBuff, szSubDirectory, NULL);
+ return FindFilesInternal(pfnFindFile, szWorkBuff);
+}
+
+static int FindFilePairsInternal(
+ FIND_PAIR_CALLBACK pfnFilePair,
+ char * szSource,
+ char * szTarget)
+{
+ char * szPlainName1;
+ char * szPlainName2;
+ int nError = ERROR_SUCCESS;
+
+ // Setup the search masks
+ strcat(szSource, "\\*");
+ szPlainName1 = strrchr(szSource, '*');
+ strcat(szTarget, "\\*");
+ szPlainName2 = strrchr(szTarget, '*');
+
+ // If both paths are OK, perform the search
+ if(szPlainName1 != NULL && szPlainName2 != NULL)
+ {
+#ifdef PLATFORM_WINDOWS
+ WIN32_FIND_DATAA wf;
+ HANDLE hFind;
+
+ // Search the second directory
+ hFind = FindFirstFileA(szTarget, &wf);
+ if(hFind != INVALID_HANDLE_VALUE)
+ {
+ // Skip the first entry, since it's always "." or ".."
+ while(FindNextFileA(hFind, &wf) && nError == ERROR_SUCCESS)
+ {
+ // Found a directory?
+ if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ if(wf.cFileName[0] != '.')
+ {
+ strcpy(szPlainName1, wf.cFileName);
+ strcpy(szPlainName2, wf.cFileName);
+ nError = FindFilePairsInternal(pfnFilePair, szSource, szTarget);
+ }
+ }
+ else
+ {
+ if(pfnFilePair != NULL)
+ {
+ strcpy(szPlainName1, wf.cFileName);
+ strcpy(szPlainName2, wf.cFileName);
+ nError = pfnFilePair(szSource, szTarget);
+ }
+ }
+ }
+
+ FindClose(hFind);
+ }
+#endif
+ }
+
+ return nError;
+}
+
+static int FindFilePairs(FIND_PAIR_CALLBACK pfnFindPair, const char * szSourceSubDir, const char * szTargetSubDir)
+{
+ char szSource[MAX_PATH];
+ char szTarget[MAX_PATH];
+
+ // Create the source search mask
+ CreateFullPathName(szSource, szSourceSubDir, NULL);
+ CreateFullPathName(szTarget, szTargetSubDir, NULL);
+ return FindFilePairsInternal(pfnFindPair, szSource, szTarget);
+}
+
+static int InitializeMpqDirectory(char * argv[], int argc)
+{
+ TLogHelper Logger("InitWorkDir");
+ TFileStream * pStream;
+ const char * szWhereFrom = NULL;
+ const char * szDirName;
+ char szFullPath[MAX_PATH];
+
+ // Retrieve the name of the MPQ directory
+ if(argc > 1 && argv[1] != NULL)
+ {
+ szWhereFrom = "command line";
+ szDirName = argv[1];
+ }
+ else
+ {
+ szWhereFrom = "default";
+ szDirName = WORK_PATH_ROOT;
+ }
+
+ // Copy the name of the MPQ directory.
+ StringCopyA(szMpqDirectory, szDirName, MAX_PATH);
+ cchMpqDirectory = strlen(szMpqDirectory);
+
+ // Cut trailing slashes and/or backslashes
+ while((cchMpqDirectory > 0) && (szMpqDirectory[cchMpqDirectory - 1] == '/' || szMpqDirectory[cchMpqDirectory - 1] == '\\'))
+ cchMpqDirectory--;
+ szMpqDirectory[cchMpqDirectory] = 0;
+
+ // Print the work directory info
+ Logger.PrintMessage("Work directory %s (%s)", szMpqDirectory, szWhereFrom);
+
+ // Verify if the work MPQ directory is writable
+ CreateFullPathName(szFullPath, NULL, "TestFile.bin");
+ pStream = FileStream_CreateFileA(szFullPath, 0);
+ if(pStream == NULL)
+ return Logger.PrintError("MPQ subdirectory doesn't exist or is not writable");
+
+ // Close the stream
+ FileStream_Close(pStream);
+ remove(szFullPath);
+
+ // Verify if the working directory exists and if there is a subdirectory with the file name
+ CreateFullPathName(szFullPath, szMpqSubDir, "ListFile_Blizzard.txt");
+ pStream = FileStream_OpenFileA(szFullPath, STREAM_FLAG_READ_ONLY);
+ if(pStream == NULL)
+ return Logger.PrintError("The main listfile (%s) was not found. Check your paths", GetShortPlainName(szFullPath));
+
+ // Close the stream
+ FileStream_Close(pStream);
+ return ERROR_SUCCESS;
+}
+
+static int GetFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName)
+{
+ TCHAR * szPatchName;
+ HANDLE hFile;
+ TCHAR szPatchChain[0x400];
+ int nPatchCount = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Open the MPQ file
+ if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile))
+ {
+ // Notify the user
+ pLogger->PrintProgress("Verifying patch chain for %s ...", GetShortPlainName(szFileName));
+
+ // Query the patch chain
+ if(!SFileGetFileInfo(hFile, SFileInfoPatchChain, szPatchChain, sizeof(szPatchChain), NULL))
+ nError = pLogger->PrintError("Failed to retrieve the patch chain on %s", szFileName);
+
+ // Is there anything at all in the patch chain?
+ if(nError == ERROR_SUCCESS && szPatchChain[0] == 0)
+ {
+ pLogger->PrintError("The patch chain for %s is empty", szFileName);
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Now calculate the number of patches
+ if(nError == ERROR_SUCCESS)
+ {
+ // Get the pointer to the patch
+ szPatchName = szPatchChain;
+
+ // Skip the base name
+ for(;;)
+ {
+ // Skip the current name
+ szPatchName = szPatchName + _tcslen(szPatchName) + 1;
+ if(szPatchName[0] == 0)
+ break;
+
+ // Increment number of patches
+ nPatchCount++;
+ }
+ }
+
+ SFileCloseFile(hFile);
+ }
+ else
+ {
+ pLogger->PrintError("Failed to open file %s", szFileName);
+ }
+
+ return nPatchCount;
+}
+
+static int VerifyFilePatchCount(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, int nExpectedPatchCount)
+{
+ int nPatchCount = 0;
+
+ // Retrieve the patch count
+ pLogger->PrintProgress("Verifying patch count for %s ...", szFileName);
+ nPatchCount = GetFilePatchCount(pLogger, hMpq, szFileName);
+
+ // Check if there are any patches at all
+ if(nExpectedPatchCount != 0 && nPatchCount == 0)
+ {
+ pLogger->PrintMessage("There are no patches beyond %s", szFileName);
+ return ERROR_FILE_CORRUPT;
+ }
+
+ // Check if the number of patches fits
+ if(nPatchCount != nExpectedPatchCount)
+ {
+ pLogger->PrintMessage("Unexpected number of patches for %s", szFileName);
+ return ERROR_FILE_CORRUPT;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int CreateEmptyFile(TLogHelper * pLogger, const char * szPlainName, ULONGLONG FileSize, char * szBuffer)
+{
+ TFileStream * pStream;
+ char szFullPath[MAX_PATH];
+
+ // Notify the user
+ pLogger->PrintProgress("Creating empty file %s ...", szPlainName);
+
+ // Construct the full path and crete the file
+ CreateFullPathName(szFullPath, NULL, szPlainName);
+ pStream = FileStream_CreateFileA(szFullPath, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
+ if(pStream == NULL)
+ return pLogger->PrintError("Failed to create file %s", szBuffer);
+
+ // Write the required size
+ FileStream_SetSize(pStream, FileSize);
+ FileStream_Close(pStream);
+
+ // Give the caller the full file name
+ if(szBuffer != NULL)
+ strcpy(szBuffer, szFullPath);
+ return ERROR_SUCCESS;
+}
+
+static int VerifyFilePosition(
+ TLogHelper * pLogger,
+ TFileStream * pStream,
+ ULONGLONG ExpectedPosition)
+{
+ ULONGLONG ByteOffset = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Retrieve the file position
+ if(FileStream_GetPos(pStream, &ByteOffset))
+ {
+ if(ByteOffset != ExpectedPosition)
+ {
+ pLogger->PrintMessage("The file position is different than expected (expected: " I64u_a ", current: " I64u_a, ExpectedPosition, ByteOffset);
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ nError = pLogger->PrintError("Failed to retrieve the file offset");
+ }
+
+ return nError;
+}
+
+static int VerifyFileMpqHeader(TLogHelper * pLogger, TFileStream * pStream, ULONGLONG * pByteOffset)
+{
+ TMPQHeader Header;
+ int nError = ERROR_SUCCESS;
+
+ memset(&Header, 0xFE, sizeof(TMPQHeader));
+ if(FileStream_Read(pStream, pByteOffset, &Header, sizeof(TMPQHeader)))
+ {
+ if(Header.dwID != ID_MPQ)
+ {
+ pLogger->PrintMessage("Read error - the data is not a MPQ header");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ nError = pLogger->PrintError("Failed to read the MPQ header");
+ }
+
+ return nError;
+}
+
+static int WriteMpqUserDataHeader(
+ TLogHelper * pLogger,
+ TFileStream * pStream,
+ ULONGLONG ByteOffset,
+ DWORD dwByteCount)
+{
+ TMPQUserData UserData;
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user
+ pLogger->PrintProgress("Writing user data header...");
+
+ // Fill the user data header
+ UserData.dwID = ID_MPQ_USERDATA;
+ UserData.cbUserDataSize = dwByteCount;
+ UserData.dwHeaderOffs = (dwByteCount + sizeof(TMPQUserData));
+ UserData.cbUserDataHeader = dwByteCount / 2;
+ if(!FileStream_Write(pStream, &ByteOffset, &UserData, sizeof(TMPQUserData)))
+ nError = GetLastError();
+ return nError;
+}
+
+static int WriteFileData(
+ TLogHelper * pLogger,
+ TFileStream * pStream,
+ ULONGLONG ByteOffset,
+ ULONGLONG ByteCount)
+{
+ ULONGLONG SaveByteCount = ByteCount;
+ ULONGLONG BytesWritten = 0;
+ LPBYTE pbDataBuffer;
+ DWORD cbDataBuffer = 0x10000;
+ int nError = ERROR_SUCCESS;
+
+ // Write some data
+ pbDataBuffer = new BYTE[cbDataBuffer];
+ if(pbDataBuffer != NULL)
+ {
+ memset(pbDataBuffer, 0, cbDataBuffer);
+ strcpy((char *)pbDataBuffer, "This is a test data written to a file.");
+
+ // Perform the write
+ while(ByteCount > 0)
+ {
+ DWORD cbToWrite = (ByteCount > cbDataBuffer) ? cbDataBuffer : (DWORD)ByteCount;
+
+ // Notify the user
+ pLogger->PrintProgress("Writing file data (%I64u of %I64u) ...", BytesWritten, SaveByteCount);
+
+ // Write the data
+ if(!FileStream_Write(pStream, &ByteOffset, pbDataBuffer, cbToWrite))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ BytesWritten += cbToWrite;
+ ByteOffset += cbToWrite;
+ ByteCount -= cbToWrite;
+ }
+
+ delete [] pbDataBuffer;
+ }
+ return nError;
+}
+
+static int CopyFileData(
+ TLogHelper * pLogger,
+ TFileStream * pStream1,
+ TFileStream * pStream2,
+ ULONGLONG ByteOffset,
+ ULONGLONG ByteCount)
+{
+ ULONGLONG BytesCopied = 0;
+ ULONGLONG EndOffset = ByteOffset + ByteCount;
+ LPBYTE pbCopyBuffer;
+ DWORD BytesToRead;
+ DWORD BlockLength = 0x100000;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate copy buffer
+ pbCopyBuffer = STORM_ALLOC(BYTE, BlockLength);
+ if(pbCopyBuffer != NULL)
+ {
+ while(ByteOffset < EndOffset)
+ {
+ // Read source
+ BytesToRead = ((EndOffset - ByteOffset) > BlockLength) ? BlockLength : (DWORD)(EndOffset - ByteOffset);
+ if(!FileStream_Read(pStream1, &ByteOffset, pbCopyBuffer, BytesToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Write to the destination file
+ if(!FileStream_Write(pStream2, NULL, pbCopyBuffer, BytesToRead))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // Increment the byte counts
+ BytesCopied += BytesToRead;
+ ByteOffset += BytesToRead;
+
+ // Notify the user
+ pLogger->PrintProgress("Copying (%I64u of %I64u complete) ...", BytesCopied, ByteCount);
+ }
+
+ STORM_FREE(pbCopyBuffer);
+ }
+
+ return nError;
+}
+
+// Support function for copying file
+static int CreateFileCopy(
+ TLogHelper * pLogger,
+ const char * szPlainName,
+ const char * szFileCopy,
+ char * szBuffer,
+ ULONGLONG PreMpqDataSize = 0,
+ ULONGLONG UserDataSize = 0)
+{
+ TFileStream * pStream1; // Source file
+ TFileStream * pStream2; // Target file
+ ULONGLONG ByteOffset = 0;
+ ULONGLONG FileSize = 0;
+ char szFileName1[MAX_PATH];
+ char szFileName2[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user
+ szPlainName += FileStream_PrefixA(szPlainName, NULL);
+ pLogger->PrintProgress("Creating copy of %s ...", szPlainName);
+
+ // Construct both file names. Check if they are not the same
+ CreateFullPathName(szFileName1, szMpqSubDir, szPlainName);
+ CreateFullPathName(szFileName2, NULL, szFileCopy + FileStream_PrefixA(szFileCopy, NULL));
+ if(!_stricmp(szFileName1, szFileName2))
+ {
+ pLogger->PrintError("Failed to create copy of MPQ (the copy name is the same like the original name)");
+ return ERROR_CAN_NOT_COMPLETE;
+ }
+
+ // Open the source file
+ pStream1 = FileStream_OpenFileA(szFileName1, STREAM_FLAG_READ_ONLY);
+ if(pStream1 == NULL)
+ {
+ pLogger->PrintError("Failed to open the source file %s", szFileName1);
+ return ERROR_CAN_NOT_COMPLETE;
+ }
+
+ // Create the destination file
+ pStream2 = FileStream_CreateFileA(szFileName2, 0);
+ if(pStream2 != NULL)
+ {
+ // If we should write some pre-MPQ data to the target file, do it
+ if(PreMpqDataSize != 0)
+ {
+ nError = WriteFileData(pLogger, pStream2, ByteOffset, PreMpqDataSize);
+ ByteOffset += PreMpqDataSize;
+ }
+
+ // If we should write some MPQ user data, write the header first
+ if(UserDataSize != 0)
+ {
+ nError = WriteMpqUserDataHeader(pLogger, pStream2, ByteOffset, (DWORD)UserDataSize);
+ ByteOffset += sizeof(TMPQUserData);
+
+ nError = WriteFileData(pLogger, pStream2, ByteOffset, UserDataSize);
+ ByteOffset += UserDataSize;
+ }
+
+ // Copy the file data from the source file to the destination file
+ FileStream_GetSize(pStream1, &FileSize);
+ if(FileSize != 0)
+ {
+ nError = CopyFileData(pLogger, pStream1, pStream2, 0, FileSize);
+ ByteOffset += FileSize;
+ }
+ FileStream_Close(pStream2);
+ }
+
+ // Close the source file
+ FileStream_Close(pStream1);
+
+ // Create the full file name of the target file, including prefix
+ if(szBuffer != NULL)
+ CreateFullPathName(szBuffer, NULL, szFileCopy);
+
+ // Report error, if any
+ if(nError != ERROR_SUCCESS)
+ pLogger->PrintError("Failed to create copy of MPQ");
+ return nError;
+}
+
+static int CreateMasterAndMirrorPaths(
+ TLogHelper * pLogger,
+ char * szMirrorPath,
+ char * szMasterPath,
+ const char * szMirrorName,
+ const char * szMasterName,
+ bool bCopyMirrorFile)
+{
+ char szCopyPath[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ // Always delete the mirror file
+ CreateFullPathName(szMasterPath, szMpqSubDir, szMasterName);
+ CreateFullPathName(szCopyPath, NULL, szMirrorName);
+ remove(szCopyPath + FileStream_PrefixA(szCopyPath, NULL));
+
+ // Copy the mirrored file from the source to the work directory
+ if(bCopyMirrorFile)
+ nError = CreateFileCopy(pLogger, szMirrorName, szMirrorName, NULL);
+
+ // Create the mirror*master path
+ if(nError == ERROR_SUCCESS)
+ sprintf(szMirrorPath, "%s*%s", szCopyPath, szMasterPath);
+
+ return nError;
+}
+
+static void WINAPI AddFileCallback(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall)
+{
+ TLogHelper * pLogger = (TLogHelper *)pvUserData;
+
+ // Keep compiler happy
+ bFinalCall = bFinalCall;
+
+ pLogger->PrintProgress("Adding file (%s) (%u of %u) (%u of %u) ...", pLogger->UserString,
+ pLogger->UserCount,
+ pLogger->UserTotal,
+ dwBytesWritten,
+ dwTotalBytes);
+}
+
+static void WINAPI CompactCallback(void * pvUserData, DWORD dwWork, ULONGLONG BytesDone, ULONGLONG TotalBytes)
+{
+ TLogHelper * pLogger = (TLogHelper *)pvUserData;
+ const char * szWork = NULL;
+
+ switch(dwWork)
+ {
+ case CCB_CHECKING_FILES:
+ szWork = "Checking files in archive";
+ break;
+
+ case CCB_CHECKING_HASH_TABLE:
+ szWork = "Checking hash table";
+ break;
+
+ case CCB_COPYING_NON_MPQ_DATA:
+ szWork = "Copying non-MPQ data";
+ break;
+
+ case CCB_COMPACTING_FILES:
+ szWork = "Compacting files";
+ break;
+
+ case CCB_CLOSING_ARCHIVE:
+ szWork = "Closing archive";
+ break;
+ }
+
+ if(szWork != NULL)
+ {
+ if(pLogger != NULL)
+ pLogger->PrintProgress("%s (%I64u of %I64u) ...", szWork, BytesDone, TotalBytes);
+ else
+ printf("%s (" I64u_a " of " I64u_a ") ... \r", szWork, BytesDone, TotalBytes);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// MPQ file utilities
+
+#define TEST_FLAG_LOAD_FILES 0x00000001 // Test function should load all files in the MPQ
+#define TEST_FLAG_HASH_FILES 0x00000002 // Test function should load all files in the MPQ
+#define TEST_FLAG_PLAY_WAVES 0x00000004 // Play extracted WAVE files
+#define TEST_FLAG_MOST_PATCHED 0x00000008 // Find the most patched file
+
+struct TFileData
+{
+ DWORD dwBlockIndex;
+ DWORD dwFileSize;
+ DWORD dwFlags;
+ DWORD dwReserved; // Alignment
+ BYTE FileData[1];
+};
+
+static bool CheckIfFileIsPresent(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, bool bShouldExist)
+{
+ HANDLE hFile = NULL;
+
+ if(SFileOpenFileEx(hMpq, szFileName, 0, &hFile))
+ {
+ if(bShouldExist == false)
+ pLogger->PrintMessage("The file %s is present, but it should not be", szFileName);
+ SFileCloseFile(hFile);
+ return true;
+ }
+ else
+ {
+ if(bShouldExist)
+ pLogger->PrintMessage("The file %s is not present, but it should be", szFileName);
+ return false;
+ }
+}
+
+static TFileData * LoadLocalFile(TLogHelper * pLogger, const char * szFileName, bool bMustSucceed)
+{
+ TFileStream * pStream;
+ TFileData * pFileData = NULL;
+ ULONGLONG FileSize = 0;
+ size_t nAllocateBytes;
+
+ // Notify the user
+ if(pLogger != NULL)
+ pLogger->PrintProgress("Loading local file ...");
+
+ // Attempt to open the file
+ pStream = FileStream_OpenFileA(szFileName, STREAM_FLAG_READ_ONLY);
+ if(pStream == NULL)
+ {
+ if(pLogger != NULL && bMustSucceed == true)
+ pLogger->PrintError("Failed to open the file %s", szFileName);
+ return NULL;
+ }
+
+ // Verify the size
+ FileStream_GetSize(pStream, &FileSize);
+ if((FileSize >> 0x20) == 0)
+ {
+ // Allocate space for the file
+ nAllocateBytes = sizeof(TFileData) + (size_t)FileSize;
+ pFileData = (TFileData *)STORM_ALLOC(BYTE, nAllocateBytes);
+ if(pFileData != NULL)
+ {
+ // Make sure it;s properly zeroed
+ memset(pFileData, 0, nAllocateBytes);
+ pFileData->dwFileSize = (DWORD)FileSize;
+
+ // Load to memory
+ if(!FileStream_Read(pStream, NULL, pFileData->FileData, pFileData->dwFileSize))
+ {
+ STORM_FREE(pFileData);
+ pFileData = NULL;
+ }
+ }
+ }
+
+ FileStream_Close(pStream);
+ return pFileData;
+}
+
+static int CompareTwoLocalFilesRR(
+ TLogHelper * pLogger,
+ TFileStream * pStream1, // Master file
+ TFileStream * pStream2, // Mirror file
+ int nIterations) // Number of iterations
+{
+ ULONGLONG RandomNumber = 0x12345678; // We need pseudo-random number that will repeat each run of the program
+ ULONGLONG RandomSeed;
+ ULONGLONG ByteOffset;
+ ULONGLONG FileSize1 = 1;
+ ULONGLONG FileSize2 = 2;
+ DWORD BytesToRead;
+ DWORD Difference;
+ LPBYTE pbBuffer1;
+ LPBYTE pbBuffer2;
+ DWORD cbBuffer = 0x100000;
+ int nError = ERROR_SUCCESS;
+
+ // Compare file sizes
+ FileStream_GetSize(pStream1, &FileSize1);
+ FileStream_GetSize(pStream2, &FileSize2);
+ if(FileSize1 != FileSize2)
+ {
+ pLogger->PrintMessage("The files have different size");
+ return ERROR_CAN_NOT_COMPLETE;
+ }
+
+ // Allocate both buffers
+ pbBuffer1 = STORM_ALLOC(BYTE, cbBuffer);
+ pbBuffer2 = STORM_ALLOC(BYTE, cbBuffer);
+ if(pbBuffer1 && pbBuffer2)
+ {
+ // Perform many random reads
+ for(int i = 0; i < nIterations; i++)
+ {
+ // Generate psudo-random offsrt and data size
+ ByteOffset = (RandomNumber % FileSize1);
+ BytesToRead = (DWORD)(RandomNumber % cbBuffer);
+
+ // Show the progress message
+ pLogger->PrintProgress("Comparing file: Offset: " I64u_a ", Length: %u", ByteOffset, BytesToRead);
+
+ // Only perform read if the byte offset is below
+ if(ByteOffset < FileSize1)
+ {
+ if((ByteOffset + BytesToRead) > FileSize1)
+ BytesToRead = (DWORD)(FileSize1 - ByteOffset);
+
+ memset(pbBuffer1, 0xEE, cbBuffer);
+ memset(pbBuffer2, 0xAA, cbBuffer);
+
+ FileStream_Read(pStream1, &ByteOffset, pbBuffer1, BytesToRead);
+ FileStream_Read(pStream2, &ByteOffset, pbBuffer2, BytesToRead);
+
+ if(!CompareBlocks(pbBuffer1, pbBuffer2, BytesToRead, &Difference))
+ {
+ pLogger->PrintMessage("Difference at %u (Offset " I64X_a ", Length %X)", Difference, ByteOffset, BytesToRead);
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Shuffle the random number
+ memcpy(&RandomSeed, pbBuffer1, sizeof(RandomSeed));
+ RandomNumber = ((RandomNumber >> 0x11) | (RandomNumber << 0x29)) ^ (RandomNumber + RandomSeed);
+ }
+ }
+ }
+
+ // Free both buffers
+ if(pbBuffer2 != NULL)
+ STORM_FREE(pbBuffer2);
+ if(pbBuffer1 != NULL)
+ STORM_FREE(pbBuffer1);
+ return nError;
+}
+
+static TFileData * LoadMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName)
+{
+ TFileData * pFileData = NULL;
+ HANDLE hFile;
+ DWORD dwFileSizeHi = 0xCCCCCCCC;
+ DWORD dwFileSizeLo = 0;
+ DWORD dwBytesRead;
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user that we are loading a file from MPQ
+ pLogger->PrintProgress("Loading file %s ...", GetShortPlainName(szFileName));
+
+#if defined(_MSC_VER) && defined(_DEBUG)
+// if(!_stricmp(szFileName, "manifest-cards.csv"))
+// DebugBreak();
+#endif
+
+ // Open the file from MPQ
+ if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile))
+ nError = pLogger->PrintError("Failed to open the file %s", szFileName);
+
+ // Get the size of the file
+ if(nError == ERROR_SUCCESS)
+ {
+ dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi);
+ if(dwFileSizeLo == SFILE_INVALID_SIZE || dwFileSizeHi != 0)
+ nError = pLogger->PrintError("Failed to query the file size");
+ }
+
+ // Spazzler protector: Creates fake files with size of 0x7FFFE7CA
+ if(nError == ERROR_SUCCESS)
+ {
+ if(dwFileSizeLo > 0x1FFFFFFF)
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Allocate buffer for the file content
+ if(nError == ERROR_SUCCESS)
+ {
+ pFileData = (TFileData *)STORM_ALLOC(BYTE, sizeof(TFileData) + dwFileSizeLo);
+ if(pFileData == NULL)
+ {
+ pLogger->PrintError("Failed to allocate buffer for the file content");
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // get the file index of the MPQ file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Store the file size
+ memset(pFileData, 0, sizeof(TFileData) + dwFileSizeLo);
+ pFileData->dwFileSize = dwFileSizeLo;
+
+ // Retrieve the block index and file flags
+ if(!SFileGetFileInfo(hFile, SFileInfoFileIndex, &pFileData->dwBlockIndex, sizeof(DWORD), NULL))
+ nError = pLogger->PrintError("Failed retrieve the file index of %s", szFileName);
+ if(!SFileGetFileInfo(hFile, SFileInfoFlags, &pFileData->dwFlags, sizeof(DWORD), NULL))
+ nError = pLogger->PrintError("Failed retrieve the file flags of %s", szFileName);
+ }
+
+ // Load the entire file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Read the file data
+ SFileReadFile(hFile, pFileData->FileData, dwFileSizeLo, &dwBytesRead, NULL);
+ if(dwBytesRead != dwFileSizeLo)
+ nError = pLogger->PrintError("Failed to read the content of the file %s", szFileName);
+ }
+
+ // If failed, free the buffer
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pFileData);
+ SetLastError(nError);
+ pFileData = NULL;
+ }
+
+ // Close the file and return what we got
+ if(hFile != NULL)
+ SFileCloseFile(hFile);
+ return pFileData;
+}
+
+static bool CompareTwoFiles(TLogHelper * pLogger, TFileData * pFileData1, TFileData * pFileData2)
+{
+ // Compare the file size
+ if(pFileData1->dwFileSize != pFileData2->dwFileSize)
+ {
+ pLogger->PrintErrorVa(_T("The files have different size (%u vs %u)"), pFileData1->dwFileSize, pFileData2->dwFileSize);
+ SetLastError(ERROR_FILE_CORRUPT);
+ return false;
+ }
+
+ // Compare the files
+ for(DWORD i = 0; i < pFileData1->dwFileSize; i++)
+ {
+ if(pFileData1->FileData[i] != pFileData2->FileData[i])
+ {
+ pLogger->PrintErrorVa(_T("Files are different at offset %08X"), i);
+ SetLastError(ERROR_FILE_CORRUPT);
+ return false;
+ }
+ }
+
+ // The files are identical
+ return true;
+}
+
+static int SearchArchive(
+ TLogHelper * pLogger,
+ HANDLE hMpq,
+ DWORD dwTestFlags = 0,
+ DWORD * pdwFileCount = NULL,
+ LPBYTE pbFileHash = NULL)
+{
+ SFILE_FIND_DATA sf;
+ TFileData * pFileData;
+ HANDLE hFind;
+ DWORD dwFileCount = 0;
+ hash_state md5state;
+ char szMostPatched[MAX_PATH] = "";
+ char szListFile[MAX_PATH];
+ bool bFound = true;
+ int nMaxPatchCount = 0;
+ int nPatchCount = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Construct the full name of the listfile
+ CreateFullPathName(szListFile, szMpqSubDir, "ListFile_Blizzard.txt");
+
+ // Prepare hashing
+ md5_init(&md5state);
+
+ // Initiate the MPQ search
+ pLogger->PrintProgress("Searching the archive ...");
+ hFind = SFileFindFirstFile(hMpq, "*", &sf, szListFile);
+ if(hFind == NULL)
+ {
+ nError = GetLastError();
+ nError = (nError == ERROR_NO_MORE_FILES) ? ERROR_SUCCESS : nError;
+ return nError;
+ }
+
+ // Perform the search
+ while(bFound == true)
+ {
+ // Increment number of files
+ dwFileCount++;
+
+// if(!_stricmp(sf.cFileName, "Interface\\Glues\\CREDITS\\1024px-Blade3_final2.blp"))
+// DebugBreak();
+
+ if(dwTestFlags & TEST_FLAG_MOST_PATCHED)
+ {
+ // Load the patch count
+ nPatchCount = GetFilePatchCount(pLogger, hMpq, sf.cFileName);
+
+ // Check if it's greater than maximum
+ if(nPatchCount > nMaxPatchCount)
+ {
+ strcpy(szMostPatched, sf.cFileName);
+ nMaxPatchCount = nPatchCount;
+ }
+ }
+
+ // Load the file to memory, if required
+ if(dwTestFlags & TEST_FLAG_LOAD_FILES)
+ {
+ // Load the entire file to the MPQ
+ pFileData = LoadMpqFile(pLogger, hMpq, sf.cFileName);
+ if(pFileData != NULL)
+ {
+ // Hash the file data, if needed
+ if((dwTestFlags & TEST_FLAG_HASH_FILES) && !IsInternalMpqFileName(sf.cFileName))
+ md5_process(&md5state, pFileData->FileData, pFileData->dwFileSize);
+
+ // Play sound files, if required
+ if((dwTestFlags & TEST_FLAG_PLAY_WAVES) && strstr(sf.cFileName, ".wav") != NULL)
+ {
+#ifdef _MSC_VER
+ pLogger->PrintProgress("Playing sound %s", sf.cFileName);
+ PlaySound((LPCTSTR)pFileData->FileData, NULL, SND_MEMORY);
+#endif
+ }
+
+ STORM_FREE(pFileData);
+ }
+ }
+
+ bFound = SFileFindNextFile(hFind, &sf);
+ }
+ SFileFindClose(hFind);
+
+ // Give the file count, if required
+ if(pdwFileCount != NULL)
+ pdwFileCount[0] = dwFileCount;
+
+ // Give the hash, if required
+ if(pbFileHash != NULL && (dwTestFlags & TEST_FLAG_HASH_FILES))
+ md5_done(&md5state, pbFileHash);
+
+ return nError;
+}
+
+static int CreateNewArchive(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
+{
+ HANDLE hMpq = NULL;
+ TCHAR szMpqName[MAX_PATH];
+ char szFullPath[MAX_PATH];
+
+ // Make sure that the MPQ is deleted
+ CreateFullPathName(szFullPath, NULL, szPlainName);
+ remove(szFullPath);
+
+ // Create the new MPQ
+ CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
+ if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq))
+ return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName);
+
+ // Shall we close it right away?
+ if(phMpq == NULL)
+ SFileCloseArchive(hMpq);
+ else
+ *phMpq = hMpq;
+
+ return ERROR_SUCCESS;
+}
+
+static int CreateNewArchive_V2(TLogHelper * pLogger, const char * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
+{
+ SFILE_CREATE_MPQ CreateInfo;
+ HANDLE hMpq = NULL;
+ TCHAR szMpqName[MAX_PATH];
+ char szFullPath[MAX_PATH];
+
+ // Make sure that the MPQ is deleted
+ CreateFullPathName(szFullPath, NULL, szPlainName);
+ CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
+ remove(szFullPath);
+
+ // Fill the create structure
+ memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ));
+ CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ);
+ CreateInfo.dwMpqVersion = (dwCreateFlags & MPQ_CREATE_ARCHIVE_VMASK) >> FLAGS_TO_FORMAT_SHIFT;
+ CreateInfo.dwStreamFlags = STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE;
+ CreateInfo.dwFileFlags1 = (dwCreateFlags & MPQ_CREATE_LISTFILE) ? MPQ_FILE_EXISTS : 0;
+ CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_EXISTS : 0;
+ CreateInfo.dwFileFlags3 = (dwCreateFlags & MPQ_CREATE_SIGNATURE) ? MPQ_FILE_EXISTS : 0;
+ CreateInfo.dwAttrFlags = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? (MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_FILETIME | MPQ_ATTRIBUTE_MD5) : 0;
+ CreateInfo.dwSectorSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) ? 0x4000 : 0x1000;
+ CreateInfo.dwRawChunkSize = (CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_4) ? 0x4000 : 0;
+ CreateInfo.dwMaxFileCount = dwMaxFileCount;
+
+ // Create the new MPQ
+ if(!SFileCreateArchive2(szMpqName, &CreateInfo, &hMpq))
+ return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName);
+
+ // Shall we close it right away?
+ if(phMpq == NULL)
+ SFileCloseArchive(hMpq);
+ else
+ *phMpq = hMpq;
+
+ return ERROR_SUCCESS;
+}
+
+// Creates new archive with UNICODE name. Adds prefix to the name
+static int CreateNewArchiveU(TLogHelper * pLogger, const wchar_t * szPlainName, DWORD dwCreateFlags, DWORD dwMaxFileCount)
+{
+#ifdef _UNICODE
+ HANDLE hMpq = NULL;
+ TCHAR szMpqName[MAX_PATH+1];
+ char szFullPath[MAX_PATH];
+
+ // Construct the full UNICODE name
+ CreateFullPathName(szFullPath, NULL, "StormLibTest_");
+ CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
+ StringCatT(szMpqName, szPlainName, MAX_PATH);
+
+ // Make sure that the MPQ is deleted
+ _tremove(szMpqName);
+
+ // Create the archive
+ pLogger->PrintProgress("Creating new archive with UNICODE name ...");
+ if(!SFileCreateArchive(szMpqName, dwCreateFlags, dwMaxFileCount, &hMpq))
+ return pLogger->PrintError(_T("Failed to create archive %s"), szMpqName);
+
+ SFileCloseArchive(hMpq);
+#else
+ pLogger = pLogger;
+ szPlainName = szPlainName;
+ dwCreateFlags = dwCreateFlags;
+ dwMaxFileCount = dwMaxFileCount;
+#endif
+ return ERROR_SUCCESS;
+}
+
+static int OpenExistingArchive(TLogHelper * pLogger, const char * szFullPath, DWORD dwFlags, HANDLE * phMpq)
+{
+ HANDLE hMpq = NULL;
+ TCHAR szMpqName[MAX_PATH];
+// bool bReopenResult;
+ int nError = ERROR_SUCCESS;
+
+ // Is it an encrypted MPQ ?
+ if(strstr(szFullPath, ".MPQE") != NULL)
+ dwFlags |= STREAM_PROVIDER_MPQE;
+ if(strstr(szFullPath, ".MPQ.part") != NULL)
+ dwFlags |= STREAM_PROVIDER_PARTIAL;
+ if(strstr(szFullPath, ".mpq.part") != NULL)
+ dwFlags |= STREAM_PROVIDER_PARTIAL;
+ if(strstr(szFullPath, ".MPQ.0") != NULL)
+ dwFlags |= STREAM_PROVIDER_BLOCK4;
+
+ // Open the copied archive
+ pLogger->PrintProgress("Opening archive %s ...", GetShortPlainName(szFullPath));
+ CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
+ if(!SFileOpenArchive(szMpqName, 0, dwFlags, &hMpq))
+ {
+ switch(nError = GetLastError())
+ {
+// case ERROR_BAD_FORMAT: // If the error is ERROR_BAD_FORMAT, try to open with MPQ_OPEN_FORCE_MPQ_V1
+// bReopenResult = SFileOpenArchive(szMpqName, 0, dwFlags | MPQ_OPEN_FORCE_MPQ_V1, &hMpq);
+// nError = (bReopenResult == false) ? GetLastError() : ERROR_SUCCESS;
+// break;
+
+ case ERROR_AVI_FILE: // Ignore the error if it's an AVI file or if the file is incomplete
+ case ERROR_FILE_INCOMPLETE:
+ return nError;
+ }
+
+ // Show the open error to the user
+ return pLogger->PrintError("Failed to open archive %s", szFullPath);
+ }
+
+ // Store the archive handle or close the archive
+ if(phMpq == NULL)
+ SFileCloseArchive(hMpq);
+ else
+ *phMpq = hMpq;
+ return nError;
+}
+
+static int OpenPatchArchive(TLogHelper * pLogger, HANDLE hMpq, const char * szFullPath)
+{
+ TCHAR szPatchName[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ pLogger->PrintProgress("Adding patch %s ...", GetShortPlainName(szFullPath));
+ CopyFileName(szPatchName, szFullPath, strlen(szFullPath));
+ if(!SFileOpenPatchArchive(hMpq, szPatchName, NULL, 0))
+ nError = pLogger->PrintError("Failed to add patch %s ...", szFullPath);
+
+ return nError;
+}
+
+static int OpenExistingArchiveWithCopy(TLogHelper * pLogger, const char * szFileName, const char * szCopyName, HANDLE * phMpq)
+{
+ DWORD dwFlags = 0;
+ char szFullPath[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ // We expect MPQ directory to be already prepared by InitializeMpqDirectory
+ assert(szMpqDirectory[0] != 0);
+
+ // At least one name must be entered
+ assert(szFileName != NULL || szCopyName != NULL);
+
+ // If both names entered, create a copy
+ if(szFileName != NULL && szCopyName != NULL)
+ {
+ nError = CreateFileCopy(pLogger, szFileName, szCopyName, szFullPath);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+ // If only source name entered, open it for read-only access
+ else if(szFileName != NULL && szCopyName == NULL)
+ {
+ CreateFullPathName(szFullPath, szMpqSubDir, szFileName);
+ dwFlags |= MPQ_OPEN_READ_ONLY;
+ }
+
+ // If only target name entered, open it directly
+ else if(szFileName == NULL && szCopyName != NULL)
+ {
+ CreateFullPathName(szFullPath, NULL, szCopyName);
+ }
+
+ // Open the archive
+ return OpenExistingArchive(pLogger, szFullPath, dwFlags, phMpq);
+}
+
+static int OpenPatchedArchive(TLogHelper * pLogger, HANDLE * phMpq, const char * PatchList[])
+{
+ HANDLE hMpq = NULL;
+ char szFullPath[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ // The first file is expected to be valid
+ assert(PatchList[0] != NULL);
+
+ // Open the primary MPQ
+ CreateFullPathName(szFullPath, szMpqSubDir, PatchList[0]);
+ nError = OpenExistingArchive(pLogger, szFullPath, MPQ_OPEN_READ_ONLY, &hMpq);
+
+ // Add all patches
+ if(nError == ERROR_SUCCESS)
+ {
+ for(size_t i = 1; PatchList[i] != NULL; i++)
+ {
+ CreateFullPathName(szFullPath, szMpqPatchDir, PatchList[i]);
+ nError = OpenPatchArchive(pLogger, hMpq, szFullPath);
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+ }
+
+ // Store the archive handle or close the archive
+ if(phMpq == NULL)
+ SFileCloseArchive(hMpq);
+ else
+ *phMpq = hMpq;
+ return nError;
+}
+
+static int AddFileToMpq(
+ TLogHelper * pLogger,
+ HANDLE hMpq,
+ const char * szFileName,
+ const char * szFileData,
+ DWORD dwFlags = 0,
+ DWORD dwCompression = 0,
+ int nExpectedError = ERROR_SUCCESS)
+{
+ HANDLE hFile = NULL;
+ DWORD dwFileSize = (DWORD)strlen(szFileData);
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user
+ pLogger->PrintProgress("Adding file %s ...", szFileName);
+
+ // Get the default flags
+ if(dwFlags == 0)
+ dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED;
+ if(dwCompression == 0)
+ dwCompression = MPQ_COMPRESSION_ZLIB;
+
+ // Create the file within the MPQ
+ if(SFileCreateFile(hMpq, szFileName, 0, dwFileSize, 0, dwFlags, &hFile))
+ {
+ // Write the file
+ if(!SFileWriteFile(hFile, szFileData, dwFileSize, dwCompression))
+ nError = pLogger->PrintError("Failed to write data to the MPQ");
+ SFileCloseFile(hFile);
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ // Check the expected error code
+ if(nExpectedError != ERROR_UNDETERMINED_RESULT && nError != nExpectedError)
+ return pLogger->PrintError("Unexpected result from SFileCreateFile(%s)", szFileName);
+ return nError;
+}
+
+static int AddLocalFileToMpq(
+ TLogHelper * pLogger,
+ HANDLE hMpq,
+ const char * szArchivedName,
+ const char * szLocalFileName,
+ DWORD dwFlags = 0,
+ DWORD dwCompression = 0,
+ bool bMustSucceed = false)
+{
+ TCHAR szFileName[MAX_PATH];
+ DWORD dwVerifyResult;
+
+ // Notify the user
+ pLogger->PrintProgress("Adding file %s (%u of %u)...", GetShortPlainName(szLocalFileName), pLogger->UserCount, pLogger->UserTotal);
+ pLogger->UserString = szArchivedName;
+
+ // Get the default flags
+ if(dwFlags == 0)
+ dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED;
+ if(dwCompression == 0)
+ dwCompression = MPQ_COMPRESSION_ZLIB;
+
+ // Set the notification callback
+ SFileSetAddFileCallback(hMpq, AddFileCallback, pLogger);
+
+ // Add the file to the MPQ
+ CopyFileName(szFileName, szLocalFileName, strlen(szLocalFileName));
+ if(!SFileAddFileEx(hMpq, szFileName, szArchivedName, dwFlags, dwCompression, MPQ_COMPRESSION_NEXT_SAME))
+ {
+ if(bMustSucceed)
+ return pLogger->PrintError("Failed to add the file %s", szArchivedName);
+ return GetLastError();
+ }
+
+ // Verify the file unless it was lossy compression
+ if((dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO)) == 0)
+ {
+ // Notify the user
+ pLogger->PrintProgress("Verifying file %s (%u of %u) ...", szArchivedName, pLogger->UserCount, pLogger->UserTotal);
+
+ // Perform the verification
+ dwVerifyResult = SFileVerifyFile(hMpq, szArchivedName, MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_MD5);
+ if(dwVerifyResult & (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR))
+ return pLogger->PrintError("CRC error on %s", szArchivedName);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int RenameMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szOldFileName, const char * szNewFileName, int nExpectedError)
+{
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user
+ pLogger->PrintProgress("Renaming %s to %s ...", szOldFileName, szNewFileName);
+
+ // Perform the deletion
+ if(!SFileRenameFile(hMpq, szOldFileName, szNewFileName))
+ nError = GetLastError();
+
+ if(nError != nExpectedError)
+ return pLogger->PrintErrorVa("Unexpected result from SFileRenameFile(%s -> %s)", szOldFileName, szNewFileName);
+ return ERROR_SUCCESS;
+}
+
+static int RemoveMpqFile(TLogHelper * pLogger, HANDLE hMpq, const char * szFileName, int nExpectedError)
+{
+ int nError = ERROR_SUCCESS;
+
+ // Notify the user
+ pLogger->PrintProgress("Removing file %s ...", szFileName);
+
+ // Perform the deletion
+ if(!SFileRemoveFile(hMpq, szFileName, 0))
+ nError = GetLastError();
+
+ if(nError != nExpectedError)
+ return pLogger->PrintError("Unexpected result from SFileRemoveFile(%s)", szFileName);
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Tests
+
+static void TestGetFileInfo(
+ TLogHelper * pLogger,
+ HANDLE hMpqOrFile,
+ SFileInfoClass InfoClass,
+ void * pvFileInfo,
+ DWORD cbFileInfo,
+ DWORD * pcbLengthNeeded,
+ bool bExpectedResult,
+ int nExpectedError)
+{
+ bool bResult;
+ int nError = ERROR_SUCCESS;
+
+ // Call the get file info
+ bResult = SFileGetFileInfo(hMpqOrFile, InfoClass, pvFileInfo, cbFileInfo, pcbLengthNeeded);
+ if(!bResult)
+ nError = GetLastError();
+
+ if(bResult != bExpectedResult)
+ pLogger->PrintMessage("Different result of SFileGetFileInfo.");
+ if(nError != nExpectedError)
+ pLogger->PrintMessage("Different error from SFileGetFileInfo (expected %u, returned %u)", nExpectedError, nError);
+}
+
+// StormLib is able to open local files (as well as the original Storm.dll)
+// I want to keep this for occasional use
+static int TestOpenLocalFile(const char * szPlainName)
+{
+ TLogHelper Logger("OpenLocalFile", szPlainName);
+ HANDLE hFile;
+ DWORD dwFileSizeHi = 0;
+ DWORD dwFileSizeLo = 0;
+ char szFileName1[MAX_PATH];
+ char szFileName2[MAX_PATH];
+ char szFileLine[0x40];
+
+ CreateFullPathName(szFileName1, szMpqSubDir, szPlainName);
+ if(SFileOpenFileEx(NULL, szFileName1, SFILE_OPEN_LOCAL_FILE, &hFile))
+ {
+ // Retrieve the file name. It must match the name under which the file was open
+ SFileGetFileName(hFile, szFileName2);
+ if(strcmp(szFileName2, szFileName1))
+ Logger.PrintMessage("The retrieved name does not match the open name");
+
+ // Retrieve the file size
+ dwFileSizeLo = SFileGetFileSize(hFile, &dwFileSizeHi);
+ if(dwFileSizeHi != 0 || dwFileSizeLo != 3904784)
+ Logger.PrintMessage("Local file size mismatch");
+
+ // Read the first line
+ memset(szFileLine, 0, sizeof(szFileLine));
+ SFileReadFile(hFile, szFileLine, 18, NULL, NULL);
+ if(strcmp(szFileLine, "(1)Enslavers01.scm"))
+ Logger.PrintMessage("Content of the listfile does not match");
+
+ SFileCloseFile(hFile);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int TestSearchListFile(const char * szPlainName)
+{
+ SFILE_FIND_DATA sf;
+ TLogHelper Logger("SearchListFile", szPlainName);
+ HANDLE hFind;
+ char szFullPath[MAX_PATH];
+ int nFileCount = 0;
+
+ CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
+ hFind = SListFileFindFirstFile(NULL, szFullPath, "*", &sf);
+ if(hFind != NULL)
+ {
+ for(;;)
+ {
+ Logger.PrintProgress("Found file (%04u): %s", nFileCount++, GetShortPlainName(sf.cFileName));
+ if(!SListFileFindNextFile(hFind, &sf))
+ break;
+ }
+
+ SListFileFindClose(hFind);
+ }
+ return ERROR_SUCCESS;
+}
+
+static void WINAPI TestReadFile_DownloadCallback(
+ void * UserData,
+ ULONGLONG ByteOffset,
+ DWORD DataLength)
+{
+ TLogHelper * pLogger = (TLogHelper *)UserData;
+
+ if(ByteOffset != 0 && DataLength != 0)
+ pLogger->PrintProgress("Downloading data (offset: " I64X_a ", length: %X)", ByteOffset, DataLength);
+ else
+ pLogger->PrintProgress("Download complete.");
+}
+
+// Open a file stream with mirroring a master file
+static int TestReadFile_MasterMirror(const char * szMirrorName, const char * szMasterName, bool bCopyMirrorFile)
+{
+ TFileStream * pStream1; // Master file
+ TFileStream * pStream2; // Mirror file
+ TLogHelper Logger("OpenMirrorFile", szMirrorName);
+ DWORD dwProvider = 0;
+ char szMirrorPath[MAX_PATH + MAX_PATH];
+ char szMasterPath[MAX_PATH];
+ int nIterations = 0x10000;
+ int nError;
+
+ // Retrieve the provider
+ FileStream_PrefixA(szMasterName, &dwProvider);
+
+#ifndef PLATFORM_WINDOWS
+ if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
+ return ERROR_SUCCESS;
+#endif
+
+ // Create copy of the file to serve as mirror, keep master there
+ nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName, bCopyMirrorFile);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Open both master and mirror file
+ pStream1 = FileStream_OpenFileA(szMasterPath, STREAM_FLAG_READ_ONLY);
+ pStream2 = FileStream_OpenFileA(szMirrorPath, STREAM_FLAG_READ_ONLY | STREAM_FLAG_USE_BITMAP);
+ if(pStream1 && pStream2)
+ {
+ // For internet based files, we limit the number of operations
+ if((dwProvider & BASE_PROVIDER_MASK) == BASE_PROVIDER_HTTP)
+ nIterations = 0x80;
+
+ FileStream_SetCallback(pStream2, TestReadFile_DownloadCallback, &Logger);
+ nError = CompareTwoLocalFilesRR(&Logger, pStream1, pStream2, nIterations);
+ }
+
+ if(pStream2 != NULL)
+ FileStream_Close(pStream2);
+ if(pStream1 != NULL)
+ FileStream_Close(pStream1);
+ }
+
+ return nError;
+}
+
+// Test of the TFileStream object
+static int TestSparseCompression()
+{
+ BYTE InpBuffer[0x1000];
+ BYTE Compressed[0x1000];
+ BYTE Decompressed[0x1000];
+ int cbCompressed = sizeof(Compressed);
+ int cbDecompressed = sizeof(Compressed);
+
+ // Prepare compressed buffer
+ memset(InpBuffer, 0, sizeof(InpBuffer));
+
+ // Compress and decompress
+ CompressSparse(Compressed, &cbCompressed, InpBuffer, sizeof(InpBuffer));
+ DecompressSparse(Decompressed, &cbDecompressed, Compressed, cbCompressed);
+
+ // Check the result of decompression
+ if(cbDecompressed != sizeof(InpBuffer))
+ return ERROR_FILE_CORRUPT;
+ if(memcmp(Decompressed, InpBuffer, sizeof(InpBuffer)))
+ return ERROR_FILE_CORRUPT;
+
+ return ERROR_SUCCESS;
+}
+
+// Test of the TFileStream object
+static int TestFileStreamOperations(const char * szPlainName, DWORD dwStreamFlags)
+{
+ TFileStream * pStream = NULL;
+ TLogHelper Logger("FileStreamTest", szPlainName);
+ ULONGLONG ByteOffset;
+ ULONGLONG FileSize = 0;
+ DWORD dwRequiredFlags = 0;
+ char szFullPath[MAX_PATH];
+ BYTE Buffer[0x10];
+ int nError = ERROR_SUCCESS;
+
+ // Copy the file so we won't screw up
+ if((dwStreamFlags & STREAM_PROVIDER_MASK) == STREAM_PROVIDER_BLOCK4)
+ CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
+ else
+ nError = CreateFileCopy(&Logger, szPlainName, szPlainName, szFullPath);
+
+ // Open the file stream
+ if(nError == ERROR_SUCCESS)
+ {
+ pStream = FileStream_OpenFileA(szFullPath, dwStreamFlags);
+ if(pStream == NULL)
+ return Logger.PrintError("Failed to open %s", szFullPath);
+ }
+
+ // Get the size of the file stream
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!FileStream_GetFlags(pStream, &dwStreamFlags))
+ nError = Logger.PrintError("Failed to retrieve the stream flags");
+
+ if(!FileStream_GetSize(pStream, &FileSize))
+ nError = Logger.PrintError("Failed to retrieve the file size");
+
+ // Any other stream except STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE should be read-only
+ if((dwStreamFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
+ dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
+// if(pStream->BlockPresent)
+// dwRequiredFlags |= STREAM_FLAG_READ_ONLY;
+
+ // Check the flags there
+ if((dwStreamFlags & dwRequiredFlags) != dwRequiredFlags)
+ {
+ Logger.PrintMessage("The stream should be read-only but it isn't");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // After successful open, the stream position must be zero
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, 0);
+
+ // Read the MPQ header from the current file offset.
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFileMpqHeader(&Logger, pStream, NULL);
+
+ // After successful open, the stream position must sizeof(TMPQHeader)
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, sizeof(TMPQHeader));
+
+ // Now try to read the MPQ header from the offset 0
+ if(nError == ERROR_SUCCESS)
+ {
+ ByteOffset = 0;
+ nError = VerifyFileMpqHeader(&Logger, pStream, &ByteOffset);
+ }
+
+ // After successful open, the stream position must sizeof(TMPQHeader)
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, sizeof(TMPQHeader));
+
+ // Try a write operation
+ if(nError == ERROR_SUCCESS)
+ {
+ bool bExpectedResult = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? false : true;
+ bool bResult;
+
+ // Attempt to write to the file
+ ByteOffset = 0;
+ bResult = FileStream_Write(pStream, &ByteOffset, Buffer, sizeof(Buffer));
+
+ // If the result is not expected
+ if(bResult != bExpectedResult)
+ {
+ Logger.PrintMessage("FileStream_Write result is different than expected");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Move the position 9 bytes from the end and try to read 10 bytes.
+ // This must fail, because stream reading functions are "all or nothing"
+ if(nError == ERROR_SUCCESS)
+ {
+ ByteOffset = FileSize - 9;
+ if(FileStream_Read(pStream, &ByteOffset, Buffer, 10))
+ {
+ Logger.PrintMessage("FileStream_Read succeeded, but it shouldn't");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Try again with 9 bytes. This must succeed, unless the file block is not available
+ if(nError == ERROR_SUCCESS)
+ {
+ ByteOffset = FileSize - 9;
+ if(!FileStream_Read(pStream, &ByteOffset, Buffer, 9))
+ {
+ Logger.PrintMessage("FileStream_Read from the end of the file failed");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Verify file position - it must be at the end of the file
+ if(nError == ERROR_SUCCESS)
+ nError = VerifyFilePosition(&Logger, pStream, FileSize);
+
+ // Close the stream
+ if(pStream != NULL)
+ FileStream_Close(pStream);
+ return nError;
+}
+
+static int TestOpenFile_OpenById(const char * szPlainName)
+{
+ TLogHelper Logger("OpenFileById", szPlainName);
+ TFileData * pFileData1 = NULL;
+ TFileData * pFileData2 = NULL;
+ HANDLE hMpq;
+ int nError;
+
+ // Copy the archive so we won't fuck up the original one
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, NULL, &hMpq);
+
+ // Now try to open a file without knowing the file name
+ if(nError == ERROR_SUCCESS)
+ {
+ // File00000023.xxx = music\dintro.wav
+ pFileData1 = LoadMpqFile(&Logger, hMpq, "File00000023.xxx");
+ if(pFileData1 == NULL)
+ nError = Logger.PrintError("Failed to load the file %s", "File00000023.xxx");
+ }
+
+ // Now try to open the file again with its original name
+ if(nError == ERROR_SUCCESS)
+ {
+ // File00000023.xxx = music\dintro.wav
+ pFileData2 = LoadMpqFile(&Logger, hMpq, "music\\dintro.wav");
+ if(pFileData2 == NULL)
+ nError = Logger.PrintError("Failed to load the file %s", "music\\dintro.wav");
+ }
+
+ // Now compare both files
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!CompareTwoFiles(&Logger, pFileData1, pFileData1))
+ nError = Logger.PrintError("The file has different size/content when open without name");
+ }
+
+ // Close the archive
+ if(pFileData2 != NULL)
+ STORM_FREE(pFileData2);
+ if(pFileData1 != NULL)
+ STORM_FREE(pFileData1);
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+static int TestOpenArchive(const char * szPlainName, const char * szListFile = NULL, bool bDontCopyArchive = false)
+{
+ TLogHelper Logger("OpenMpqTest", szPlainName);
+ TFileData * pFileData;
+ const char * szCopyName = (bDontCopyArchive) ? NULL : szPlainName;
+ HANDLE hMpq;
+ DWORD dwFileCount = 0;
+ DWORD dwTestFlags;
+ char szListFileBuff[MAX_PATH];
+ bool bIsPartialMpq = false;
+ int nError;
+
+ // If the file is a partial MPQ, don't load all files
+ bIsPartialMpq = (strstr(szPlainName, ".MPQ.part") != NULL);
+
+ // Copy the archive so we won't fuck up the original one
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szCopyName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // If the listfile was given, add it to the MPQ
+ if(szListFile != NULL)
+ {
+ Logger.PrintProgress("Adding listfile %s ...", szListFile);
+ CreateFullPathName(szListFileBuff, szMpqSubDir, szListFile);
+ nError = SFileAddListFile(hMpq, szListFileBuff);
+ if(nError != ERROR_SUCCESS)
+ Logger.PrintMessage("Failed to add the listfile to the MPQ");
+ }
+
+ // Attempt to open the listfile and attributes
+ if(SFileHasFile(hMpq, LISTFILE_NAME))
+ {
+ pFileData = LoadMpqFile(&Logger, hMpq, LISTFILE_NAME);
+ if(pFileData != NULL)
+ STORM_FREE(pFileData);
+ }
+
+ // Attempt to open the listfile and attributes
+ if(SFileHasFile(hMpq, ATTRIBUTES_NAME))
+ {
+ pFileData = LoadMpqFile(&Logger, hMpq, ATTRIBUTES_NAME);
+ if(pFileData != NULL)
+ STORM_FREE(pFileData);
+ }
+
+ // Search the archive and load every file
+ dwTestFlags = bIsPartialMpq ? 0 : TEST_FLAG_LOAD_FILES;
+ nError = SearchArchive(&Logger, hMpq, dwTestFlags, &dwFileCount);
+ SFileCloseArchive(hMpq);
+ }
+
+ return nError;
+}
+
+
+// Open an empty archive (found in WoW cache - it's just a header)
+static int TestOpenArchive_WillFail(const char * szPlainName)
+{
+ TLogHelper Logger("FailMpqTest", szPlainName);
+ HANDLE hMpq = NULL;
+ TCHAR szMpqName[MAX_PATH];
+ char szFullPath[MAX_PATH];
+
+ // Create the full path name for the archive
+ CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
+ CopyFileName(szMpqName, szFullPath, strlen(szFullPath));
+
+ // Try to open the archive. It is expected to fail
+ Logger.PrintProgress("Opening archive %s", szPlainName);
+ if(!SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq))
+ return ERROR_SUCCESS;
+
+ Logger.PrintError("Archive %s succeeded to open, even if it should not.", szPlainName);
+ SFileCloseArchive(hMpq);
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+static int TestOpenArchive_Corrupt(const char * szPlainName)
+{
+ TLogHelper Logger("OpenCorruptMpqTest", szPlainName);
+ HANDLE hMpq = NULL;
+ TCHAR szFullPathT[MAX_PATH];
+ char szFullPath[MAX_PATH];
+
+ // Copy the archive so we won't fuck up the original one
+ CreateFullPathName(szFullPath, szMpqSubDir, szPlainName);
+ CopyFileName(szFullPathT, szFullPath, strlen(szFullPath));
+ if(SFileOpenArchive(szFullPathT, 0, STREAM_FLAG_READ_ONLY, &hMpq))
+ {
+ SFileCloseArchive(hMpq);
+ Logger.PrintMessage("Opening archive %s succeeded, but it shouldn't", szFullPath);
+ return ERROR_CAN_NOT_COMPLETE;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+
+// Opens a patched MPQ archive
+static int TestOpenArchive_Patched(const char * PatchList[], const char * szPatchedFile, int nExpectedPatchCount)
+{
+ TLogHelper Logger("OpenPatchedMpqTest", PatchList[0]);
+ HANDLE hMpq;
+ HANDLE hFile;
+ BYTE Buffer[0x100];
+ DWORD dwFileCount = 0;
+ DWORD BytesRead = 0;
+ int nError;
+
+ // Open a patched MPQ archive
+ nError = OpenPatchedArchive(&Logger, &hMpq, PatchList);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Check patch count
+ if(szPatchedFile != NULL)
+ nError = VerifyFilePatchCount(&Logger, hMpq, szPatchedFile, nExpectedPatchCount);
+
+ // Try to open and read the file
+ if(nError == ERROR_SUCCESS)
+ {
+ if(SFileOpenFileEx(hMpq, szPatchedFile, 0, &hFile))
+ {
+ SFileReadFile(hFile, Buffer, sizeof(Buffer), &BytesRead, NULL);
+ SFileCloseFile(hFile);
+ }
+ }
+
+ // Search the archive and load every file
+ if(nError == ERROR_SUCCESS)
+ nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES, &dwFileCount);
+
+ // Close the archive
+ SFileCloseArchive(hMpq);
+ }
+
+ return nError;
+}
+
+// Open an archive for read-only access
+static int TestOpenArchive_ReadOnly(const char * szPlainName, bool bReadOnly)
+{
+ const char * szCopyName;
+ TLogHelper Logger("ReadOnlyTest", szPlainName);
+ HANDLE hMpq = NULL;
+ char szFullPathName[MAX_PATH];
+ DWORD dwFlags = bReadOnly ? MPQ_OPEN_READ_ONLY : 0;;
+ int nExpectedError;
+ int nError;
+
+ // Copy the fiel so we wont screw up something
+ szCopyName = bReadOnly ? "StormLibTest_ReadOnly.mpq" : "StormLibTest_ReadWrite.mpq";
+ nError = CreateFileCopy(&Logger, szPlainName, szCopyName, szFullPathName);
+
+ // Now open the archive for read-only access
+ if(nError == ERROR_SUCCESS)
+ nError = OpenExistingArchive(&Logger, szFullPathName, dwFlags, &hMpq);
+
+ // Now try to add a file. This must fail if the MPQ is read only
+ if(nError == ERROR_SUCCESS)
+ {
+ nExpectedError = (bReadOnly) ? ERROR_ACCESS_DENIED : ERROR_SUCCESS;
+ AddFileToMpq(&Logger, hMpq, "AddedFile.txt", "This is an added file.", MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, 0, nExpectedError);
+ }
+
+ // Now try to rename a file in the MPQ. This must only succeed if the MPQ is not read only
+ if(nError == ERROR_SUCCESS)
+ {
+ nExpectedError = (bReadOnly) ? ERROR_ACCESS_DENIED : ERROR_SUCCESS;
+ RenameMpqFile(&Logger, hMpq, "spawn.mpq", "spawn-renamed.mpq", nExpectedError);
+ }
+
+ // Now try to delete a file in the MPQ. This must only succeed if the MPQ is not read only
+ if(nError == ERROR_SUCCESS)
+ {
+ nExpectedError = (bReadOnly) ? ERROR_ACCESS_DENIED : ERROR_SUCCESS;
+ RemoveMpqFile(&Logger, hMpq, "spawn-renamed.mpq", nExpectedError);
+ }
+
+ // Close the archive
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+static int TestOpenArchive_GetFileInfo(const char * szPlainName1, const char * szPlainName4)
+{
+ TLogHelper Logger("GetFileInfoTest", szPlainName1, szPlainName4);
+ HANDLE hFile;
+ HANDLE hMpq4;
+ HANDLE hMpq1;
+ DWORD cbLength;
+ BYTE DataBuff[0x400];
+ int nError1;
+ int nError4;
+
+ // Copy the archive so we won't fuck up the original one
+ nError1 = OpenExistingArchiveWithCopy(&Logger, szPlainName1, NULL, &hMpq1);
+ nError4 = OpenExistingArchiveWithCopy(&Logger, szPlainName4, NULL, &hMpq4);
+ if(nError1 == ERROR_SUCCESS && nError4 == ERROR_SUCCESS)
+ {
+ // Invalid handle - expected (false, ERROR_INVALID_HANDLE)
+ TestGetFileInfo(&Logger, NULL, SFileMpqBetHeader, NULL, 0, NULL, false, ERROR_INVALID_HANDLE);
+
+ // Valid handle but invalid value of file info class (false, ERROR_INVALID_PARAMETER)
+ TestGetFileInfo(&Logger, NULL, (SFileInfoClass)0xFFF, NULL, 0, NULL, false, ERROR_INVALID_PARAMETER);
+
+ // Valid archive handle but file info class is for file (false, ERROR_INVALID_HANDLE)
+ TestGetFileInfo(&Logger, NULL, SFileInfoNameHash1, NULL, 0, NULL, false, ERROR_INVALID_HANDLE);
+
+ // Valid handle and all parameters NULL
+ // Returns (true, ERROR_SUCCESS), if BET table is present, otherwise (false, ERROR_CAN_NOT_COMPLETE)
+ TestGetFileInfo(&Logger, hMpq1, SFileMpqBetHeader, NULL, 0, NULL, false, ERROR_FILE_NOT_FOUND);
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 0, NULL, true, ERROR_SUCCESS);
+
+ // Now try to retrieve the required size of the BET table header
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 0, &cbLength, true, ERROR_SUCCESS);
+
+ // When we call SFileInfo with buffer = NULL and nonzero buffer size, it is ignored
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, NULL, 3, &cbLength, true, ERROR_SUCCESS);
+
+ // When we call SFileInfo with buffer != NULL and nonzero buffer size, it should return error
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, 3, &cbLength, false, ERROR_INSUFFICIENT_BUFFER);
+
+ // Request for bet table header should also succeed if we want header only
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, sizeof(TMPQBetHeader), &cbLength, true, ERROR_SUCCESS);
+
+ // Request for bet table header should also succeed if we want header+flag table only
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqBetHeader, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS);
+
+ // Try to retrieve strong signature from the MPQ
+ TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, NULL, 0, NULL, true, ERROR_SUCCESS);
+ TestGetFileInfo(&Logger, hMpq4, SFileMpqStrongSignature, NULL, 0, NULL, false, ERROR_FILE_NOT_FOUND);
+
+ // Strong signature is returned including the signature ID
+ TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, NULL, 0, &cbLength, true, ERROR_SUCCESS);
+ assert(cbLength == MPQ_STRONG_SIGNATURE_SIZE + 4);
+
+ // Retrieve the signature
+ TestGetFileInfo(&Logger, hMpq1, SFileMpqStrongSignature, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS);
+ assert(memcmp(DataBuff, "NGIS", 4) == 0);
+
+ // Check SFileGetFileInfo on
+ if(SFileOpenFileEx(hMpq4, LISTFILE_NAME, 0, &hFile))
+ {
+ // Valid parameters but the handle should be file handle
+ TestGetFileInfo(&Logger, hMpq4, SFileInfoFileTime, DataBuff, sizeof(DataBuff), &cbLength, false, ERROR_INVALID_HANDLE);
+
+ // Valid parameters
+ TestGetFileInfo(&Logger, hFile, SFileInfoFileTime, DataBuff, sizeof(DataBuff), &cbLength, true, ERROR_SUCCESS);
+
+ SFileCloseFile(hFile);
+ }
+ }
+
+ if(hMpq4 != NULL)
+ SFileCloseArchive(hMpq4);
+ if(hMpq1 != NULL)
+ SFileCloseArchive(hMpq1);
+ return ERROR_SUCCESS;
+}
+
+static int TestOpenArchive_MasterMirror(const char * szMirrorName, const char * szMasterName, const char * szFileToExtract, bool bCopyMirrorFile)
+{
+ TFileData * pFileData;
+ TLogHelper Logger("OpenServerMirror", szMirrorName);
+ HANDLE hFile = NULL;
+ HANDLE hMpq = NULL;
+ DWORD dwVerifyResult;
+ char szMirrorPath[MAX_PATH + MAX_PATH]; // Combined name
+ char szMasterPath[MAX_PATH]; // Original (server) name
+ int nError;
+
+ // Create both paths
+ nError = CreateMasterAndMirrorPaths(&Logger, szMirrorPath, szMasterPath, szMirrorName, szMasterName, bCopyMirrorFile);
+
+ // Now open both archives as local-server pair
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = OpenExistingArchive(&Logger, szMirrorPath, 0, &hMpq);
+ }
+
+ // The MPQ must be read-only. Writing to mirrored MPQ is not allowed
+ if(nError == ERROR_SUCCESS)
+ {
+ if(SFileCreateFile(hMpq, "AddedFile.bin", 0, 0x10, 0, MPQ_FILE_COMPRESS, &hFile))
+ {
+ SFileCloseFile(hFile);
+ Logger.PrintMessage("The archive is writable, although it should not be");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Verify the file
+ if(nError == ERROR_SUCCESS && szFileToExtract != NULL)
+ {
+ dwVerifyResult = SFileVerifyFile(hMpq, szFileToExtract, SFILE_VERIFY_ALL);
+ if(dwVerifyResult & VERIFY_FILE_ERROR_MASK)
+ {
+ Logger.PrintMessage("File verification failed");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Load the file to memory
+ if(nError == ERROR_SUCCESS && szFileToExtract)
+ {
+ pFileData = LoadMpqFile(&Logger, hMpq, szFileToExtract);
+ if(pFileData != NULL)
+ STORM_FREE(pFileData);
+ }
+
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+
+static int TestOpenArchive_VerifySignature(const char * szPlainName, const char * szOriginalName)
+{
+ TLogHelper Logger("VerifySignatureTest", szPlainName);
+ HANDLE hMpq;
+ DWORD dwSignatures = 0;
+ int nVerifyError;
+ int nError = ERROR_SUCCESS;
+
+ // We need original name for the signature check
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szOriginalName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Query the signature types
+ Logger.PrintProgress("Retrieving signatures ...");
+ TestGetFileInfo(&Logger, hMpq, SFileMpqSignatures, &dwSignatures, sizeof(DWORD), NULL, true, ERROR_SUCCESS);
+
+ // Verify any of the present signatures
+ Logger.PrintProgress("Verifying archive signature ...");
+ nVerifyError = SFileVerifyArchive(hMpq);
+
+ // Verify the result
+ if((dwSignatures & SIGNATURE_TYPE_STRONG) && (nVerifyError != ERROR_STRONG_SIGNATURE_OK))
+ {
+ Logger.PrintMessage("Strong signature verification error");
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Verify the result
+ if((dwSignatures & SIGNATURE_TYPE_WEAK) && (nVerifyError != ERROR_WEAK_SIGNATURE_OK))
+ {
+ Logger.PrintMessage("Weak signature verification error");
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ SFileCloseArchive(hMpq);
+ }
+ return nError;
+}
+
+static int TestOpenArchive_ModifySigned(const char * szPlainName, const char * szOriginalName)
+{
+ TLogHelper Logger("ModifySignedTest", szPlainName);
+ HANDLE hMpq = NULL;
+ int nVerifyError;
+ int nError = ERROR_SUCCESS;
+
+ // We need original name for the signature check
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szOriginalName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify the weak signature
+ Logger.PrintProgress("Verifying archive signature ...");
+ nVerifyError = SFileVerifyArchive(hMpq);
+
+ // Check the result signature
+ if(nVerifyError != ERROR_WEAK_SIGNATURE_OK)
+ {
+ Logger.PrintMessage("Weak signature verification error");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Add a file and verify the signature again
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify any of the present signatures
+ Logger.PrintProgress("Modifying signed archive ...");
+ nError = AddFileToMpq(&Logger, hMpq, "AddedFile01.txt", "This is a file added to signed MPQ", 0, 0, ERROR_SUCCESS);
+ }
+
+ // Verify the signature again
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify the weak signature
+ Logger.PrintProgress("Verifying archive signature ...");
+ nVerifyError = SFileVerifyArchive(hMpq);
+
+ // Check the result signature
+ if(nVerifyError != ERROR_WEAK_SIGNATURE_OK)
+ {
+ Logger.PrintMessage("Weak signature verification error");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Close the MPQ
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+static int TestOpenArchive_SignExisting(const char * szPlainName)
+{
+ TLogHelper Logger("SignExistingMpq", szPlainName);
+ HANDLE hMpq = NULL;
+ int nVerifyError;
+ int nError = ERROR_SUCCESS;
+
+ // We need original name for the signature check
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szPlainName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify the weak signature
+ Logger.PrintProgress("Verifying archive signature ...");
+ nVerifyError = SFileVerifyArchive(hMpq);
+
+ // Check the result signature
+ if(nVerifyError != ERROR_NO_SIGNATURE)
+ {
+ Logger.PrintMessage("There already is a signature in the MPQ");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Add a file and verify the signature again
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify any of the present signatures
+ Logger.PrintProgress("Signing the MPQ ...");
+ if(!SFileSignArchive(hMpq, SIGNATURE_TYPE_WEAK))
+ {
+ Logger.PrintMessage("Failed to create archive signature");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Verify the signature again
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify the weak signature
+ Logger.PrintProgress("Verifying archive signature ...");
+ nVerifyError = SFileVerifyArchive(hMpq);
+
+ // Check the result signature
+ if(nVerifyError != ERROR_WEAK_SIGNATURE_OK)
+ {
+ Logger.PrintMessage("Weak signature verification error");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Close the MPQ
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+// Open an empty archive (found in WoW cache - it's just a header)
+static int TestOpenArchive_CraftedUserData(const char * szPlainName, const char * szCopyName)
+{
+ TLogHelper Logger("CraftedMpqTest", szPlainName);
+ HANDLE hMpq;
+ DWORD dwFileCount1 = 0;
+ DWORD dwFileCount2 = 0;
+ BYTE FileHash1[MD5_DIGEST_SIZE];
+ BYTE FileHash2[MD5_DIGEST_SIZE];
+ char szFullPath[MAX_PATH];
+ int nError;
+
+ // Create copy of the archive, with interleaving some user data
+ nError = CreateFileCopy(&Logger, szPlainName, szCopyName, szFullPath, 0x400, 0x531);
+
+ // Open the archive and load some files
+ if(nError == ERROR_SUCCESS)
+ {
+ // Open the archive
+ nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Verify presence of (listfile) and (attributes)
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true);
+
+ // Search the archive and load every file
+ nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES, &dwFileCount1, FileHash1);
+ SFileCloseArchive(hMpq);
+ }
+
+ // Try to compact the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ // Open the archive again
+ nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Compact the archive
+ Logger.PrintProgress("Compacting archive %s ...", GetShortPlainName(szFullPath));
+ if(!SFileSetCompactCallback(hMpq, CompactCallback, &Logger))
+ nError = Logger.PrintError("Failed to compact archive %s", szFullPath);
+
+ SFileCompactArchive(hMpq, NULL, false);
+ SFileCloseArchive(hMpq);
+ }
+
+ // Open the archive and load some files
+ if(nError == ERROR_SUCCESS)
+ {
+ // Open the archive
+ nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Verify presence of (listfile) and (attributes)
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true);
+
+ // Search the archive and load every file
+ nError = SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES, &dwFileCount2, FileHash2);
+ SFileCloseArchive(hMpq);
+ }
+
+ // Compare the file counts and their hashes
+ if(nError == ERROR_SUCCESS)
+ {
+ if(dwFileCount2 != dwFileCount1)
+ Logger.PrintMessage("Different file count after compacting archive: %u vs %u", dwFileCount2, dwFileCount1);
+
+ if(memcmp(FileHash2, FileHash1, MD5_DIGEST_SIZE))
+ Logger.PrintMessage("Different file hash after compacting archive");
+ }
+
+ return nError;
+}
+
+static int ForEachFile_VerifyFileChecksum(const char * szFullPath)
+{
+ const char * szShortPlainName = GetShortPlainName(szFullPath);
+ TFileData * pFileData;
+ char * szExtension;
+ char szShaFileName[MAX_PATH+1];
+ char szSha1Text[0x40];
+ int nError = ERROR_SUCCESS;
+
+ // Try to load the file with the SHA extension
+ StringCopyA(szShaFileName, szFullPath, MAX_PATH);
+ szExtension = strrchr(szShaFileName, '.');
+ if(szExtension == NULL)
+ return ERROR_SUCCESS;
+
+ // Skip .SHA and .TXT files
+ if(!_stricmp(szExtension, ".sha") || !_stricmp(szExtension, ".txt"))
+ return ERROR_SUCCESS;
+
+ // Load the local file to memory
+ strcpy(szExtension, ".sha");
+ pFileData = LoadLocalFile(NULL, szShaFileName, false);
+ if(pFileData != NULL)
+ {
+ TLogHelper Logger("VerifyFileHash", szShortPlainName);
+
+ // Calculate SHA1 of the entire file
+ nError = CalculateFileSha1(&Logger, szFullPath, szSha1Text);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Compare with what we loaded from the file
+ if(pFileData->dwFileSize >= (SHA1_DIGEST_SIZE * 2))
+ {
+ // Compare the SHA1
+ if(_strnicmp(szSha1Text, (char *)pFileData->FileData, (SHA1_DIGEST_SIZE * 2)))
+ {
+ Logger.PrintError("File CRC check failed: %s", szFullPath);
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ }
+
+ STORM_FREE(pFileData);
+ }
+
+ return nError;
+}
+
+// Opens a found archive
+static int ForEachFile_OpenArchive(const char * szFullPath)
+{
+ HANDLE hMpq = NULL;
+ DWORD dwFileCount = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Check if it's a MPQ file type
+ if(IsMpqExtension(szFullPath))
+ {
+ TLogHelper Logger("OpenEachMpqTest", GetShortPlainName(szFullPath));
+
+ // Open the MPQ name
+ nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
+ if(nError == ERROR_AVI_FILE || nError == ERROR_FILE_CORRUPT || nError == ERROR_BAD_FORMAT)
+ return ERROR_SUCCESS;
+
+ // Search the archive and load every file
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = SearchArchive(&Logger, hMpq, 0, &dwFileCount);
+ SFileCloseArchive(hMpq);
+ }
+ }
+
+ // Correct some errors
+ if(nError == ERROR_FILE_CORRUPT || nError == ERROR_FILE_INCOMPLETE)
+ return ERROR_SUCCESS;
+ return nError;
+}
+
+// Adding a file to MPQ that had size of the file table equal
+// or greater than hash table, but has free entries
+static int TestAddFile_FullTable(const char * szFullMpqName)
+{
+ TLogHelper Logger("FullMpqTest", szFullMpqName);
+ const char * szFileName = "AddedFile001.txt";
+ const char * szFileData = "0123456789ABCDEF";
+ HANDLE hMpq = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Copy the archive so we won't fuck up the original one
+ nError = OpenExistingArchiveWithCopy(&Logger, szFullMpqName, szFullMpqName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Attempt to add a file
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, MPQ_FILE_IMPLODE, MPQ_COMPRESSION_PKWARE, ERROR_SUCCESS);
+ SFileCloseArchive(hMpq);
+ }
+
+ return nError;
+}
+
+// Adding a file to MPQ that had no (listfile) and no (attributes).
+// We expect that neither of these will be present after the archive is closed
+static int TestAddFile_ListFileTest(const char * szSourceMpq, bool bShouldHaveListFile, bool bShouldHaveAttributes)
+{
+ TLogHelper Logger("ListFileTest", szSourceMpq);
+ TFileData * pFileData = NULL;
+ const char * szBackupMpq = bShouldHaveListFile ? "StormLibTest_HasListFile.mpq" : "StormLibTest_NoListFile.mpq";
+ const char * szFileName = "AddedFile001.txt";
+ const char * szFileData = "0123456789ABCDEF";
+ HANDLE hMpq = NULL;
+ DWORD dwFileSize = (DWORD)strlen(szFileData);
+ int nError = ERROR_SUCCESS;
+
+ // Copy the archive so we won't fuck up the original one
+ nError = OpenExistingArchiveWithCopy(&Logger, szSourceMpq, szBackupMpq, &hMpq);
+
+ // Add a file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Now add a file
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, MPQ_FILE_IMPLODE, MPQ_COMPRESSION_PKWARE);
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+ }
+
+ // Now reopen the archive
+ if(nError == ERROR_SUCCESS)
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szBackupMpq, &hMpq);
+
+ // Now the file has been written and the MPQ has been saved.
+ // We Reopen the MPQ and check if there is no (listfile) nor (attributes).
+ if(nError == ERROR_SUCCESS)
+ {
+ // Verify presence of (listfile) and (attributes)
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, bShouldHaveListFile);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, bShouldHaveAttributes);
+
+ // Try to open the file that we recently added
+ pFileData = LoadMpqFile(&Logger, hMpq, szFileName);
+ if(pFileData != NULL)
+ {
+ // Verify if the file size matches
+ if(pFileData->dwFileSize == dwFileSize)
+ {
+ // Verify if the file data match
+ if(memcmp(pFileData->FileData, szFileData, dwFileSize))
+ {
+ Logger.PrintError("The data of the added file does not match");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+ else
+ {
+ Logger.PrintError("The size of the added file does not match");
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ // Delete the file data
+ STORM_FREE(pFileData);
+ }
+ else
+ {
+ nError = Logger.PrintError("Failed to open the file previously added");
+ }
+ }
+
+ // Close the MPQ archive
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+/*
+static int TestCreateArchive_Deprotect(const char * szPlainName)
+{
+ TLogHelper Logger("DeprotectTest", szPlainName);
+ HANDLE hMpq1 = NULL;
+ HANDLE hMpq2 = NULL;
+ char szMpqName1[MAX_PATH];
+ char szMpqName2[MAX_PATH];
+ BYTE FileHash1[MD5_DIGEST_SIZE];
+ BYTE FileHash2[MD5_DIGEST_SIZE];
+ DWORD dwFileCount1 = 0;
+ DWORD dwFileCount2 = 0;
+ DWORD dwTestFlags = TEST_FLAG_LOAD_FILES | TEST_FLAG_HASH_FILES;
+ int nError = ERROR_SUCCESS;
+
+ // First copy: The original (untouched) file
+ if(nError == ERROR_SUCCESS)
+ {
+ AddStringBeforeExtension(szMpqName1, szPlainName, "_original");
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szMpqName1, &hMpq1);
+ if(nError != ERROR_SUCCESS)
+ Logger.PrintMessage("Failed to open %s", szMpqName1);
+ }
+
+ // Second copy: Will be deprotected
+ if(nError == ERROR_SUCCESS)
+ {
+ AddStringBeforeExtension(szMpqName2, szPlainName, "_deprotected");
+ nError = OpenExistingArchiveWithCopy(&Logger, szPlainName, szMpqName2, &hMpq2);
+ if(nError != ERROR_SUCCESS)
+ Logger.PrintMessage("Failed to open %s", szMpqName2);
+ }
+
+ // Deprotect the second map
+ if(nError == ERROR_SUCCESS)
+ {
+ SFileSetCompactCallback(hMpq2, CompactCallback, &Logger);
+ if(!SFileCompactArchive(hMpq2, NULL, false))
+ nError = Logger.PrintError("Failed to deprotect archive %s", szMpqName2);
+ }
+
+ // Calculate number of files and compare their hash (archive 1)
+ if(nError == ERROR_SUCCESS)
+ {
+ memset(FileHash1, 0, sizeof(FileHash1));
+ nError = SearchArchive(&Logger, hMpq1, dwTestFlags, &dwFileCount1, FileHash1);
+ }
+
+ // Calculate number of files and compare their hash (archive 2)
+ if(nError == ERROR_SUCCESS)
+ {
+ memset(FileHash1, 0, sizeof(FileHash2));
+ nError = SearchArchive(&Logger, hMpq2, dwTestFlags, &dwFileCount2, FileHash2);
+ }
+
+ if(nError == ERROR_SUCCESS)
+ {
+ if(dwFileCount1 != dwFileCount2)
+ Logger.PrintMessage("Different file count (%u in %s; %u in %s)", dwFileCount1, szMpqName1, dwFileCount2, szMpqName2);
+ if(memcmp(FileHash1, FileHash2, MD5_DIGEST_SIZE))
+ Logger.PrintMessage("Different file hash (%s vs %s)", szMpqName1, szMpqName2);
+ }
+
+ // Close both MPQs
+ if(hMpq2 != NULL)
+ SFileCloseArchive(hMpq2);
+ if(hMpq1 != NULL)
+ SFileCloseArchive(hMpq1);
+ return nError;
+}
+*/
+static int TestCreateArchive_EmptyMpq(const char * szPlainName, DWORD dwCreateFlags)
+{
+ TLogHelper Logger("CreateEmptyMpq", szPlainName);
+ HANDLE hMpq = NULL;
+ DWORD dwFileCount = 0;
+ int nError;
+
+ // Create the full path name
+ nError = CreateNewArchive(&Logger, szPlainName, dwCreateFlags, 0, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ SearchArchive(&Logger, hMpq);
+ SFileCloseArchive(hMpq);
+ }
+
+ // Reopen the empty MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ SFileGetFileInfo(hMpq, SFileMpqNumberOfFiles, &dwFileCount, sizeof(dwFileCount), NULL);
+
+ CheckIfFileIsPresent(&Logger, hMpq, "File00000000.xxx", false);
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, false);
+ SearchArchive(&Logger, hMpq);
+ SFileCloseArchive(hMpq);
+ }
+ }
+
+ return nError;
+}
+
+static int TestCreateArchive_TestGaps(const char * szPlainName)
+{
+ TLogHelper Logger("CreateGapsTest", szPlainName);
+ ULONGLONG ByteOffset1 = 0xFFFFFFFF;
+ ULONGLONG ByteOffset2 = 0xEEEEEEEE;
+ HANDLE hMpq = NULL;
+ HANDLE hFile = NULL;
+ char szFullPath[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ // Create new MPQ
+ nError = CreateNewArchive_V2(&Logger, szPlainName, MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES | MPQ_FORMAT_VERSION_4, 4000, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Add one file and flush the archive
+ nError = AddFileToMpq(&Logger, hMpq, "AddedFile01.txt", "This is the file data.", MPQ_FILE_COMPRESS);
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+ }
+
+ // Reopen the MPQ and add another file.
+ // The new file must be added to the position of the (listfile)
+ if(nError == ERROR_SUCCESS)
+ {
+ CreateFullPathName(szFullPath, NULL, szPlainName);
+ nError = OpenExistingArchive(&Logger, szFullPath, 0, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Retrieve the position of the (listfile)
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hFile))
+ {
+ SFileGetFileInfo(hFile, SFileInfoByteOffset, &ByteOffset1, sizeof(ULONGLONG), NULL);
+ SFileCloseFile(hFile);
+ }
+ else
+ nError = GetLastError();
+ }
+ }
+
+ // Add another file and check its position. It must be at the position of the former listfile
+ if(nError == ERROR_SUCCESS)
+ {
+ const char * szAddedFile = "AddedFile02.txt";
+
+ // Add another file
+ nError = AddFileToMpq(&Logger, hMpq, szAddedFile, "This is the second added file.", MPQ_FILE_COMPRESS);
+
+ // Retrieve the position of the (listfile)
+ if(SFileOpenFileEx(hMpq, szAddedFile, 0, &hFile))
+ {
+ SFileGetFileInfo(hFile, SFileInfoByteOffset, &ByteOffset2, sizeof(ULONGLONG), NULL);
+ SFileCloseFile(hFile);
+ }
+ else
+ nError = GetLastError();
+ }
+
+ // Now check the positions
+ if(nError == ERROR_SUCCESS)
+ {
+ if(ByteOffset1 != ByteOffset2)
+ {
+ Logger.PrintError("The added file was not written to the position of (listfile)");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Close the archive if needed
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+static int TestCreateArchive_Signed(const char * szPlainName, bool bSignAtCreate)
+{
+ TLogHelper Logger("CreateSignedMpq", szPlainName);
+ HANDLE hMpq = NULL;
+ DWORD dwCreateFlags = MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES | MPQ_FORMAT_VERSION_1;
+ DWORD dwSignatures = 0;
+ DWORD nVerifyError = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Method 1: Create the archive as signed
+ if(bSignAtCreate)
+ dwCreateFlags |= MPQ_CREATE_SIGNATURE;
+
+ // Create new MPQ
+ nError = CreateNewArchive_V2(&Logger, szPlainName, dwCreateFlags, 4000, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Add one file and flush the archive
+ nError = AddFileToMpq(&Logger, hMpq, "AddedFile01.txt", "This is the file data.", MPQ_FILE_COMPRESS);
+ }
+
+ // Sign the archive with weak signature
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!SFileSignArchive(hMpq, SIGNATURE_TYPE_WEAK))
+ nError = ERROR_SUCCESS;
+ }
+
+ // Reopen the MPQ and add another file.
+ // The new file must be added to the position of the (listfile)
+ if(nError == ERROR_SUCCESS)
+ {
+ // Query the signature types
+ Logger.PrintProgress("Retrieving signatures ...");
+ TestGetFileInfo(&Logger, hMpq, SFileMpqSignatures, &dwSignatures, sizeof(DWORD), NULL, true, ERROR_SUCCESS);
+
+ // Verify any of the present signatures
+ Logger.PrintProgress("Verifying archive signature ...");
+ nVerifyError = SFileVerifyArchive(hMpq);
+
+ // Verify the result
+ if((dwSignatures != SIGNATURE_TYPE_WEAK) && (nVerifyError != ERROR_WEAK_SIGNATURE_OK))
+ {
+ Logger.PrintMessage("Weak signature verification error");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Close the archive
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ return nError;
+}
+
+static int TestCreateArchive_MpqEditor(const char * szPlainName, const char * szFileName)
+{
+ TLogHelper Logger("CreateMpqEditor", szPlainName);
+ HANDLE hMpq = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Create new MPQ
+ nError = CreateNewArchive_V2(&Logger, szPlainName, MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, 4000, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Flush the archive first
+ SFileFlushArchive(hMpq);
+
+ // Add one file
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, "This is the file data.", MPQ_FILE_COMPRESS);
+
+ // Flush the archive again
+ SFileFlushArchive(hMpq);
+ SFileCloseArchive(hMpq);
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ return nError;
+}
+
+static int TestCreateArchive_FillArchive(const char * szPlainName, DWORD dwCreateFlags)
+{
+ TLogHelper Logger("CreateFullMpq", szPlainName);
+ const char * szFileData = "TestCreateArchive_FillArchive: Testing file data";
+ char szFileName[MAX_PATH];
+ HANDLE hMpq = NULL;
+ DWORD dwMaxFileCount = 6;
+ DWORD dwCompression = MPQ_COMPRESSION_ZLIB;
+ DWORD dwFlags = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS;
+ int nError;
+
+ //
+ // Note that StormLib will round the maxfile count
+ // up to hash table size (nearest power of two)
+ //
+ if((dwCreateFlags & MPQ_CREATE_LISTFILE) == 0)
+ dwMaxFileCount++;
+ if((dwCreateFlags & MPQ_CREATE_ATTRIBUTES) == 0)
+ dwMaxFileCount++;
+
+ // Create the new MPQ archive
+ nError = CreateNewArchive_V2(&Logger, szPlainName, dwCreateFlags, dwMaxFileCount, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Flush the archive first
+ SFileFlushArchive(hMpq);
+
+ // Add all files
+ for(unsigned int i = 0; i < dwMaxFileCount; i++)
+ {
+ sprintf(szFileName, "AddedFile%03u.txt", i);
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression);
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+
+ // Flush the archive again
+ SFileFlushArchive(hMpq);
+ }
+
+ // Now the MPQ should be full. It must not be possible to add another file
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, ERROR_DISK_FULL);
+ assert(nError != ERROR_SUCCESS);
+ nError = ERROR_SUCCESS;
+ }
+
+ // Close the archive to enforce saving all tables
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+
+ // Reopen the archive again
+ if(nError == ERROR_SUCCESS)
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+
+ // The archive should still be full
+ if(nError == ERROR_SUCCESS)
+ {
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, (dwCreateFlags & MPQ_CREATE_LISTFILE) ? true : false);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? true : false);
+ nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, ERROR_DISK_FULL);
+ assert(nError != ERROR_SUCCESS);
+ nError = ERROR_SUCCESS;
+ }
+
+ // The (listfile) and (attributes) must be present
+ if(nError == ERROR_SUCCESS)
+ {
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, (dwCreateFlags & MPQ_CREATE_LISTFILE) ? true : false);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? true : false);
+ nError = RemoveMpqFile(&Logger, hMpq, szFileName, ERROR_SUCCESS);
+ }
+
+ // Now add the file again. This time, it should be possible OK
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression, ERROR_SUCCESS);
+ assert(nError == ERROR_SUCCESS);
+ }
+
+ // Now add the file again. This time, it should fail
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, dwFlags, dwCompression, ERROR_ALREADY_EXISTS);
+ assert(nError != ERROR_SUCCESS);
+ nError = ERROR_SUCCESS;
+ }
+
+ // Now add the file again. This time, it should fail
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = AddFileToMpq(&Logger, hMpq, "ShouldNotBeHere.txt", szFileData, dwFlags, dwCompression, ERROR_DISK_FULL);
+ assert(nError != ERROR_SUCCESS);
+ nError = ERROR_SUCCESS;
+ }
+
+ // Close the archive and return
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+
+ // Reopen the archive for the third time to verify that both internal files are there
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, (dwCreateFlags & MPQ_CREATE_LISTFILE) ? true : false);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? true : false);
+ SFileCloseArchive(hMpq);
+ }
+ }
+
+ return nError;
+}
+
+static int TestCreateArchive_IncMaxFileCount(const char * szPlainName)
+{
+ TLogHelper Logger("IncMaxFileCount", szPlainName);
+ const char * szFileData = "TestCreateArchive_IncMaxFileCount: Testing file data";
+ char szFileName[MAX_PATH];
+ HANDLE hMpq = NULL;
+ DWORD dwMaxFileCount = 1;
+ int nError;
+
+ // Create the new MPQ
+ nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, dwMaxFileCount, &hMpq);
+
+ // Now add exactly one file
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = AddFileToMpq(&Logger, hMpq, "AddFile_base.txt", szFileData);
+ SFileFlushArchive(hMpq);
+ SFileCloseArchive(hMpq);
+ }
+
+ // Now add 10 files. Each time we cannot add the file due to archive being full,
+ // we increment the max file count
+ if(nError == ERROR_SUCCESS)
+ {
+ for(unsigned int i = 0; i < 10; i++)
+ {
+ // Open the archive again
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ // Add one file
+ sprintf(szFileName, "AddFile_%04u.txt", i);
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, 0, 0, ERROR_UNDETERMINED_RESULT);
+ if(nError != ERROR_SUCCESS)
+ {
+ // Increment the max file count by one
+ dwMaxFileCount = SFileGetMaxFileCount(hMpq) + 1;
+ Logger.PrintProgress("Increasing max file count to %u ...", dwMaxFileCount);
+ SFileSetMaxFileCount(hMpq, dwMaxFileCount);
+
+ // Attempt to create the file again
+ nError = AddFileToMpq(&Logger, hMpq, szFileName, szFileData, 0, 0, ERROR_SUCCESS);
+ }
+
+ // Compact the archive and close it
+ SFileSetCompactCallback(hMpq, CompactCallback, &Logger);
+ SFileCompactArchive(hMpq, NULL, false);
+ SFileCloseArchive(hMpq);
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+ }
+
+ return nError;
+}
+
+static int TestCreateArchive_UnicodeNames()
+{
+ TLogHelper Logger("MpqUnicodeName");
+ DWORD dwCreateFlags = MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES;
+ int nError = ERROR_SUCCESS;
+
+ nError = CreateNewArchiveU(&Logger, szUnicodeName1, dwCreateFlags | MPQ_CREATE_ARCHIVE_V1, 15);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ nError = CreateNewArchiveU(&Logger, szUnicodeName2, dwCreateFlags | MPQ_CREATE_ARCHIVE_V2, 58);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ nError = CreateNewArchiveU(&Logger, szUnicodeName3, dwCreateFlags | MPQ_CREATE_ARCHIVE_V3, 15874);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ nError = CreateNewArchiveU(&Logger, szUnicodeName4, dwCreateFlags | MPQ_CREATE_ARCHIVE_V4, 87541);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ nError = CreateNewArchiveU(&Logger, szUnicodeName5, dwCreateFlags | MPQ_CREATE_ARCHIVE_V3, 87541);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ nError = CreateNewArchiveU(&Logger, szUnicodeName5, dwCreateFlags | MPQ_CREATE_ARCHIVE_V2, 87541);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ return nError;
+}
+
+static int TestCreateArchive_FileFlagTest(const char * szPlainName)
+{
+ TLogHelper Logger("FileFlagTest", szPlainName);
+ HANDLE hMpq = NULL; // Handle of created archive
+ char szFileName1[MAX_PATH];
+ char szFileName2[MAX_PATH];
+ char szFullPath[MAX_PATH];
+ const char * szMiddleFile = "FileTest_10.exe";
+ LCID LocaleIDs[] = {0x000, 0x405, 0x406, 0x407, 0xFFFF};
+ char szArchivedName[MAX_PATH];
+ DWORD dwMaxFileCount = 0;
+ DWORD dwFileCount = 0;
+ size_t i;
+ int nError;
+
+ // Create paths for local file to be added
+ CreateFullPathName(szFileName1, szMpqSubDir, "AddFile.exe");
+ CreateFullPathName(szFileName2, szMpqSubDir, "AddFile.bin");
+
+ // Create an empty file that will serve as holder for the MPQ
+ nError = CreateEmptyFile(&Logger, szPlainName, 0x100000, szFullPath);
+
+ // Create new MPQ archive over that file
+ if(nError == ERROR_SUCCESS)
+ nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V1 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, 17, &hMpq);
+
+ // Add the same file multiple times
+ if(nError == ERROR_SUCCESS)
+ {
+ dwMaxFileCount = SFileGetMaxFileCount(hMpq);
+ for(i = 0; AddFlags[i] != 0xFFFFFFFF; i++)
+ {
+ sprintf(szArchivedName, "FileTest_%02u.exe", (unsigned int)i);
+ nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName1, AddFlags[i], 0);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ dwFileCount++;
+ }
+ }
+
+ // Delete a file in the middle of the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ Logger.PrintProgress("Removing file %s ...", szMiddleFile);
+ nError = RemoveMpqFile(&Logger, hMpq, szMiddleFile, ERROR_SUCCESS);
+ dwFileCount--;
+ }
+
+ // Add one more file
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = AddLocalFileToMpq(&Logger, hMpq, "FileTest_xx.exe", szFileName1);
+ dwFileCount++;
+ }
+
+ // Try to decrement max file count. This must succeed
+ if(nError == ERROR_SUCCESS)
+ {
+ Logger.PrintProgress("Attempting to decrement max file count ...");
+ if(SFileSetMaxFileCount(hMpq, 5))
+ nError = Logger.PrintError("Max file count decremented, even if it should fail");
+ }
+
+ // Add ZeroSize.txt several times under a different locale
+ if(nError == ERROR_SUCCESS)
+ {
+ for(i = 0; LocaleIDs[i] != 0xFFFF; i++)
+ {
+ bool bMustSucceed = ((dwFileCount + 2) < dwMaxFileCount);
+
+ SFileSetLocale(LocaleIDs[i]);
+ nError = AddLocalFileToMpq(&Logger, hMpq, "ZeroSize_1.txt", szFileName2);
+ if(nError != ERROR_SUCCESS)
+ {
+ if(bMustSucceed == false)
+ nError = ERROR_SUCCESS;
+ break;
+ }
+
+ dwFileCount++;
+ }
+ }
+
+ // Add ZeroSize.txt again several times under a different locale
+ if(nError == ERROR_SUCCESS)
+ {
+ for(i = 0; LocaleIDs[i] != 0xFFFF; i++)
+ {
+ bool bMustSucceed = ((dwFileCount + 2) < dwMaxFileCount);
+
+ SFileSetLocale(LocaleIDs[i]);
+ nError = AddLocalFileToMpq(&Logger, hMpq, "ZeroSize_2.txt", szFileName2, 0, 0, bMustSucceed);
+ if(nError != ERROR_SUCCESS)
+ {
+ if(bMustSucceed == false)
+ nError = ERROR_SUCCESS;
+ break;
+ }
+
+ dwFileCount++;
+ }
+ }
+
+ // Verify how many files did we add to the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ if(dwFileCount + 2 != dwMaxFileCount)
+ {
+ Logger.PrintErrorVa("Number of files added to MPQ was unexpected (expected %u, added %u)", dwFileCount, dwMaxFileCount - 2);
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Test rename function
+ if(nError == ERROR_SUCCESS)
+ {
+ Logger.PrintProgress("Testing rename files ...");
+ SFileSetLocale(LANG_NEUTRAL);
+ if(!SFileRenameFile(hMpq, "FileTest_08.exe", "FileTest_08a.exe"))
+ nError = Logger.PrintError("Failed to rename the file");
+ }
+
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!SFileRenameFile(hMpq, "FileTest_08a.exe", "FileTest_08.exe"))
+ nError = Logger.PrintError("Failed to rename the file");
+ }
+
+ if(nError == ERROR_SUCCESS)
+ {
+ if(SFileRenameFile(hMpq, "FileTest_10.exe", "FileTest_10a.exe"))
+ {
+ Logger.PrintError("Rename test succeeded even if it shouldn't");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ if(nError == ERROR_SUCCESS)
+ {
+ if(SFileRenameFile(hMpq, "FileTest_10a.exe", "FileTest_10.exe"))
+ {
+ Logger.PrintError("Rename test succeeded even if it shouldn't");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ // Close the archive
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+
+ // Try to reopen the archive
+ if(nError == ERROR_SUCCESS)
+ nError = OpenExistingArchive(&Logger, szFullPath, 0, NULL);
+ return nError;
+}
+
+static int TestCreateArchive_WaveCompressionsTest(const char * szPlainName, const char * szWaveFile)
+{
+ TLogHelper Logger("CompressionsTest", szPlainName);
+ HANDLE hMpq = NULL; // Handle of created archive
+ char szFileName[MAX_PATH]; // Source file to be added
+ char szArchivedName[MAX_PATH];
+ DWORD dwCmprCount = sizeof(WaveCompressions) / sizeof(DWORD);
+ DWORD dwAddedFiles = 0;
+ DWORD dwFoundFiles = 0;
+ int nError;
+
+ // Create paths for local file to be added
+ CreateFullPathName(szFileName, szMpqSubDir, szWaveFile);
+
+ // Create new archive
+ nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V1 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, 0x40, &hMpq);
+
+ // Add the same file multiple times
+ if(nError == ERROR_SUCCESS)
+ {
+ Logger.UserTotal = dwCmprCount;
+ for(unsigned int i = 0; i < dwCmprCount; i++)
+ {
+ sprintf(szArchivedName, "WaveFile_%02u.wav", i + 1);
+ nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_SECTOR_CRC, WaveCompressions[i]);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ Logger.UserCount++;
+ dwAddedFiles++;
+ }
+
+ SFileCloseArchive(hMpq);
+ }
+
+ // Reopen the archive extract each WAVE file and try to play it
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ SearchArchive(&Logger, hMpq, TEST_FLAG_LOAD_FILES | TEST_FLAG_PLAY_WAVES, &dwFoundFiles, NULL);
+ SFileCloseArchive(hMpq);
+ }
+
+ // Check if the number of found files is the same like the number of added files
+ // DOn;t forget that there will be (listfile) and (attributes)
+ if(dwFoundFiles != (dwAddedFiles + 2))
+ {
+ Logger.PrintError("Number of found files does not match number of added files.");
+ nError = ERROR_FILE_CORRUPT;
+ }
+ }
+
+ return nError;
+}
+
+static int TestCreateArchive_ListFilePos(const char * szPlainName)
+{
+ TFileData * pFileData;
+ const char * szReaddedFile = "AddedFile_##.txt";
+ const char * szFileMask = "AddedFile_%02u.txt";
+ TLogHelper Logger("ListFilePos", szPlainName);
+ HANDLE hMpq = NULL; // Handle of created archive
+ char szArchivedName[MAX_PATH];
+ DWORD dwMaxFileCount = 0x0E;
+ DWORD dwFileCount = 0;
+ size_t i;
+ int nError;
+
+ // Create a new archive with the limit of 0x20 files
+ nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, dwMaxFileCount, &hMpq);
+
+ // Add maximum files files
+ if(nError == ERROR_SUCCESS)
+ {
+ for(i = 0; i < dwMaxFileCount; i++)
+ {
+ sprintf(szArchivedName, szFileMask, i);
+ nError = AddFileToMpq(&Logger, hMpq, szArchivedName, "This is a text data.", 0, 0, ERROR_SUCCESS);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ dwFileCount++;
+ }
+ }
+
+ // Delete few middle files
+ if(nError == ERROR_SUCCESS)
+ {
+ for(i = 0; i < (dwMaxFileCount / 2); i++)
+ {
+ sprintf(szArchivedName, szFileMask, i);
+ nError = RemoveMpqFile(&Logger, hMpq, szArchivedName, ERROR_SUCCESS);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ dwFileCount--;
+ }
+ }
+
+ // Close the archive
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+
+ // Reopen the archive to catch any asserts
+ if(nError == ERROR_SUCCESS)
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+
+ // Check that (listfile) is at the end
+ if(nError == ERROR_SUCCESS)
+ {
+ pFileData = LoadMpqFile(&Logger, hMpq, LISTFILE_NAME);
+ if(pFileData != NULL)
+ {
+ if(pFileData->dwBlockIndex < dwFileCount)
+ Logger.PrintMessage("Unexpected file index of %s", LISTFILE_NAME);
+ STORM_FREE(pFileData);
+ }
+
+ pFileData = LoadMpqFile(&Logger, hMpq, ATTRIBUTES_NAME);
+ if(pFileData != NULL)
+ {
+ if(pFileData->dwBlockIndex <= dwFileCount)
+ Logger.PrintMessage("Unexpected file index of %s", ATTRIBUTES_NAME);
+ STORM_FREE(pFileData);
+ }
+
+ // Add new file to the archive. It should be added to the last position
+ nError = AddFileToMpq(&Logger, hMpq, szReaddedFile, "This is a re-added file.", 0, 0, ERROR_SUCCESS);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Force update of the tables
+ SFileFlushArchive(hMpq);
+
+ // Load the file
+ pFileData = LoadMpqFile(&Logger, hMpq, szReaddedFile);
+ if(pFileData != NULL)
+ {
+ if(pFileData->dwBlockIndex != dwFileCount)
+ Logger.PrintMessage("Unexpected file index of %s", szReaddedFile);
+ STORM_FREE(pFileData);
+ }
+ }
+
+ SFileCloseArchive(hMpq);
+ }
+
+ return nError;
+}
+
+static int TestCreateArchive_BigArchive(const char * szPlainName)
+{
+ const char * szFileMask = "AddedFile_%02u.txt";
+ TLogHelper Logger("BigMpqTest");
+ HANDLE hMpq = NULL; // Handle of created archive
+ char szLocalFileName[MAX_PATH];
+ char szArchivedName[MAX_PATH];
+ DWORD dwMaxFileCount = 0x20;
+ DWORD dwAddedCount = 0;
+ size_t i;
+ int nError;
+
+ // Create a new archive with the limit of 0x20 files
+ nError = CreateNewArchive(&Logger, szPlainName, MPQ_CREATE_ARCHIVE_V3 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES, dwMaxFileCount, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Now add few really big files
+ CreateFullPathName(szLocalFileName, szMpqSubDir, "MPQ_1997_v1_Diablo1_DIABDAT.MPQ");
+ Logger.UserTotal = (dwMaxFileCount / 2);
+
+ for(i = 0; i < dwMaxFileCount / 2; i++)
+ {
+ sprintf(szArchivedName, szFileMask, i + 1);
+ nError = AddLocalFileToMpq(&Logger, hMpq, szArchivedName, szLocalFileName, 0, 0, true);
+ if(nError != ERROR_SUCCESS)
+ break;
+
+ Logger.UserCount++;
+ dwAddedCount++;
+ }
+ }
+
+ // Close the archive
+ if(hMpq != NULL)
+ SFileCloseArchive(hMpq);
+ hMpq = NULL;
+
+ // Reopen the archive to catch any asserts
+ if(nError == ERROR_SUCCESS)
+ nError = OpenExistingArchiveWithCopy(&Logger, NULL, szPlainName, &hMpq);
+
+ // Check that (listfile) is at the end
+ if(nError == ERROR_SUCCESS)
+ {
+ CheckIfFileIsPresent(&Logger, hMpq, LISTFILE_NAME, true);
+ CheckIfFileIsPresent(&Logger, hMpq, ATTRIBUTES_NAME, true);
+
+ SFileCloseArchive(hMpq);
+ }
+
+ return nError;
+}
+
+// "MPQ_2014_v4_Heroes_Replay.MPQ", "AddFile-replay.message.events"
+static int TestModifyArchive_ReplaceFile(const char * szMpqPlainName, const char * szFileName)
+{
+ TLogHelper Logger("ModifyTest");
+ HANDLE hMpq = NULL;
+ const char * szArchivedName;
+ char szLocalFileName[MAX_PATH];
+ size_t nOffset = 0;
+ int nError;
+
+ // Open an existing archive
+ nError = OpenExistingArchiveWithCopy(&Logger, szMpqPlainName, szMpqPlainName, &hMpq);
+
+ // Add the given file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Get the name of archived file
+ if(!_strnicmp(szFileName, "AddFile-", 8))
+ nOffset = 8;
+ szArchivedName = szFileName + nOffset;
+
+ // Create the local file name
+ CreateFullPathName(szLocalFileName, szMpqSubDir, szFileName);
+
+ // Add the file to MPQ
+ nError = AddLocalFileToMpq(&Logger, hMpq,
+ szArchivedName,
+ szLocalFileName,
+ MPQ_FILE_REPLACEEXISTING | MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT,
+ MPQ_COMPRESSION_ZLIB,
+ true);
+ }
+
+ // Reopen the MPQ and compact it
+ if(nError == ERROR_SUCCESS)
+ {
+ // Compact the archive
+ Logger.PrintProgress("Compacting archive %s ...", szMpqPlainName);
+ if(!SFileSetCompactCallback(hMpq, CompactCallback, &Logger))
+ nError = Logger.PrintError("Failed to compact archive %s", szMpqPlainName);
+
+ if(!SFileCompactArchive(hMpq, NULL, 0))
+ nError = GetLastError();
+
+ SFileCloseArchive(hMpq);
+ }
+
+ // Try to open the archive again
+ if(nError == ERROR_SUCCESS)
+ {
+ CreateFullPathName(szLocalFileName, NULL, szMpqPlainName);
+ nError = OpenExistingArchive(&Logger, szLocalFileName, 0, &hMpq);
+ if(nError == ERROR_SUCCESS)
+ SFileCloseArchive(hMpq);
+ }
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Comparing two directories, creating links
+
+#define LINK_COMPARE_BLOCK_SIZE 0x200
+
+static int CreateArchiveLinkFile(const char * szFullPath1, const char * szFullPath2, const char * szFileHash)
+{
+ TFileStream * pStream;
+ char szLinkData[MAX_PATH + 0x80];
+ char szLinkFile[MAX_PATH];
+ char szLinkPath[MAX_PATH];
+ int nLength;
+ int nError = ERROR_SUCCESS;
+
+ // Construct the link file name
+ CalculateRelativePath(szFullPath1, szFullPath2, szLinkPath);
+ sprintf(szLinkFile, "%s.link", szFullPath2);
+
+ // Format the content of the link file
+ nLength = sprintf(szLinkData, "LINK:%s\x0D\x0ASHA1:%s", szLinkPath, szFileHash);
+
+ // Create the link file
+ pStream = FileStream_CreateFileA(szLinkFile, 0);
+ if(pStream == NULL)
+ return GetLastError();
+
+ // Write the content of the link file
+ if(!FileStream_Write(pStream, NULL, szLinkData, (DWORD)nLength))
+ nError = GetLastError();
+
+ FileStream_Close(pStream);
+ return ERROR_SUCCESS;
+}
+
+static int ForEachFile_CreateArchiveLink(const char * szFullPath1, const char * szFullPath2)
+{
+ TLogHelper Logger("CreateMpqLink", GetShortPlainName(szFullPath2));
+ char szFileHash1[0x40];
+ char szFileHash2[0x40];
+ int nError;
+
+ // Prevent logger from witing any result messages
+ Logger.bDontPrintResult = true;
+
+ // Create SHA1 of both files
+ nError = CalculateFileSha1(&Logger, szFullPath1, szFileHash1);
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = CalculateFileSha1(&Logger, szFullPath2, szFileHash2);
+ if(nError == ERROR_SUCCESS)
+ {
+ // If the hashes are identical, we can create link
+ if(!strcmp(szFileHash1, szFileHash2))
+ {
+ nError = CreateArchiveLinkFile(szFullPath1, szFullPath2, szFileHash1);
+ if(nError == ERROR_SUCCESS)
+ {
+ Logger.PrintMessage("Created link to %s", szFullPath2);
+ }
+ }
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Main
+
+int main(int argc, char * argv[])
+{
+ int nError = ERROR_SUCCESS;
+
+#if defined(_MSC_VER) && defined(_DEBUG)
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif // defined(_MSC_VER) && defined(_DEBUG)
+
+ // Initialize storage and mix the random number generator
+ printf("==== Test Suite for StormLib version %s ====\n", STORMLIB_VERSION_STRING);
+ nError = InitializeMpqDirectory(argv, argc);
+
+ // Not a test, but rather a tool for creating links to duplicated files
+// if(nError == ERROR_SUCCESS)
+// nError = FindFilePairs(ForEachFile_CreateArchiveLink, "2004 - WoW\\06080", "2004 - WoW\\06299");
+/*
+ // Search all testing archives and verify their SHA1 hash
+ if(nError == ERROR_SUCCESS)
+ nError = FindFiles(ForEachFile_VerifyFileChecksum, szMpqSubDir);
+
+ // Test sparse compression
+ if(nError == ERROR_SUCCESS)
+ nError = TestSparseCompression();
+
+ // Test reading linear file without bitmap
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-original.MPQ", 0);
+
+ // Test reading linear file without bitmap (read only)
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-original.MPQ", STREAM_FLAG_READ_ONLY);
+
+ // Test reading linear file with bitmap
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("MPQ_2013_v4_alternate-complete.MPQ", STREAM_FLAG_USE_BITMAP);
+
+ // Test reading partial file
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("part-file://MPQ_2009_v2_WoW_patch.MPQ.part", 0);
+
+ // Test reading Block4K file
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("blk4-file://streaming/model.MPQ.0", STREAM_PROVIDER_BLOCK4);
+
+ // Test reading encrypted file
+ if(nError == ERROR_SUCCESS)
+ nError = TestFileStreamOperations("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE", STREAM_PROVIDER_MPQE);
+
+ // Open a stream, paired with local master. The mirror file is created new
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-created.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", false);
+
+ // Open a stream, paired with local master. Only part of the mirror exists
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-partial.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", true);
+
+ // Open a stream, paired with local master. Only part of the mirror exists
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("part-file://MPQ_2009_v1_patch-complete.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", true);
+
+ // Open a stream, paired with local master
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-created.MPQ", "MPQ_2013_v4_alternate-original.MPQ", false);
+
+ // Open a stream, paired with local master
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-incomplete.MPQ", "MPQ_2013_v4_alternate-incomplete.MPQ", true);
+
+ // Open a stream, paired with local master
+ if(nError == ERROR_SUCCESS)
+ nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-complete.MPQ", "MPQ_2013_v4_alternate-original.MPQ", true);
+
+ // Open a stream, paired with remote master (takes hell lot of time!)
+// if(nError == ERROR_SUCCESS)
+// nError = TestReadFile_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "http://www.zezula.net\\mpqs\\alternate.zip", false);
+
+ // Search in listfile
+ if(nError == ERROR_SUCCESS)
+ nError = TestSearchListFile("ListFile_Blizzard.txt");
+
+ // Test opening local file with SFileOpenFileEx
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenLocalFile("ListFile_Blizzard.txt");
+
+ // Test working with an archive that has no listfile
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenFile_OpenById("MPQ_1997_v1_Diablo1_DIABDAT.MPQ");
+
+ // Open an empty archive (found in WoW cache - it's just a header)
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2012_v2_EmptyMpq.MPQ");
+
+ // Open an empty archive (created artificially - it's just a header)
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2013_v4_EmptyMpq.MPQ");
+
+ // Open an empty archive (found in WoW cache - it's just a header)
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2013_v4_patch-base-16357.MPQ");
+
+ // Open an empty archive (A buggy MPQ with invalid HET entry count)
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2011_v4_InvalidHetEntryCount.MPQ");
+
+ // Open a truncated archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2002_v1_BlockTableCut.MPQ");
+
+ // Open a MPQ that actually has user data
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2010_v2_HasUserData.s2ma");
+
+ // Open an Warcraft III map whose "(attributes)" file has (BlockTableSize-1) entries
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2014_v1_AttributesOneEntryLess.w3x");
+
+ // Open a MPQ archive v 3.0
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2010_v3_expansion-locale-frFR.MPQ");
+
+ // Open an encrypted archive from Starcraft II installer
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("mpqe-file://MPQ_2011_v2_EncryptedMpq.MPQE");
+
+ // Open a MPK archive from Longwu online
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPx_2013_v1_LongwuOnline.mpk");
+
+ // Open a SQP archive from War of the Immortals
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPx_2013_v1_WarOfTheImmortals.sqp", "ListFile_WarOfTheImmortals.txt");
+
+ // Open a partial MPQ with compressed hash table
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("part-file://MPQ_2010_v2_HashTableCompressed.MPQ.part");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_HashTable_FakeValid.w3x");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_InvalidUserData.w3x");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_InvalidMpqFormat.w3x");
+
+ // Open an Warcraft III map locked by the Spazzler protector
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_Spazzler.w3x");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2014_v1_ProtectedMap_Spazzler2.w3x");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2014_v1_ProtectedMap_Spazzler3.w3x");
+
+ // Open an Warcraft III map locked by the BOBA protector
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2002_v1_ProtectedMap_BOBA.w3m");
+
+ // Open an Warcraft III map locked by a protector
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2015_v1_ProtectedMap_KangTooJee.w3x");
+
+ // Open an Warcraft III map locked by a protector
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2015_v1_ProtectedMap_Somj2hM16.w3x");
+
+ // Open an Warcraft III map locked by Spazy protector
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2015_v1_ProtectedMap_Spazy.w3x");
+
+ // Open an protected map
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("MPQ_2015_v1_flem1.w3x");
+
+ // Open the multi-file archive with wrong prefix to see how StormLib deals with it
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_WillFail("flat-file://streaming/model.MPQ.0");
+
+ // Open an archive that is merged with multiple files
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive("blk4-file://streaming/model.MPQ.0", NULL, true);
+
+ // Open every MPQ that we have in the storage
+ if(nError == ERROR_SUCCESS)
+ nError = FindFiles(ForEachFile_OpenArchive, NULL);
+
+ // Test on an archive that has been invalidated by extending an old valid MPQ
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Corrupt("MPQ_2013_vX_Battle.net.MPQ");
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_WoW_OldWorld13286, "OldWorld\\World\\Model.blob", 2);
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_WoW_15050, "World\\Model.blob", 8);
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_WoW_16965, "DBFilesClient\\BattlePetNPCTeamMember.db2", 0);
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_SC2_32283, "TriggerLibs\\natives.galaxy", 6);
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_SC2_34644, "TriggerLibs\\GameData\\GameData.galaxy", 2);
+
+ // Open a patched archive with new format of BSDIFF patch
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_SC2_34644_Maps, "Maps\\Campaign\\THorner03.SC2Map\\BankList.xml", 3);
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_SC2_32283_enGB, "LocalizedData\\GameHotkeys.txt", 6);
+
+ // Open a patched archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_Patched(PatchList_HS_6898_enGB, "Hearthstone_Data\\Managed\\Assembly-Csharp.dll", 10);
+
+ // Check the opening archive for read-only
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_ReadOnly("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", true);
+
+ // Check the opening archive for read-only
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_ReadOnly("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false);
+
+ // Check the SFileGetFileInfo function
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_GetFileInfo("MPQ_2002_v1_StrongSignature.w3m", "MPQ_2013_v4_SC2_EmptyMap.SC2Map");
+
+ // Downloadable MPQ archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_MasterMirror("part-file://MPQ_2009_v1_patch-partial.MPQ.part", "MPQ_2009_v1_patch-original.MPQ", "world\\Azeroth\\DEADMINES\\PASSIVEDOODADS\\GOBLINMELTINGPOT\\DUST2.BLP", false);
+
+ // Downloadable MPQ archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_MasterMirror("MPQ_2013_v4_alternate-downloaded.MPQ", "MPQ_2013_v4_alternate-original.MPQ", "alternate\\DUNGEONS\\TEXTURES\\ICECROWN\\GATE\\jlo_IceC_Floor_Thrown.blp", false);
+
+ // Check archive signature
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_VerifySignature("MPQ_1999_v1_WeakSignature.exe", "War2Patch_202.exe");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_VerifySignature("MPQ_2003_v1_WeakSignatureEmpty.exe", "WoW-1.2.3.4211-enUS-patch.exe");
+
+ // Check archive signature
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_VerifySignature("MPQ_2002_v1_StrongSignature.w3m", "(10)DustwallowKeys.w3m");
+
+ // Compact the archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_CraftedUserData("MPQ_2010_v3_expansion-locale-frFR.MPQ", "StormLibTest_CraftedMpq1_v3.mpq");
+
+ // Open a MPQ (add custom user data to it)
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_SC2_EmptyMap.SC2Map", "StormLibTest_CraftedMpq2_v4.mpq");
+
+ // Open a MPQ (add custom user data to it)
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_CraftedUserData("MPQ_2013_v4_expansion1.MPQ", "StormLibTest_CraftedMpq3_v4.mpq");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestAddFile_FullTable("MPQ_2014_v1_out1.w3x");
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestAddFile_FullTable("MPQ_2014_v1_out2.w3x");
+
+ // Test modifying file with no (listfile) and no (attributes)
+ if(nError == ERROR_SUCCESS)
+ nError = TestAddFile_ListFileTest("MPQ_1997_v1_Diablo1_DIABDAT.MPQ", false, false);
+
+ // Test modifying an archive that contains (listfile) and (attributes)
+ if(nError == ERROR_SUCCESS)
+ nError = TestAddFile_ListFileTest("MPQ_2013_v4_SC2_EmptyMap.SC2Map", true, true);
+
+ // Create an empty archive v2
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_EmptyMpq("StormLibTest_EmptyMpq_v2.mpq", MPQ_CREATE_ARCHIVE_V2 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES);
+
+ // Create an empty archive v4
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_EmptyMpq("StormLibTest_EmptyMpq_v4.mpq", MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_LISTFILE | MPQ_CREATE_ATTRIBUTES);
+
+ // Test creating of an archive the same way like MPQ Editor does
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_TestGaps("StormLibTest_GapsTest.mpq");
+
+ // Sign an existing non-signed archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_SignExisting("MPQ_1998_v1_StarDat.mpq");
+
+ // Open a signed archive, add a file and verify the signature
+ if(nError == ERROR_SUCCESS)
+ nError = TestOpenArchive_ModifySigned("MPQ_1999_v1_WeakSignature.exe", "War2Patch_202.exe");
+
+ // Create new archive and sign it
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_Signed("MPQ_1999_v1_WeakSigned1.mpq", true);
+
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_Signed("MPQ_1999_v1_WeakSigned2.mpq", false);
+
+ // Test creating of an archive the same way like MPQ Editor does
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_MpqEditor("StormLibTest_MpqEditorTest.mpq", "AddedFile.exe");
+
+ // Create an archive and fill it with files up to the max file count
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", 0);
+
+ // Create an archive and fill it with files up to the max file count
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", MPQ_CREATE_LISTFILE);
+
+ // Create an archive and fill it with files up to the max file count
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", MPQ_CREATE_ATTRIBUTES);
+
+ // Create an archive and fill it with files up to the max file count
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_FillArchive("StormLibTest_FileTableFull.mpq", MPQ_CREATE_ATTRIBUTES | MPQ_CREATE_LISTFILE);
+
+ // Create an archive, and increment max file count several times
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_IncMaxFileCount("StormLibTest_IncMaxFileCount.mpq");
+
+ // Create a MPQ archive with UNICODE names
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_UnicodeNames();
+
+ // Create a MPQ file, add files with various flags
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_FileFlagTest("StormLibTest_FileFlagTest.mpq");
+
+ // Create a MPQ file, add a mono-WAVE file with various compressions
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_WaveCompressionsTest("StormLibTest_AddWaveMonoTest.mpq", "AddFile-Mono.wav");
+
+ // Create a MPQ file, add a mono-WAVE with 8 bits per sample file with various compressions
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_WaveCompressionsTest("StormLibTest_AddWaveMonoBadTest.mpq", "AddFile-MonoBad.wav");
+
+ // Create a MPQ file, add a stereo-WAVE file with various compressions
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_WaveCompressionsTest("StormLibTest_AddWaveStereoTest.mpq", "AddFile-Stereo.wav");
+
+ // Check if the listfile is always created at the end of the file table in the archive
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_ListFilePos("StormLibTest_ListFilePos.mpq");
+
+ // Open a MPQ (add custom user data to it)
+ if(nError == ERROR_SUCCESS)
+ nError = TestCreateArchive_BigArchive("StormLibTest_BigArchive_v4.mpq");
+*/
+ // Test replacing a file with zero size file
+ if(nError == ERROR_SUCCESS)
+ nError = TestModifyArchive_ReplaceFile("MPQ_2014_v4_Base.StormReplay", "AddFile-replay.message.events");
+
+#ifdef _MSC_VER
+ _CrtDumpMemoryLeaks();
+#endif // _MSC_VER
+
+ return nError;
+}
diff --git a/test/TLogHelper.cpp b/test/TLogHelper.cpp
index 078de1d..d22b76d 100644
--- a/test/TLogHelper.cpp
+++ b/test/TLogHelper.cpp
@@ -1,421 +1,421 @@
-/*****************************************************************************/
-/* TLogHelper.cpp Copyright (c) Ladislav Zezula 2013 */
-/*---------------------------------------------------------------------------*/
-/* Helper class for reporting StormLib tests */
-/* This file should be included directly from StormTest.cpp using #include */
-/*---------------------------------------------------------------------------*/
-/* Date Ver Who Comment */
-/* -------- ---- --- ------- */
-/* 26.11.13 1.00 Lad The first version of TLogHelper.cpp */
-/*****************************************************************************/
-
-//-----------------------------------------------------------------------------
-// Definition of the TLogHelper class
-
-class TLogHelper
-{
- public:
-
- TLogHelper(const char * szNewMainTitle = NULL, const char * szNewSubTitle1 = NULL, const char * szNewSubTitle2 = NULL);
- ~TLogHelper();
-
-#if defined(UNICODE) || defined(UNICODE)
- // TCHAR-based functions. They are only needed on UNICODE builds.
- // On ANSI builds is TCHAR = char, so we don't need them at all
- int PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine);
- void PrintProgress(const TCHAR * szFormat, ...);
- int PrintErrorVa(const TCHAR * szFormat, ...);
- int PrintError(const TCHAR * szFormat, const TCHAR * szFileName = NULL);
-#endif // defined(UNICODE) || defined(UNICODE)
-
- // ANSI functions
- int PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine);
- void PrintProgress(const char * szFormat, ...);
- void PrintMessage(const char * szFormat, ...);
- int PrintErrorVa(const char * szFormat, ...);
- int PrintError(const char * szFormat, const char * szFileName = NULL);
-
- const char * UserString;
- unsigned int UserCount;
- unsigned int UserTotal;
- bool bDontPrintResult;
-
- protected:
-
-#if defined(UNICODE) || defined(UNICODE)
- TCHAR * CopyFormatCharacter(TCHAR * szBuffer, const TCHAR *& szFormat);
-#endif
- char * CopyFormatCharacter(char * szBuffer, const char *& szFormat);
- int GetConsoleWidth();
-
- const char * szMainTitle; // Title of the text (usually name)
- const char * szSubTitle1; // Title of the text (can be name of the tested file)
- const char * szSubTitle2; // Title of the text (can be name of the tested file)
- size_t nTextLength; // Length of the previous progress message
- bool bMessagePrinted;
-};
-
-//-----------------------------------------------------------------------------
-// String replacements for format strings
-
-#ifdef _MSC_VER
-#define I64u_t _T("%I64u")
-#define I64u_a "%I64u"
-#define I64X_t _T("%I64X")
-#define I64X_a "%I64X"
-#else
-#define I64u_t _T("%llu")
-#define I64u_a "%llu"
-#define I64X_t _T("%llX")
-#define I64X_a "%llX"
-#endif
-
-//-----------------------------------------------------------------------------
-// Constructor and destructor
-
-
-TLogHelper::TLogHelper(const char * szNewMainTitle, const char * szNewSubTitle1, const char * szNewSubTitle2)
-{
- UserString = "";
- UserCount = 1;
- UserTotal = 1;
-
- // Fill the test line structure
- szMainTitle = szNewMainTitle;
- szSubTitle1 = szNewSubTitle1;
- szSubTitle2 = szNewSubTitle2;
- nTextLength = 0;
- bMessagePrinted = false;
- bDontPrintResult = false;
-
- // Print the initial information
- if(szMainTitle != NULL)
- {
- if(szSubTitle1 != NULL && szSubTitle2 != NULL)
- printf("Running %s (%s+%s) ...", szMainTitle, szSubTitle1, szSubTitle2);
- else if(szSubTitle1 != NULL)
- printf("Running %s (%s) ...", szMainTitle, szSubTitle1);
- else
- printf("Running %s ...", szMainTitle);
- }
-}
-
-TLogHelper::~TLogHelper()
-{
- const char * szSaveMainTitle = szMainTitle;
- const char * szSaveSubTitle1 = szSubTitle1;
- const char * szSaveSubTitle2 = szSubTitle2;
-
- // Set both to NULL so the won't be printed
- szMainTitle = szSubTitle1 = szSubTitle2 = NULL;
-
- // Print the final information
- if(szSaveMainTitle != NULL && bMessagePrinted == false)
- {
- if(bDontPrintResult == false)
- {
- if(szSaveSubTitle1 != NULL && szSaveSubTitle2 != NULL)
- PrintMessage("%s (%s+%s) succeeded.", szSaveMainTitle, szSaveSubTitle1, szSaveSubTitle2);
- else if(szSaveSubTitle1 != NULL)
- PrintMessage("%s (%s) succeeded.", szSaveMainTitle, szSaveSubTitle1);
- else
- PrintMessage("%s succeeded.", szSaveMainTitle);
- }
- else
- {
- PrintProgress(" ");
- printf("\r");
- }
- }
-
-#if defined(_MSC_VER) && defined(_DEBUG)
- if(_CrtDumpMemoryLeaks())
- {
- PrintMessage("Memory leak detected after %s\n.", szSaveMainTitle);
- }
-#endif // _MSC_VER
-}
-
-//-----------------------------------------------------------------------------
-// TCHAR-based functions. They are only needed on UNICODE builds.
-// On ANSI builds is TCHAR = char, so we don't need them at all
-
-#if defined(UNICODE) || defined(UNICODE)
-int TLogHelper::PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine)
-{
- TCHAR szFormatBuff[0x200];
- TCHAR szMessage[0x200];
- TCHAR * szBuffer = szFormatBuff;
- int nRemainingWidth;
- int nConsoleWidth = GetConsoleWidth();
- int nLength = 0;
- int nError = GetLastError();
-
- // Always start the buffer with '\r'
- *szBuffer++ = '\r';
-
- // Print the prefix, if needed
- if(szMainTitle != NULL && bPrintPrefix)
- {
- while(szMainTitle[nLength] != 0)
- *szBuffer++ = szMainTitle[nLength++];
-
- *szBuffer++ = ':';
- *szBuffer++ = ' ';
- }
-
- // Copy the message format itself. Replace %s with "%s", unless it's (%s)
- if(szFormat != NULL)
- {
- szBuffer = CopyFormatCharacter(szBuffer, szFormat);
- szFormat += nLength;
- }
-
- // Append the last error
- if(bPrintLastError)
- {
- nLength = _stprintf(szBuffer, _T(" (error code: %u)"), nError);
- szBuffer += nLength;
- }
-
- // Create the result string
- szBuffer[0] = 0;
- nLength = _vstprintf(szMessage, szFormatBuff, argList);
- szBuffer = szMessage + nLength;
-
- // Shall we pad the string?
- if(nLength < nConsoleWidth)
- {
- // Calculate the remaining width
- nRemainingWidth = nConsoleWidth - nLength - 1;
-
- // Pad the string with spaces to fill it up to the end of the line
- for(int i = 0; i < nRemainingWidth; i++)
- *szBuffer++ = 0x20;
- }
-
- // Put the newline, if requested
- *szBuffer++ = bPrintEndOfLine ? '\n' : 0;
- *szBuffer = 0;
-
- // Remember if we printed a message
- if(bPrintEndOfLine)
- bMessagePrinted = true;
-
- // Spit out the text in one single printf
- _tprintf(_T("%s"), szMessage);
- return nError;
-}
-
-void TLogHelper::PrintProgress(const TCHAR * szFormat, ...)
-{
- va_list argList;
-
- va_start(argList, szFormat);
- PrintWithClreol(szFormat, argList, true, false, false);
- va_end(argList);
-}
-
-int TLogHelper::PrintErrorVa(const TCHAR * szFormat, ...)
-{
- va_list argList;
- int nResult;
-
- va_start(argList, szFormat);
- nResult = PrintWithClreol(szFormat, argList, true, true, true);
- va_end(argList);
-
- return nResult;
-}
-
-int TLogHelper::PrintError(const TCHAR * szFormat, const TCHAR * szFileName)
-{
- return PrintErrorVa(szFormat, szFileName);
-}
-#endif // defined(UNICODE) || defined(UNICODE)
-
-//-----------------------------------------------------------------------------
-// ANSI functions
-
-int TLogHelper::PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine)
-{
- char szFormatBuff[0x200];
- char szMessage[0x200];
- char * szBuffer = szFormatBuff;
- int nRemainingWidth;
- int nConsoleWidth = GetConsoleWidth();
- int nLength = 0;
- int nError = GetLastError();
-
- // Always start the buffer with '\r'
- *szBuffer++ = '\r';
-
- // Print the prefix, if needed
- if(szMainTitle != NULL && bPrintPrefix)
- {
- while(szMainTitle[nLength] != 0)
- *szBuffer++ = szMainTitle[nLength++];
-
- *szBuffer++ = ':';
- *szBuffer++ = ' ';
- }
-
- // Copy the message format itself. Replace %s with "%s", unless it's (%s)
- if(szFormat != NULL)
- {
- while(szFormat[0] != 0)
- {
- szBuffer = CopyFormatCharacter(szBuffer, szFormat);
- }
- }
-
- // Append the last error
- if(bPrintLastError)
- {
- nLength = sprintf(szBuffer, " (error code: %u)", nError);
- szBuffer += nLength;
- }
-
- // Create the result string
- szBuffer[0] = 0;
- nLength = vsprintf(szMessage, szFormatBuff, argList);
-
- // Shall we pad the string?
- szBuffer = szMessage + nLength;
- if(nLength < nConsoleWidth)
- {
- // Calculate the remaining width
- nRemainingWidth = nConsoleWidth - nLength - 1;
-
- // Pad the string with spaces to fill it up to the end of the line
- for(int i = 0; i < nRemainingWidth; i++)
- *szBuffer++ = 0x20;
- }
-
- // Put the newline, if requested
- *szBuffer++ = bPrintEndOfLine ? '\n' : '\r';
- *szBuffer = 0;
-
- // Remember if we printed a message
- if(bPrintEndOfLine)
- bMessagePrinted = true;
-
- // Spit out the text in one single printf
- printf("%s", szMessage);
- return nError;
-}
-
-void TLogHelper::PrintProgress(const char * szFormat, ...)
-{
- va_list argList;
-
- va_start(argList, szFormat);
- PrintWithClreol(szFormat, argList, true, false, false);
- va_end(argList);
-}
-
-void TLogHelper::PrintMessage(const char * szFormat, ...)
-{
- va_list argList;
-
- va_start(argList, szFormat);
- PrintWithClreol(szFormat, argList, true, false, true);
- va_end(argList);
-}
-
-int TLogHelper::PrintErrorVa(const char * szFormat, ...)
-{
- va_list argList;
- int nResult;
-
- va_start(argList, szFormat);
- nResult = PrintWithClreol(szFormat, argList, true, true, true);
- va_end(argList);
-
- return nResult;
-}
-
-int TLogHelper::PrintError(const char * szFormat, const char * szFileName)
-{
- return PrintErrorVa(szFormat, szFileName);
-}
-
-//-----------------------------------------------------------------------------
-// Protected functions
-
-#ifdef _UNICODE
-TCHAR * TLogHelper::CopyFormatCharacter(TCHAR * szBuffer, const TCHAR *& szFormat)
-{
- static const TCHAR * szStringFormat = _T("\"%s\"");
- static const TCHAR * szUint64Format = I64u_t;
-
- // String format
- if(szFormat[0] == '%')
- {
- if(szFormat[1] == 's' && szFormat[2] != ')')
- {
- _tcscpy(szBuffer, szStringFormat);
- szFormat += 2;
- return szBuffer + _tcslen(szStringFormat);
- }
-
- // Replace %I64u with the proper platform-dependent suffix
- if(szFormat[1] == 'I' && szFormat[2] == '6' && szFormat[3] == '4' && szFormat[4] == 'u')
- {
- _tcscpy(szBuffer, szUint64Format);
- szFormat += 5;
- return szBuffer + _tcslen(szUint64Format);
- }
- }
-
- // Copy the character as-is
- *szBuffer++ = *szFormat++;
- return szBuffer;
-}
-#endif
-
-char * TLogHelper::CopyFormatCharacter(char * szBuffer, const char *& szFormat)
-{
- static const char * szStringFormat = "\"%s\"";
- static const char * szUint64Format = I64u_a;
-
- // String format
- if(szFormat[0] == '%')
- {
- if(szFormat[1] == 's' && szFormat[2] != ')')
- {
- strcpy(szBuffer, szStringFormat);
- szFormat += 2;
- return szBuffer + strlen(szStringFormat);
- }
-
- // Replace %I64u with the proper platform-dependent suffix
- if(szFormat[1] == 'I' && szFormat[2] == '6' && szFormat[3] == '4' && szFormat[4] == 'u')
- {
- strcpy(szBuffer, szUint64Format);
- szFormat += 5;
- return szBuffer + strlen(szUint64Format);
- }
- }
-
- // Copy the character as-is
- *szBuffer++ = *szFormat++;
- return szBuffer;
-}
-
-int TLogHelper::GetConsoleWidth()
-{
-#ifdef PLATFORM_WINDOWS
-
- CONSOLE_SCREEN_BUFFER_INFO ScreenInfo;
- GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ScreenInfo);
- return (int)(ScreenInfo.srWindow.Right - ScreenInfo.srWindow.Left);
-
-#else
-
- // On non-Windows platforms, we assume that width of the console line
- // is 80 characters
- return 120;
-
-#endif
-}
+/*****************************************************************************/
+/* TLogHelper.cpp Copyright (c) Ladislav Zezula 2013 */
+/*---------------------------------------------------------------------------*/
+/* Helper class for reporting StormLib tests */
+/* This file should be included directly from StormTest.cpp using #include */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 26.11.13 1.00 Lad The first version of TLogHelper.cpp */
+/*****************************************************************************/
+
+//-----------------------------------------------------------------------------
+// Definition of the TLogHelper class
+
+class TLogHelper
+{
+ public:
+
+ TLogHelper(const char * szNewMainTitle = NULL, const char * szNewSubTitle1 = NULL, const char * szNewSubTitle2 = NULL);
+ ~TLogHelper();
+
+#if defined(UNICODE) || defined(UNICODE)
+ // TCHAR-based functions. They are only needed on UNICODE builds.
+ // On ANSI builds is TCHAR = char, so we don't need them at all
+ int PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine);
+ void PrintProgress(const TCHAR * szFormat, ...);
+ int PrintErrorVa(const TCHAR * szFormat, ...);
+ int PrintError(const TCHAR * szFormat, const TCHAR * szFileName = NULL);
+#endif // defined(UNICODE) || defined(UNICODE)
+
+ // ANSI functions
+ int PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine);
+ void PrintProgress(const char * szFormat, ...);
+ void PrintMessage(const char * szFormat, ...);
+ int PrintErrorVa(const char * szFormat, ...);
+ int PrintError(const char * szFormat, const char * szFileName = NULL);
+
+ const char * UserString;
+ unsigned int UserCount;
+ unsigned int UserTotal;
+ bool bDontPrintResult;
+
+ protected:
+
+#if defined(UNICODE) || defined(UNICODE)
+ TCHAR * CopyFormatCharacter(TCHAR * szBuffer, const TCHAR *& szFormat);
+#endif
+ char * CopyFormatCharacter(char * szBuffer, const char *& szFormat);
+ int GetConsoleWidth();
+
+ const char * szMainTitle; // Title of the text (usually name)
+ const char * szSubTitle1; // Title of the text (can be name of the tested file)
+ const char * szSubTitle2; // Title of the text (can be name of the tested file)
+ size_t nTextLength; // Length of the previous progress message
+ bool bMessagePrinted;
+};
+
+//-----------------------------------------------------------------------------
+// String replacements for format strings
+
+#ifdef _MSC_VER
+#define I64u_t _T("%I64u")
+#define I64u_a "%I64u"
+#define I64X_t _T("%I64X")
+#define I64X_a "%I64X"
+#else
+#define I64u_t _T("%llu")
+#define I64u_a "%llu"
+#define I64X_t _T("%llX")
+#define I64X_a "%llX"
+#endif
+
+//-----------------------------------------------------------------------------
+// Constructor and destructor
+
+
+TLogHelper::TLogHelper(const char * szNewMainTitle, const char * szNewSubTitle1, const char * szNewSubTitle2)
+{
+ UserString = "";
+ UserCount = 1;
+ UserTotal = 1;
+
+ // Fill the test line structure
+ szMainTitle = szNewMainTitle;
+ szSubTitle1 = szNewSubTitle1;
+ szSubTitle2 = szNewSubTitle2;
+ nTextLength = 0;
+ bMessagePrinted = false;
+ bDontPrintResult = false;
+
+ // Print the initial information
+ if(szMainTitle != NULL)
+ {
+ if(szSubTitle1 != NULL && szSubTitle2 != NULL)
+ printf("Running %s (%s+%s) ...", szMainTitle, szSubTitle1, szSubTitle2);
+ else if(szSubTitle1 != NULL)
+ printf("Running %s (%s) ...", szMainTitle, szSubTitle1);
+ else
+ printf("Running %s ...", szMainTitle);
+ }
+}
+
+TLogHelper::~TLogHelper()
+{
+ const char * szSaveMainTitle = szMainTitle;
+ const char * szSaveSubTitle1 = szSubTitle1;
+ const char * szSaveSubTitle2 = szSubTitle2;
+
+ // Set both to NULL so the won't be printed
+ szMainTitle = szSubTitle1 = szSubTitle2 = NULL;
+
+ // Print the final information
+ if(szSaveMainTitle != NULL && bMessagePrinted == false)
+ {
+ if(bDontPrintResult == false)
+ {
+ if(szSaveSubTitle1 != NULL && szSaveSubTitle2 != NULL)
+ PrintMessage("%s (%s+%s) succeeded.", szSaveMainTitle, szSaveSubTitle1, szSaveSubTitle2);
+ else if(szSaveSubTitle1 != NULL)
+ PrintMessage("%s (%s) succeeded.", szSaveMainTitle, szSaveSubTitle1);
+ else
+ PrintMessage("%s succeeded.", szSaveMainTitle);
+ }
+ else
+ {
+ PrintProgress(" ");
+ printf("\r");
+ }
+ }
+
+#if defined(_MSC_VER) && defined(_DEBUG)
+ if(_CrtDumpMemoryLeaks())
+ {
+ PrintMessage("Memory leak detected after %s\n.", szSaveMainTitle);
+ }
+#endif // _MSC_VER
+}
+
+//-----------------------------------------------------------------------------
+// TCHAR-based functions. They are only needed on UNICODE builds.
+// On ANSI builds is TCHAR = char, so we don't need them at all
+
+#if defined(UNICODE) || defined(UNICODE)
+int TLogHelper::PrintWithClreol(const TCHAR * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine)
+{
+ TCHAR szFormatBuff[0x200];
+ TCHAR szMessage[0x200];
+ TCHAR * szBuffer = szFormatBuff;
+ int nRemainingWidth;
+ int nConsoleWidth = GetConsoleWidth();
+ int nLength = 0;
+ int nError = GetLastError();
+
+ // Always start the buffer with '\r'
+ *szBuffer++ = '\r';
+
+ // Print the prefix, if needed
+ if(szMainTitle != NULL && bPrintPrefix)
+ {
+ while(szMainTitle[nLength] != 0)
+ *szBuffer++ = szMainTitle[nLength++];
+
+ *szBuffer++ = ':';
+ *szBuffer++ = ' ';
+ }
+
+ // Copy the message format itself. Replace %s with "%s", unless it's (%s)
+ if(szFormat != NULL)
+ {
+ szBuffer = CopyFormatCharacter(szBuffer, szFormat);
+ szFormat += nLength;
+ }
+
+ // Append the last error
+ if(bPrintLastError)
+ {
+ nLength = _stprintf(szBuffer, _T(" (error code: %u)"), nError);
+ szBuffer += nLength;
+ }
+
+ // Create the result string
+ szBuffer[0] = 0;
+ nLength = _vstprintf(szMessage, szFormatBuff, argList);
+ szBuffer = szMessage + nLength;
+
+ // Shall we pad the string?
+ if(nLength < nConsoleWidth)
+ {
+ // Calculate the remaining width
+ nRemainingWidth = nConsoleWidth - nLength - 1;
+
+ // Pad the string with spaces to fill it up to the end of the line
+ for(int i = 0; i < nRemainingWidth; i++)
+ *szBuffer++ = 0x20;
+ }
+
+ // Put the newline, if requested
+ *szBuffer++ = bPrintEndOfLine ? '\n' : 0;
+ *szBuffer = 0;
+
+ // Remember if we printed a message
+ if(bPrintEndOfLine)
+ bMessagePrinted = true;
+
+ // Spit out the text in one single printf
+ _tprintf(_T("%s"), szMessage);
+ return nError;
+}
+
+void TLogHelper::PrintProgress(const TCHAR * szFormat, ...)
+{
+ va_list argList;
+
+ va_start(argList, szFormat);
+ PrintWithClreol(szFormat, argList, true, false, false);
+ va_end(argList);
+}
+
+int TLogHelper::PrintErrorVa(const TCHAR * szFormat, ...)
+{
+ va_list argList;
+ int nResult;
+
+ va_start(argList, szFormat);
+ nResult = PrintWithClreol(szFormat, argList, true, true, true);
+ va_end(argList);
+
+ return nResult;
+}
+
+int TLogHelper::PrintError(const TCHAR * szFormat, const TCHAR * szFileName)
+{
+ return PrintErrorVa(szFormat, szFileName);
+}
+#endif // defined(UNICODE) || defined(UNICODE)
+
+//-----------------------------------------------------------------------------
+// ANSI functions
+
+int TLogHelper::PrintWithClreol(const char * szFormat, va_list argList, bool bPrintPrefix, bool bPrintLastError, bool bPrintEndOfLine)
+{
+ char szFormatBuff[0x200];
+ char szMessage[0x200];
+ char * szBuffer = szFormatBuff;
+ int nRemainingWidth;
+ int nConsoleWidth = GetConsoleWidth();
+ int nLength = 0;
+ int nError = GetLastError();
+
+ // Always start the buffer with '\r'
+ *szBuffer++ = '\r';
+
+ // Print the prefix, if needed
+ if(szMainTitle != NULL && bPrintPrefix)
+ {
+ while(szMainTitle[nLength] != 0)
+ *szBuffer++ = szMainTitle[nLength++];
+
+ *szBuffer++ = ':';
+ *szBuffer++ = ' ';
+ }
+
+ // Copy the message format itself. Replace %s with "%s", unless it's (%s)
+ if(szFormat != NULL)
+ {
+ while(szFormat[0] != 0)
+ {
+ szBuffer = CopyFormatCharacter(szBuffer, szFormat);
+ }
+ }
+
+ // Append the last error
+ if(bPrintLastError)
+ {
+ nLength = sprintf(szBuffer, " (error code: %u)", nError);
+ szBuffer += nLength;
+ }
+
+ // Create the result string
+ szBuffer[0] = 0;
+ nLength = vsprintf(szMessage, szFormatBuff, argList);
+
+ // Shall we pad the string?
+ szBuffer = szMessage + nLength;
+ if(nLength < nConsoleWidth)
+ {
+ // Calculate the remaining width
+ nRemainingWidth = nConsoleWidth - nLength - 1;
+
+ // Pad the string with spaces to fill it up to the end of the line
+ for(int i = 0; i < nRemainingWidth; i++)
+ *szBuffer++ = 0x20;
+ }
+
+ // Put the newline, if requested
+ *szBuffer++ = bPrintEndOfLine ? '\n' : '\r';
+ *szBuffer = 0;
+
+ // Remember if we printed a message
+ if(bPrintEndOfLine)
+ bMessagePrinted = true;
+
+ // Spit out the text in one single printf
+ printf("%s", szMessage);
+ return nError;
+}
+
+void TLogHelper::PrintProgress(const char * szFormat, ...)
+{
+ va_list argList;
+
+ va_start(argList, szFormat);
+ PrintWithClreol(szFormat, argList, true, false, false);
+ va_end(argList);
+}
+
+void TLogHelper::PrintMessage(const char * szFormat, ...)
+{
+ va_list argList;
+
+ va_start(argList, szFormat);
+ PrintWithClreol(szFormat, argList, true, false, true);
+ va_end(argList);
+}
+
+int TLogHelper::PrintErrorVa(const char * szFormat, ...)
+{
+ va_list argList;
+ int nResult;
+
+ va_start(argList, szFormat);
+ nResult = PrintWithClreol(szFormat, argList, true, true, true);
+ va_end(argList);
+
+ return nResult;
+}
+
+int TLogHelper::PrintError(const char * szFormat, const char * szFileName)
+{
+ return PrintErrorVa(szFormat, szFileName);
+}
+
+//-----------------------------------------------------------------------------
+// Protected functions
+
+#ifdef _UNICODE
+TCHAR * TLogHelper::CopyFormatCharacter(TCHAR * szBuffer, const TCHAR *& szFormat)
+{
+ static const TCHAR * szStringFormat = _T("\"%s\"");
+ static const TCHAR * szUint64Format = I64u_t;
+
+ // String format
+ if(szFormat[0] == '%')
+ {
+ if(szFormat[1] == 's' && szFormat[2] != ')')
+ {
+ _tcscpy(szBuffer, szStringFormat);
+ szFormat += 2;
+ return szBuffer + _tcslen(szStringFormat);
+ }
+
+ // Replace %I64u with the proper platform-dependent suffix
+ if(szFormat[1] == 'I' && szFormat[2] == '6' && szFormat[3] == '4' && szFormat[4] == 'u')
+ {
+ _tcscpy(szBuffer, szUint64Format);
+ szFormat += 5;
+ return szBuffer + _tcslen(szUint64Format);
+ }
+ }
+
+ // Copy the character as-is
+ *szBuffer++ = *szFormat++;
+ return szBuffer;
+}
+#endif
+
+char * TLogHelper::CopyFormatCharacter(char * szBuffer, const char *& szFormat)
+{
+ static const char * szStringFormat = "\"%s\"";
+ static const char * szUint64Format = I64u_a;
+
+ // String format
+ if(szFormat[0] == '%')
+ {
+ if(szFormat[1] == 's' && szFormat[2] != ')')
+ {
+ strcpy(szBuffer, szStringFormat);
+ szFormat += 2;
+ return szBuffer + strlen(szStringFormat);
+ }
+
+ // Replace %I64u with the proper platform-dependent suffix
+ if(szFormat[1] == 'I' && szFormat[2] == '6' && szFormat[3] == '4' && szFormat[4] == 'u')
+ {
+ strcpy(szBuffer, szUint64Format);
+ szFormat += 5;
+ return szBuffer + strlen(szUint64Format);
+ }
+ }
+
+ // Copy the character as-is
+ *szBuffer++ = *szFormat++;
+ return szBuffer;
+}
+
+int TLogHelper::GetConsoleWidth()
+{
+#ifdef PLATFORM_WINDOWS
+
+ CONSOLE_SCREEN_BUFFER_INFO ScreenInfo;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ScreenInfo);
+ return (int)(ScreenInfo.srWindow.Right - ScreenInfo.srWindow.Left);
+
+#else
+
+ // On non-Windows platforms, we assume that width of the console line
+ // is 80 characters
+ return 120;
+
+#endif
+}