diff --git a/dep/Changes.log b/dep/Changes.log
new file mode 100644
index 000000000..470c68987
--- /dev/null
+++ b/dep/Changes.log
@@ -0,0 +1,58 @@
+June 16, 2019
+ - revert some ACE changes which badly affected threads in FreeBSD
+
+May 27, 2019
+ - fixed TomLib::Crypt build on VS
+ - fixed Detour/Recast CMake configuration
+ - assuring STATIC generation of libraries for ZLIB::ZLIB, BZIP2::BZIP2 and mpqlib
+
+May 17,2019
+ - removed useless files from ACE
+
+May 15, 2019
+ - added PCH support for ACE
+
+May 10th, 2019
+ - cleaned up libmpq
+ - bzip2 version raised to 1.0.6
+ - zlib version raised to 1.2.11
+ - root CMakeLists.txt logic improved
+ - revert some FreBSD patches
+
+May 3rd, 2019
+ - added missing sources to ACE CMakeLists.txt
+ - fixed ACE_SIZE_T_FORMAT_SPECIFIER for FreeBSD
+ - applied FreeBSD 12+ patches from ports collection to ACE
+
+May 2nd, 2019
+ - fixed a typo which prevented ACE to be built as static lib
+ - added _WANT_SEMUN compiler definition for ACE when build on FreeBSD 12+
+ - removed __fastcall attribute from G3D sources
+ - removed redundant zlib and bzip2 sources from stormlib
+ - added TomCrypt and TomMath as static libs for future removal of OpenSSL
+ - changed CmakeLists.txt files to proper show header files on IDEs
+ - removed mersenne source code
+
+January 16th, 2019
+ - ACE is built as static library by default.
+ - Fixed ACE_HAS_CPP11/14 defines for compatibility with other compilers.
+ - added support for FreeBSD on stormlib
+
+March 7th, 2018
+ - To build ACE as static library, one has to add the following line to the global CMakeLists.txt
+ 'add_definitions(-DACE_AS_STATIC_LIBS)'
+
+2017 and older
+ - The default files for ACE_LITE were amended in commit: https://github.com/mangos/mangosDeps/commit/52e4c984dbca474f7a4353b444339067a95ea277
+ - The same changes need to be reapplied if a new version is added.
+ - The following two lines were amended to fix a connection bottleneck (especially on Linux):
+ ACE_DEFAULT_BACKLOG - Change 5 to 128 https://github.com/mangos/mangosDeps/blob/Rel21/acelite/ace/Default_Constants.h#L81
+ ACE_DEFAULT_ASYNCH_BACKLOG - Change 5 to 128 https://github.com/mangos/mangosDeps/blob/Rel21/acelite/ace/Default_Constants.h#L85
+ - For MacOS X, the ACE_UINT64_TYPE should be defined as follows in `ace/config-macosx-leopard.h`
+ #define ACE_UINT64_TYPE unsigned long long
+ - To get Travis CI working with `macos10.12` and `xcode8.3`, the following changes should be made to `ace/config-macosx-yosemite.h`
+ #include "ace/config-macosx-mavericks.h"
+ #undef ACE_LACKS_CLOCKID_T
+ #undef ACE_LACKS_CLOCK_MONOTONIC
+ #undef ACE_LACKS_CLOCK_REALTIME
+
diff --git a/dep/README.md b/dep/README.md
new file mode 100644
index 000000000..677aed632
--- /dev/null
+++ b/dep/README.md
@@ -0,0 +1,65 @@
+
+
+Mangos Dependencies
+------------
+*Mangos* stands on the shoulders of well-known Open Source
+libraries, and a few awesome, but less known libraries to prevent us from
+inventing the wheel again.
+
+*Please note that Linux and Mac OS X users should install packages using
+their systems package management instead of source packages.*
+
+* **MySQL** / **PostgreSQL**: to store content, and user data, we rely on
+ [MySQL][1]/[MariaDB][2] and [PostgreSQL][3] to handle data.
+* **ACE**: the [ADAPTIVE Communication Environment][4] aka. *ACE* provides us
+ with a solid cross-platform framework for abstracting operating system
+ specific details.
+* **Recast**: in order to create navigation data from the client's map files,
+ we use [Recast][5] to do the dirty work. It provides functions for
+ rendering, pathing, etc.
+* **G3D**: the [G3D][6] engine provides the basic framework for handling 3D
+ data, and is used to handle basic map data.
+* **libmpq**: [libmpq][7] provides an abstraction layer for reading from the
+ client's data files.
+* **Zlib**: [Zlib][12] ([Zlib for Windows][10]) provides compression algorithms
+ used in both MPQ archive handling and the client/server protocol.
+* **Bzip2**: [Bzip2][13] ([Bzip2 for Windows][11]) provides compression
+ algorithms used in MPQ archives.
+* **OpenSSL**: [OpenSSL][8] ([OpenSSL for Windows][14]) provides encryption
+ algorithms used when authenticating clients.
+* **Lua**: [Lua 5.2][15] ([Lua 5.2 for Windows][16]) provides a convenient, fast
+ scripting environment, which allows us to make live changes to scripted
+ content.
+
+*Recast*, *G3D* and *libmpq* are included in the *Mangos* distribution as
+we rely on specific versions. *libmpq* is to be replaced with *stormlib* shortly.
+
+Optional dependencies
+---------------------
+
+* **Doxygen**: if you want to export HTML or PDF formatted documentation for the
+ *Mangos* API, you should install [Doxygen][9].
+
+[1]: http://www.mysql.com/ "MySQL · The world's most popular open source database"
+[2]: http://www.mariadb.org/ "MariaDB · An enhanced, drop-in replacement for MySQL"
+[3]: http://www.postgresql.org/ "PostgreSQL · The world's most advanced open source database"
+[4]: http://www.cs.wustl.edu/~schmidt/ACE.html "ACE · The ADAPTIVE Communication Environment"
+[5]: http://github.com/memononen/recastnavigation "Recast · Navigation-mesh Toolset for Games"
+[6]: http://sourceforge.net/projects/g3d/ "G3D · G3D Innovation Engine"
+[7]: http://github.com/ge0rg/libmpq "libmpq · A library for reading data from MPQ archives"
+[8]: http://www.openssl.org/ "OpenSSL · The Open Source toolkit for SSL/TLS"
+[9]: http://www.stack.nl/~dimitri/doxygen/ "Doxygen · API documentation generator"
+[10]: http://gnuwin32.sourceforge.net/packages/zlib.htm "Zlib for Windows"
+[11]: http://gnuwin32.sourceforge.net/packages/bzip2.htm "Bzip2 for Windows"
+[12]: http://www.zlib.net/ "Zlib"
+[13]: http://www.bzip.org/ "Bzip2"
+[14]: http://slproweb.com/products/Win32OpenSSL.html "OpenSSL for Windows"
+[15]: http://www.lua.org/ "Lua"
+[16]: https://code.google.com/p/luaforwindows/ "Lua for Windows"
diff --git a/dep/StormLib/CMakeLists.txt b/dep/StormLib/CMakeLists.txt
new file mode 100644
index 000000000..3c3b3c818
--- /dev/null
+++ b/dep/StormLib/CMakeLists.txt
@@ -0,0 +1,54 @@
+project(StormLib VERSION 9.22.0)
+
+add_library(stormlib STATIC
+ src/adpcm/adpcm.cpp
+ src/huffman/huff.cpp
+ src/jenkins/lookup3.c
+ src/lzma/C/LzFind.c
+ src/lzma/C/LzmaDec.c
+ src/lzma/C/LzmaEnc.c
+ src/pklib/explode.c
+ src/pklib/implode.c
+ src/sparse/sparse.cpp
+ src/rsa/rsa_verify_simple.c
+ src/FileStream.cpp
+ src/SBaseCommon.cpp
+ src/SBaseDumpData.cpp
+ src/SBaseFileTable.cpp
+ src/SBaseSubTypes.cpp
+ src/SCompression.cpp
+ src/SFileAddFile.cpp
+ src/SFileAttributes.cpp
+ src/SFileCompactArchive.cpp
+ src/SFileCreateArchive.cpp
+ src/SFileExtractFile.cpp
+ src/SFileFindFile.cpp
+ src/SFileGetFileInfo.cpp
+ src/SFileListFile.cpp
+ src/SFileOpenArchive.cpp
+ src/SFileOpenFileEx.cpp
+ src/SFilePatchArchives.cpp
+ src/SFileReadFile.cpp
+ src/SFileVerify.cpp
+)
+
+target_include_directories(stormlib
+ PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
+
+target_compile_definitions(stormlib
+ PUBLIC
+ _7ZIP_ST
+ __SYS_BZLIB
+ __SYS_ZLIB
+ LTM_DESC
+ INTERFACE
+ STORMLIB_NO_AUTO_LINK
+)
+
+target_link_libraries(stormlib
+ PRIVATE
+ ZLIB::ZLIB
+ BZip2::BZip2
+ TomLib::Crypt
+ TomLib::Math
+)
\ No newline at end of file
diff --git a/dep/StormLib/LICENSE b/dep/StormLib/LICENSE
new file mode 100644
index 000000000..136cae4f4
--- /dev/null
+++ b/dep/StormLib/LICENSE
@@ -0,0 +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.
diff --git a/dep/StormLib/README b/dep/StormLib/README
new file mode 100644
index 000000000..d5d803ec7
--- /dev/null
+++ b/dep/StormLib/README
@@ -0,0 +1 @@
+This is official repository for the StomLib library, an open-source project that can work with Blizzard MPQ archives.
\ No newline at end of file
diff --git a/dep/StormLib/src/FileStream.cpp b/dep/StormLib/src/FileStream.cpp
new file mode 100644
index 000000000..55324ecb3
--- /dev/null
+++ b/dep/StormLib/src/FileStream.cpp
@@ -0,0 +1,2841 @@
+/*****************************************************************************/
+/* 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 DWORD nLastError = ERROR_SUCCESS;
+
+DWORD GetLastError()
+{
+ return nLastError;
+}
+
+void SetLastError(DWORD dwErrCode)
+{
+ nLastError = dwErrCode;
+}
+#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;
+ close(handle);
+ 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;
+
+ // 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)
+ {
+ 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)
+ {
+ // 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;
+ TCHAR StatusCode[0x08];
+
+ // Check if the file succeeded to open
+ dwDataSize = sizeof(StatusCode);
+ if(HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, StatusCode, &dwDataSize, &dwIndex))
+ {
+ if(_tcscmp(StatusCode, _T("200")))
+ {
+ InternetCloseHandle(hRequest);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return false;
+ }
+ }
+
+ // 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)
+ {
+ InternetCloseHandle(hRequest);
+ pStream->Base.Http.FileSize = dwFileSize;
+ pStream->Base.Http.FilePos = 0;
+ return true;
+ }
+ }
+ }
+
+ // Close the request
+ InternetCloseHandle(hRequest);
+ }
+
+ // Close the connection handle
+ InternetCloseHandle(pStream->Base.Http.hConnect);
+ pStream->Base.Http.hConnect = NULL;
+ }
+
+ // Close the internet handle
+ InternetCloseHandle(pStream->Base.Http.hInternet);
+ pStream->Base.Http.hInternet = NULL;
+ }
+
+ // If the file is not there or is not available for random access, report error
+ pStream->BaseClose(pStream);
+ return false;
+
+#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);
+
+ // ... or close base stream, if any
+ else if(pStream->BaseClose != NULL)
+ pStream->BaseClose(pStream);
+
+ // Free the stream itself
+ STORM_FREE(pStream);
+ }
+}
diff --git a/dep/StormLib/src/FileStream.h b/dep/StormLib/src/FileStream.h
new file mode 100644
index 000000000..44beeed91
--- /dev/null
+++ b/dep/StormLib/src/FileStream.h
@@ -0,0 +1,217 @@
+/*****************************************************************************/
+/* FileStream.h Copyright (c) Ladislav Zezula 2012 */
+/*---------------------------------------------------------------------------*/
+/* Description: Definitions for FileStream object */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 14.04.12 1.00 Lad The first version of FileStream.h */
+/*****************************************************************************/
+
+#ifndef __FILESTREAM_H__
+#define __FILESTREAM_H__
+
+//-----------------------------------------------------------------------------
+// Function prototypes
+
+typedef void (*STREAM_INIT)(
+ struct TFileStream * pStream // Pointer to an unopened stream
+);
+
+typedef bool (*STREAM_CREATE)(
+ struct TFileStream * pStream // Pointer to an unopened stream
+ );
+
+typedef bool (*STREAM_OPEN)(
+ struct TFileStream * pStream, // Pointer to an unopened stream
+ const TCHAR * szFileName, // Pointer to file name to be open
+ DWORD dwStreamFlags // Stream flags
+ );
+
+typedef bool (*STREAM_READ)(
+ struct TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
+ void * pvBuffer, // Pointer to data to be read
+ DWORD dwBytesToRead // Number of bytes to read from the file
+ );
+
+typedef bool (*STREAM_WRITE)(
+ struct TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it writes to the current position
+ const void * pvBuffer, // Pointer to data to be written
+ DWORD dwBytesToWrite // Number of bytes to read from the file
+ );
+
+typedef bool (*STREAM_RESIZE)(
+ struct TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG FileSize // New size for the file, in bytes
+ );
+
+typedef bool (*STREAM_GETSIZE)(
+ struct TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG * pFileSize // Receives the file size, in bytes
+ );
+
+typedef bool (*STREAM_GETPOS)(
+ struct TFileStream * pStream, // Pointer to an open stream
+ ULONGLONG * pByteOffset // Pointer to store current file position
+ );
+
+typedef void (*STREAM_CLOSE)(
+ struct TFileStream * pStream // Pointer to an open stream
+ );
+
+typedef bool (*BLOCK_READ)(
+ struct TFileStream * pStream, // Pointer to a block-oriented stream
+ ULONGLONG StartOffset, // Byte offset of start of the block array
+ ULONGLONG EndOffset, // End offset (either end of the block or end of the file)
+ LPBYTE BlockBuffer, // Pointer to block-aligned buffer
+ DWORD BytesNeeded, // Number of bytes that are really needed
+ bool bAvailable // true if the block is available
+ );
+
+typedef bool (*BLOCK_CHECK)(
+ struct TFileStream * pStream, // Pointer to a block-oriented stream
+ ULONGLONG BlockOffset // Offset of the file to check
+ );
+
+typedef void (*BLOCK_SAVEMAP)(
+ struct TFileStream * pStream // Pointer to a block-oriented stream
+ );
+
+//-----------------------------------------------------------------------------
+// Local structures - partial file structure and bitmap footer
+
+#define ID_FILE_BITMAP_FOOTER 0x33767470 // Signature of the file bitmap footer ('ptv3')
+#define DEFAULT_BLOCK_SIZE 0x00004000 // Default size of the stream block
+#define DEFAULT_BUILD_NUMBER 10958 // Build number for newly created partial MPQs
+
+typedef struct _PART_FILE_HEADER
+{
+ DWORD PartialVersion; // Always set to 2
+ char GameBuildNumber[0x20]; // Minimum build number of the game that can use this MPQ
+ DWORD Flags; // Flags (details unknown)
+ DWORD FileSizeLo; // Low 32 bits of the contained file size
+ DWORD FileSizeHi; // High 32 bits of the contained file size
+ DWORD BlockSize; // Size of one file block, in bytes
+
+} PART_FILE_HEADER, *PPART_FILE_HEADER;
+
+// Structure describing the block-to-file map entry
+typedef struct _PART_FILE_MAP_ENTRY
+{
+ DWORD Flags; // 3 = the block is present in the file
+ DWORD BlockOffsLo; // Low 32 bits of the block position in the file
+ DWORD BlockOffsHi; // High 32 bits of the block position in the file
+ DWORD LargeValueLo; // 64-bit value, meaning is unknown
+ DWORD LargeValueHi;
+
+} PART_FILE_MAP_ENTRY, *PPART_FILE_MAP_ENTRY;
+
+typedef struct _FILE_BITMAP_FOOTER
+{
+ DWORD Signature; // 'ptv3' (ID_FILE_BITMAP_FOOTER)
+ DWORD Version; // Unknown, seems to always have value of 3 (version?)
+ DWORD BuildNumber; // Game build number for that MPQ
+ DWORD MapOffsetLo; // Low 32-bits of the offset of the bit map
+ DWORD MapOffsetHi; // High 32-bits of the offset of the bit map
+ DWORD BlockSize; // Size of one block (usually 0x4000 bytes)
+
+} FILE_BITMAP_FOOTER, *PFILE_BITMAP_FOOTER;
+
+//-----------------------------------------------------------------------------
+// Structure for file stream
+
+union TBaseProviderData
+{
+ struct
+ {
+ ULONGLONG FileSize; // Size of the file
+ ULONGLONG FilePos; // Current file position
+ ULONGLONG FileTime; // Last write time
+ HANDLE hFile; // File handle
+ } File;
+
+ struct
+ {
+ ULONGLONG FileSize; // Size of the file
+ ULONGLONG FilePos; // Current file position
+ ULONGLONG FileTime; // Last write time
+ LPBYTE pbFile; // Pointer to mapped view
+ } Map;
+
+ struct
+ {
+ ULONGLONG FileSize; // Size of the file
+ ULONGLONG FilePos; // Current file position
+ ULONGLONG FileTime; // Last write time
+ HANDLE hInternet; // Internet handle
+ HANDLE hConnect; // Connection to the internet server
+ } Http;
+};
+
+struct TFileStream
+{
+ // Stream provider functions
+ STREAM_READ StreamRead; // Pointer to stream read function for this archive. Do not use directly.
+ STREAM_WRITE StreamWrite; // Pointer to stream write function for this archive. Do not use directly.
+ STREAM_RESIZE StreamResize; // Pointer to function changing file size
+ STREAM_GETSIZE StreamGetSize; // Pointer to function returning file size
+ STREAM_GETPOS StreamGetPos; // Pointer to function that returns current file position
+ STREAM_CLOSE StreamClose; // Pointer to function closing the stream
+
+ // Block-oriented functions
+ BLOCK_READ BlockRead; // Pointer to function reading one or more blocks
+ BLOCK_CHECK BlockCheck; // Pointer to function checking whether the block is present
+
+ // Base provider functions
+ STREAM_CREATE BaseCreate; // Pointer to base create function
+ STREAM_OPEN BaseOpen; // Pointer to base open function
+ STREAM_READ BaseRead; // Read from the stream
+ STREAM_WRITE BaseWrite; // Write to the stream
+ STREAM_RESIZE BaseResize; // Pointer to function changing file size
+ STREAM_GETSIZE BaseGetSize; // Pointer to function returning file size
+ STREAM_GETPOS BaseGetPos; // Pointer to function that returns current file position
+ STREAM_CLOSE BaseClose; // Pointer to function closing the stream
+
+ // Base provider data (file size, file position)
+ TBaseProviderData Base;
+
+ // Stream provider data
+ TFileStream * pMaster; // Master stream (e.g. MPQ on a web server)
+ TCHAR * szFileName; // File name (self-relative pointer)
+
+ ULONGLONG StreamSize; // Stream size (can be less than file size)
+ ULONGLONG StreamPos; // Stream position
+ DWORD BuildNumber; // Game build number
+ DWORD dwFlags; // Stream flags
+
+ // Followed by stream provider data, with variable length
+};
+
+//-----------------------------------------------------------------------------
+// Structures for block-oriented stream
+
+struct TBlockStream : public TFileStream
+{
+ SFILE_DOWNLOAD_CALLBACK pfnCallback; // Callback for downloading
+ void * FileBitmap; // Array of bits for file blocks
+ void * UserData; // User data to be passed to the download callback
+ DWORD BitmapSize; // Size of the file bitmap (in bytes)
+ DWORD BlockSize; // Size of one block, in bytes
+ DWORD BlockCount; // Number of data blocks in the file
+ DWORD IsComplete; // If nonzero, no blocks are missing
+ DWORD IsModified; // nonzero if the bitmap has been modified
+};
+
+//-----------------------------------------------------------------------------
+// Structure for encrypted stream
+
+#define MPQE_CHUNK_SIZE 0x40 // Size of one chunk to be decrypted
+
+struct TEncryptedStream : public TBlockStream
+{
+ BYTE Key[MPQE_CHUNK_SIZE]; // File key
+};
+
+#endif // __FILESTREAM_H__
diff --git a/dep/StormLib/src/SBaseCommon.cpp b/dep/StormLib/src/SBaseCommon.cpp
new file mode 100644
index 000000000..782e154a3
--- /dev/null
+++ b/dep/StormLib/src/SBaseCommon.cpp
@@ -0,0 +1,1842 @@
+/*****************************************************************************/
+/* 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 (for ANSI builds)
+
+void StringCopy(char * szTarget, size_t cchTarget, const char * szSource)
+{
+ if(cchTarget > 0)
+ {
+ size_t cchSource = strlen(szSource);
+
+ if(cchSource >= cchTarget)
+ cchSource = cchTarget - 1;
+
+ memcpy(szTarget, szSource, cchSource);
+ szTarget[cchSource] = 0;
+ }
+}
+
+void StringCat(char * szTarget, size_t cchTargetMax, const char * szSource)
+{
+ // Get the current length of the target
+ size_t cchTarget = strlen(szTarget);
+
+ // Copy the string to the target
+ if(cchTarget < cchTargetMax)
+ {
+ StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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
+void StringCopy(TCHAR * szTarget, size_t cchTarget, const char * szSource)
+{
+ if(cchTarget > 0)
+ {
+ size_t cchSource = strlen(szSource);
+
+ if(cchSource >= cchTarget)
+ cchSource = cchTarget - 1;
+
+ mbstowcs(szTarget, szSource, cchSource);
+ szTarget[cchSource] = 0;
+ }
+}
+
+void StringCopy(char * szTarget, size_t cchTarget, const TCHAR * szSource)
+{
+ if(cchTarget > 0)
+ {
+ size_t cchSource = _tcslen(szSource);
+
+ if(cchSource >= cchTarget)
+ cchSource = cchTarget - 1;
+
+ wcstombs(szTarget, szSource, cchSource);
+ szTarget[cchSource] = 0;
+ }
+}
+
+void StringCopy(TCHAR * szTarget, size_t cchTarget, const TCHAR * szSource)
+{
+ if(cchTarget > 0)
+ {
+ size_t cchSource = _tcslen(szSource);
+
+ if(cchSource >= cchTarget)
+ cchSource = cchTarget - 1;
+
+ memcpy(szTarget, szSource, cchSource * sizeof(TCHAR));
+ szTarget[cchSource] = 0;
+ }
+}
+
+void StringCat(TCHAR * szTarget, size_t cchTargetMax, const TCHAR * szSource)
+{
+ // Get the current length of the target
+ size_t cchTarget = _tcslen(szTarget);
+
+ // Copy the string to the target
+ if(cchTarget < cchTargetMax)
+ {
+ StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource);
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// 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;
+ }
+}
+
+//
+// Note: Implementation of this function in WorldEdit.exe and storm.dll
+// incorrectly treats the character as signed, which leads to the
+// a buffer underflow if the character in the file name >= 0x80:
+// The following steps happen when *pbKey == 0xBF and dwHashType == 0x0000
+// (calculating hash index)
+//
+// 1) Result of AsciiToUpperTable_Slash[*pbKey++] is sign-extended to 0xffffffbf
+// 2) The "ch" is added to dwHashType (0xffffffbf + 0x0000 => 0xffffffbf)
+// 3) The result is used as index to the StormBuffer table,
+// thus dereferences a random value BEFORE the begin of StormBuffer.
+//
+// As result, MPQs containing files with non-ANSI characters will not work between
+// various game versions and localizations. Even WorldEdit, after importing a file
+// with Korean characters in the name, cannot open the file back.
+//
+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
+
+// Returns the nearest higher power of two.
+// If the value is already a power of two, returns the same value
+DWORD GetNearestPowerOfTwo(DWORD dwFileCount)
+{
+ dwFileCount --;
+
+ dwFileCount |= dwFileCount >> 1;
+ dwFileCount |= dwFileCount >> 2;
+ dwFileCount |= dwFileCount >> 4;
+ dwFileCount |= dwFileCount >> 8;
+ dwFileCount |= dwFileCount >> 16;
+
+ return dwFileCount + 1;
+}
+/*
+DWORD GetNearestPowerOfTwo(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)
+ // 2)
+ // 3)
+ // 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 && MPQ_BLOCK_INDEX(pHash) < 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 && MPQ_BLOCK_INDEX(pHash) < 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->Platform = 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/dep/StormLib/src/SBaseDumpData.cpp b/dep/StormLib/src/SBaseDumpData.cpp
new file mode 100644
index 000000000..334561b85
--- /dev/null
+++ b/dep/StormLib/src/SBaseDumpData.cpp
@@ -0,0 +1,189 @@
+/*****************************************************************************/
+/* SBaseDumpData.cpp Copyright (c) Ladislav Zezula 2011 */
+/*---------------------------------------------------------------------------*/
+/* Description : */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 26.01.11 1.00 Lad The first version of SBaseDumpData.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+#ifdef __STORMLIB_DUMP_DATA__
+
+void DumpMpqHeader(TMPQHeader * pHeader)
+{
+ printf("== MPQ Header =================================\n");
+ printf("DWORD dwID = %08X\n", pHeader->dwID);
+ printf("DWORD dwHeaderSize = %08X\n", pHeader->dwHeaderSize);
+ printf("DWORD dwArchiveSize = %08X\n", pHeader->dwArchiveSize);
+ printf("USHORT wFormatVersion = %04X\n", pHeader->wFormatVersion);
+ printf("USHORT wSectorSize = %04X\n", pHeader->wSectorSize);
+ printf("DWORD dwHashTablePos = %08X\n", pHeader->dwHashTablePos);
+ printf("DWORD dwBlockTablePos = %08X\n", pHeader->dwBlockTablePos);
+ printf("DWORD dwHashTableSize = %08X\n", pHeader->dwHashTableSize);
+ printf("DWORD dwBlockTableSize = %08X\n", pHeader->dwBlockTableSize);
+ printf("ULONGLONG HiBlockTablePos64 = %016llX\n", pHeader->HiBlockTablePos64);
+ printf("USHORT wHashTablePosHi = %04X\n", pHeader->wHashTablePosHi);
+ printf("USHORT wBlockTablePosHi = %04X\n", pHeader->wBlockTablePosHi);
+ printf("ULONGLONG ArchiveSize64 = %016llX\n", pHeader->ArchiveSize64);
+ printf("ULONGLONG BetTablePos64 = %016llX\n", pHeader->BetTablePos64);
+ printf("ULONGLONG HetTablePos64 = %016llX\n", pHeader->HetTablePos64);
+ printf("ULONGLONG HashTableSize64 = %016llX\n", pHeader->HashTableSize64);
+ printf("ULONGLONG BlockTableSize64 = %016llX\n", pHeader->BlockTableSize64);
+ printf("ULONGLONG HiBlockTableSize64 = %016llX\n", pHeader->HiBlockTableSize64);
+ printf("ULONGLONG HetTableSize64 = %016llX\n", pHeader->HetTableSize64);
+ printf("ULONGLONG BetTableSize64 = %016llX\n", pHeader->BetTableSize64);
+ printf("DWORD dwRawChunkSize = %08X\n", pHeader->dwRawChunkSize);
+ printf("-----------------------------------------------\n\n");
+}
+
+void DumpHashTable(TMPQHash * pHashTable, DWORD dwHashTableSize)
+{
+ DWORD i;
+
+ if(pHashTable == NULL || dwHashTableSize == 0)
+ return;
+
+ printf("== Hash Table =================================\n");
+ for(i = 0; i < dwHashTableSize; i++)
+ {
+ printf("[%08x] %08X %08X %04X %02X %08X\n", i,
+ pHashTable[i].dwName1,
+ pHashTable[i].dwName2,
+ pHashTable[i].lcLocale,
+ pHashTable[i].Platform,
+ pHashTable[i].dwBlockIndex);
+ }
+ printf("-----------------------------------------------\n\n");
+}
+
+void DumpHetAndBetTable(TMPQHetTable * pHetTable, TMPQBetTable * pBetTable)
+{
+ DWORD i;
+
+ if(pHetTable == NULL || pBetTable == NULL)
+ return;
+
+ printf("== HET Header =================================\n");
+ printf("ULONGLONG AndMask64 = %016llX\n", pHetTable->AndMask64);
+ printf("ULONGLONG OrMask64 = %016llX\n", pHetTable->OrMask64);
+ printf("DWORD dwEntryCount = %08X\n", pHetTable->dwEntryCount);
+ printf("DWORD dwTotalCount = %08X\n", pHetTable->dwTotalCount);
+ printf("DWORD dwNameHashBitSize = %08X\n", pHetTable->dwNameHashBitSize);
+ printf("DWORD dwIndexSizeTotal = %08X\n", pHetTable->dwIndexSizeTotal);
+ printf("DWORD dwIndexSizeExtra = %08X\n", pHetTable->dwIndexSizeExtra);
+ printf("DWORD dwIndexSize = %08X\n", pHetTable->dwIndexSize);
+ printf("-----------------------------------------------\n\n");
+
+ printf("== BET Header =================================\n");
+ printf("DWORD dwTableEntrySize = %08X\n", pBetTable->dwTableEntrySize);
+ printf("DWORD dwBitIndex_FilePos = %08X\n", pBetTable->dwBitIndex_FilePos);
+ printf("DWORD dwBitIndex_FileSize = %08X\n", pBetTable->dwBitIndex_FileSize);
+ printf("DWORD dwBitIndex_CmpSize = %08X\n", pBetTable->dwBitIndex_CmpSize);
+ printf("DWORD dwBitIndex_FlagIndex = %08X\n", pBetTable->dwBitIndex_FlagIndex);
+ printf("DWORD dwBitIndex_Unknown = %08X\n", pBetTable->dwBitIndex_Unknown);
+ printf("DWORD dwBitCount_FilePos = %08X\n", pBetTable->dwBitCount_FilePos);
+ printf("DWORD dwBitCount_FileSize = %08X\n", pBetTable->dwBitCount_FileSize);
+ printf("DWORD dwBitCount_CmpSize = %08X\n", pBetTable->dwBitCount_CmpSize);
+ printf("DWORD dwBitCount_FlagIndex = %08X\n", pBetTable->dwBitCount_FlagIndex);
+ printf("DWORD dwBitCount_Unknown = %08X\n", pBetTable->dwBitCount_Unknown);
+ printf("DWORD dwBitTotal_NameHash2 = %08X\n", pBetTable->dwBitTotal_NameHash2);
+ printf("DWORD dwBitExtra_NameHash2 = %08X\n", pBetTable->dwBitExtra_NameHash2);
+ printf("DWORD dwBitCount_NameHash2 = %08X\n", pBetTable->dwBitCount_NameHash2);
+ printf("DWORD dwEntryCount = %08X\n", pBetTable->dwEntryCount);
+ printf("DWORD dwFlagCount = %08X\n", pBetTable->dwFlagCount);
+ printf("-----------------------------------------------\n\n");
+
+ printf("== HET & Bet Table ======================================================================\n\n");
+ printf("HetIdx HetHash BetIdx BetHash ByteOffset FileSize CmpSize FlgIdx Flags \n");
+ printf("------ ------- ------ ---------------- ---------------- -------- -------- ------ --------\n");
+ for(i = 0; i < pHetTable->dwTotalCount; i++)
+ {
+ ULONGLONG ByteOffset = 0;
+ ULONGLONG BetHash = 0;
+ DWORD dwFileSize = 0;
+ DWORD dwCmpSize = 0;
+ DWORD dwFlagIndex = 0;
+ DWORD dwFlags = 0;
+ DWORD dwBetIndex = 0;
+
+ GetBits(pHetTable->pBetIndexes, i * pHetTable->dwIndexSizeTotal,
+ pHetTable->dwIndexSize,
+ &dwBetIndex,
+ 4);
+
+ if(dwBetIndex < pHetTable->dwTotalCount)
+ {
+ DWORD dwEntryIndex = pBetTable->dwTableEntrySize * dwBetIndex;
+
+ GetBits(pBetTable->pNameHashes, dwBetIndex * pBetTable->dwBitTotal_NameHash2,
+ pBetTable->dwBitCount_NameHash2,
+ &BetHash,
+ 8);
+
+ GetBits(pBetTable->pFileTable, dwEntryIndex + pBetTable->dwBitIndex_FilePos,
+ pBetTable->dwBitCount_FilePos,
+ &ByteOffset,
+ 8);
+
+ GetBits(pBetTable->pFileTable, dwEntryIndex + pBetTable->dwBitIndex_FileSize,
+ pBetTable->dwBitCount_FileSize,
+ &dwFileSize,
+ 4);
+
+ GetBits(pBetTable->pFileTable, dwEntryIndex + pBetTable->dwBitIndex_CmpSize,
+ pBetTable->dwBitCount_CmpSize,
+ &dwCmpSize,
+ 4);
+
+ GetBits(pBetTable->pFileTable, dwEntryIndex + pBetTable->dwBitIndex_FlagIndex,
+ pBetTable->dwBitCount_FlagIndex,
+ &dwFlagIndex,
+ 4);
+
+ dwFlags = pBetTable->pFileFlags[dwFlagIndex];
+ }
+
+ printf(" %04X %02lX %04X %016llX %016llX %08X %08X %04X %08X\n", i,
+ pHetTable->pNameHashes[i],
+ dwBetIndex,
+ BetHash,
+ ByteOffset,
+ dwFileSize,
+ dwCmpSize,
+ dwFlagIndex,
+ dwFlags);
+ }
+ printf("-----------------------------------------------------------------------------------------\n");
+}
+
+void DumpFileTable(TFileEntry * pFileTable, DWORD dwFileTableSize)
+{
+ DWORD i;
+
+ if(pFileTable == NULL || dwFileTableSize == 0)
+ return;
+
+ printf("== File Table =================================\n");
+ for(i = 0; i < dwFileTableSize; i++, pFileTable++)
+ {
+ printf("[%04u] %08X-%08X %08X-%08X %08X-%08X 0x%08X 0x%08X 0x%08X %s\n", i,
+ (DWORD)(pFileTable->FileNameHash >> 0x20),
+ (DWORD)(pFileTable->FileNameHash & 0xFFFFFFFF),
+ (DWORD)(pFileTable->ByteOffset >> 0x20),
+ (DWORD)(pFileTable->ByteOffset & 0xFFFFFFFF),
+ (DWORD)(pFileTable->FileTime >> 0x20),
+ (DWORD)(pFileTable->FileTime & 0xFFFFFFFF),
+ pFileTable->dwFileSize,
+ pFileTable->dwCmpSize,
+ pFileTable->dwFlags,
+ pFileTable->szFileName != NULL ? pFileTable->szFileName : "");
+ }
+ printf("-----------------------------------------------\n\n");
+}
+
+#endif // __STORMLIB_DUMP_DATA__
diff --git a/dep/StormLib/src/SBaseFileTable.cpp b/dep/StormLib/src/SBaseFileTable.cpp
new file mode 100644
index 000000000..e66536d7c
--- /dev/null
+++ b/dep/StormLib/src/SBaseFileTable.cpp
@@ -0,0 +1,2958 @@
+/*****************************************************************************/
+/* SBaseFileTable.cpp Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* Description: Common handler for classic and new hash&block tables */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 06.09.10 1.00 Lad The first version of SBaseFileTable.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local defines
+
+#define INVALID_FLAG_VALUE 0xCCCCCCCC
+#define MAX_FLAG_INDEX 512
+
+//-----------------------------------------------------------------------------
+// Support for calculating bit sizes
+
+static void InitFileFlagArray(LPDWORD FlagArray)
+{
+ memset(FlagArray, 0xCC, MAX_FLAG_INDEX * sizeof(DWORD));
+}
+
+static DWORD GetFileFlagIndex(LPDWORD FlagArray, DWORD dwFlags)
+{
+ // Find free or equal entry in the flag array
+ for(DWORD dwFlagIndex = 0; dwFlagIndex < MAX_FLAG_INDEX; dwFlagIndex++)
+ {
+ if(FlagArray[dwFlagIndex] == INVALID_FLAG_VALUE || FlagArray[dwFlagIndex] == dwFlags)
+ {
+ FlagArray[dwFlagIndex] = dwFlags;
+ return dwFlagIndex;
+ }
+ }
+
+ // This should never happen
+ assert(false);
+ return 0xFFFFFFFF;
+}
+
+static DWORD GetNecessaryBitCount(ULONGLONG MaxValue)
+{
+ DWORD dwBitCount = 0;
+
+ while(MaxValue > 0)
+ {
+ MaxValue >>= 1;
+ dwBitCount++;
+ }
+
+ return dwBitCount;
+}
+
+//-----------------------------------------------------------------------------
+// Support functions for BIT_ARRAY
+
+static USHORT SetBitsMask[] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};
+
+static TBitArray * CreateBitArray(
+ DWORD NumberOfBits,
+ BYTE FillValue)
+{
+ TBitArray * pBitArray;
+ size_t nSize = sizeof(TBitArray) + (NumberOfBits + 7) / 8;
+
+ // Allocate the bit array
+ pBitArray = (TBitArray *)STORM_ALLOC(BYTE, nSize);
+ if(pBitArray != NULL)
+ {
+ memset(pBitArray, FillValue, nSize);
+ pBitArray->NumberOfBytes = (NumberOfBits + 7) / 8;
+ pBitArray->NumberOfBits = NumberOfBits;
+ }
+
+ return pBitArray;
+}
+
+void GetBits(
+ TBitArray * pArray,
+ unsigned int nBitPosition,
+ unsigned int nBitLength,
+ void * pvBuffer,
+ int nResultByteSize)
+{
+ unsigned char * pbBuffer = (unsigned char *)pvBuffer;
+ unsigned int nBytePosition0 = (nBitPosition / 8);
+ unsigned int nBytePosition1 = nBytePosition0 + 1;
+ unsigned int nByteLength = (nBitLength / 8);
+ unsigned int nBitOffset = (nBitPosition & 0x07);
+ unsigned char BitBuffer;
+
+ // Keep compiler happy for platforms where nResultByteSize is not used
+ nResultByteSize = nResultByteSize;
+
+#ifdef _DEBUG
+ // Check if the target is properly zeroed
+ for(int i = 0; i < nResultByteSize; i++)
+ assert(pbBuffer[i] == 0);
+#endif
+
+#ifndef PLATFORM_LITTLE_ENDIAN
+ // Adjust the buffer pointer for big endian platforms
+ pbBuffer += (nResultByteSize - 1);
+#endif
+
+ // Copy whole bytes, if any
+ while(nByteLength > 0)
+ {
+ // Is the current position in the Elements byte-aligned?
+ if(nBitOffset != 0)
+ {
+ BitBuffer = (unsigned char)((pArray->Elements[nBytePosition0] >> nBitOffset) | (pArray->Elements[nBytePosition1] << (0x08 - nBitOffset)));
+ }
+ else
+ {
+ BitBuffer = pArray->Elements[nBytePosition0];
+ }
+
+#ifdef PLATFORM_LITTLE_ENDIAN
+ *pbBuffer++ = BitBuffer;
+#else
+ *pbBuffer-- = BitBuffer;
+#endif
+
+ // Move byte positions and lengths
+ nBytePosition1++;
+ nBytePosition0++;
+ nByteLength--;
+ }
+
+ // Get the rest of the bits
+ nBitLength = (nBitLength & 0x07);
+ if(nBitLength != 0)
+ {
+ *pbBuffer = (unsigned char)(pArray->Elements[nBytePosition0] >> nBitOffset);
+
+ if(nBitLength > (8 - nBitOffset))
+ *pbBuffer = (unsigned char)((pArray->Elements[nBytePosition1] << (8 - nBitOffset)) | (pArray->Elements[nBytePosition0] >> nBitOffset));
+
+ *pbBuffer &= (0x01 << nBitLength) - 1;
+ }
+}
+
+void SetBits(
+ TBitArray * pArray,
+ unsigned int nBitPosition,
+ unsigned int nBitLength,
+ void * pvBuffer,
+ int nResultByteSize)
+{
+ unsigned char * pbBuffer = (unsigned char *)pvBuffer;
+ unsigned int nBytePosition = (nBitPosition / 8);
+ unsigned int nBitOffset = (nBitPosition & 0x07);
+ unsigned short BitBuffer = 0;
+ unsigned short AndMask = 0;
+ unsigned short OneByte = 0;
+
+ // Keep compiler happy for platforms where nResultByteSize is not used
+ nResultByteSize = nResultByteSize;
+
+#ifndef PLATFORM_LITTLE_ENDIAN
+ // Adjust the buffer pointer for big endian platforms
+ pbBuffer += (nResultByteSize - 1);
+#endif
+
+ // Copy whole bytes, if any
+ while(nBitLength > 8)
+ {
+ // Reload the bit buffer
+#ifdef PLATFORM_LITTLE_ENDIAN
+ OneByte = *pbBuffer++;
+#else
+ OneByte = *pbBuffer--;
+#endif
+ // Update the BitBuffer and AndMask for the bit array
+ BitBuffer = (BitBuffer >> 0x08) | (OneByte << nBitOffset);
+ AndMask = (AndMask >> 0x08) | (0x00FF << nBitOffset);
+
+ // Update the byte in the array
+ pArray->Elements[nBytePosition] = (BYTE)((pArray->Elements[nBytePosition] & ~AndMask) | BitBuffer);
+
+ // Move byte positions and lengths
+ nBytePosition++;
+ nBitLength -= 0x08;
+ }
+
+ if(nBitLength != 0)
+ {
+ // Reload the bit buffer
+ OneByte = *pbBuffer;
+
+ // Update the AND mask for the last bit
+ BitBuffer = (BitBuffer >> 0x08) | (OneByte << nBitOffset);
+ AndMask = (AndMask >> 0x08) | (SetBitsMask[nBitLength] << nBitOffset);
+
+ // Update the byte in the array
+ pArray->Elements[nBytePosition] = (BYTE)((pArray->Elements[nBytePosition] & ~AndMask) | BitBuffer);
+
+ // Update the next byte, if needed
+ if(AndMask & 0xFF00)
+ {
+ nBytePosition++;
+ BitBuffer >>= 0x08;
+ AndMask >>= 0x08;
+
+ pArray->Elements[nBytePosition] = (BYTE)((pArray->Elements[nBytePosition] & ~AndMask) | BitBuffer);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Support for MPQ header
+
+static ULONGLONG DetermineArchiveSize_V1(
+ TMPQArchive * ha,
+ TMPQHeader * pHeader,
+ ULONGLONG MpqOffset,
+ ULONGLONG FileSize)
+{
+ ULONGLONG ByteOffset;
+ ULONGLONG EndOfMpq = FileSize;
+ DWORD SignatureHeader = 0;
+ DWORD dwArchiveSize32;
+
+ // This could only be called for MPQs version 1.0
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1);
+
+ // Check if we can rely on the archive size in the header
+ if(pHeader->dwBlockTablePos < pHeader->dwArchiveSize)
+ {
+ // The block table cannot be compressed, so the sizes must match
+ if((pHeader->dwArchiveSize - pHeader->dwBlockTablePos) == (pHeader->dwBlockTableSize * sizeof(TMPQBlock)))
+ return pHeader->dwArchiveSize;
+
+ // If the archive size in the header is less than real file size
+ dwArchiveSize32 = (DWORD)(FileSize - MpqOffset);
+ if(pHeader->dwArchiveSize == dwArchiveSize32)
+ return pHeader->dwArchiveSize;
+ }
+
+ // Check if there is a signature header
+ if((EndOfMpq - MpqOffset) > (MPQ_STRONG_SIGNATURE_SIZE + 4))
+ {
+ ByteOffset = EndOfMpq - MPQ_STRONG_SIGNATURE_SIZE - 4;
+ if(FileStream_Read(ha->pStream, &ByteOffset, &SignatureHeader, sizeof(DWORD)))
+ {
+ if(BSWAP_INT32_UNSIGNED(SignatureHeader) == MPQ_STRONG_SIGNATURE_ID)
+ EndOfMpq = EndOfMpq - MPQ_STRONG_SIGNATURE_SIZE - 4;
+ }
+ }
+
+ // Return the returned archive size
+ return (EndOfMpq - MpqOffset);
+}
+
+static ULONGLONG DetermineArchiveSize_V2(
+ TMPQHeader * pHeader,
+ ULONGLONG MpqOffset,
+ ULONGLONG FileSize)
+{
+ ULONGLONG EndOfMpq = FileSize;
+ DWORD dwArchiveSize32;
+
+ // This could only be called for MPQs version 2.0
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_2);
+
+ // Check if we can rely on the archive size in the header
+ if((FileSize >> 0x20) == 0)
+ {
+ if(pHeader->dwBlockTablePos < pHeader->dwArchiveSize)
+ {
+ if((pHeader->dwArchiveSize - pHeader->dwBlockTablePos) <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)))
+ return pHeader->dwArchiveSize;
+
+ // If the archive size in the header is less than real file size
+ dwArchiveSize32 = (DWORD)(FileSize - MpqOffset);
+ if(pHeader->dwArchiveSize <= dwArchiveSize32)
+ return pHeader->dwArchiveSize;
+ }
+ }
+
+ // Return the calculated archive size
+ return (EndOfMpq - MpqOffset);
+}
+
+ULONGLONG FileOffsetFromMpqOffset(TMPQArchive * ha, ULONGLONG MpqOffset)
+{
+ if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ {
+ // For MPQ archive v1, any file offset is only 32-bit
+ return (ULONGLONG)((DWORD)ha->MpqPos + (DWORD)MpqOffset);
+ }
+ else
+ {
+ // For MPQ archive v2+, file offsets are full 64-bit
+ return ha->MpqPos + MpqOffset;
+ }
+}
+
+ULONGLONG CalculateRawSectorOffset(
+ TMPQFile * hf,
+ DWORD dwSectorOffset)
+{
+ ULONGLONG RawFilePos;
+
+ // Must be used for files within a MPQ
+ assert(hf->ha != NULL);
+ assert(hf->ha->pHeader != NULL);
+
+ //
+ // Some MPQ protectors place the sector offset table after the actual file data.
+ // Sector offsets in the sector offset table are negative. When added
+ // to MPQ file offset from the block table entry, the result is a correct
+ // position of the file data in the MPQ.
+ //
+ // For MPQs version 1.0, the offset is purely 32-bit
+ //
+
+ RawFilePos = hf->RawFilePos + dwSectorOffset;
+ if(hf->ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
+ RawFilePos = (DWORD)hf->ha->MpqPos + (DWORD)hf->pFileEntry->ByteOffset + dwSectorOffset;
+
+ // We also have to add patch header size, if patch header is present
+ if(hf->pPatchInfo != NULL)
+ RawFilePos += hf->pPatchInfo->dwLength;
+
+ // Return the result offset
+ return RawFilePos;
+}
+
+// This function converts the MPQ header so it always looks like version 4
+int ConvertMpqHeaderToFormat4(
+ TMPQArchive * ha,
+ ULONGLONG MpqOffset,
+ ULONGLONG FileSize,
+ DWORD dwFlags,
+ bool bIsWarcraft3Map)
+{
+ TMPQHeader * pHeader = (TMPQHeader *)ha->HeaderData;
+ ULONGLONG BlockTablePos64 = 0;
+ ULONGLONG HashTablePos64 = 0;
+ ULONGLONG BlockTableMask = (ULONGLONG)-1;
+ ULONGLONG ByteOffset;
+ USHORT wFormatVersion = BSWAP_INT16_UNSIGNED(pHeader->wFormatVersion);
+ int nError = ERROR_SUCCESS;
+
+ // If version 1.0 is forced, then the format version is forced to be 1.0
+ // Reason: Storm.dll in Warcraft III ignores format version value
+ if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) || bIsWarcraft3Map)
+ wFormatVersion = MPQ_FORMAT_VERSION_1;
+
+ // Format-specific fixes
+ switch(wFormatVersion)
+ {
+ case MPQ_FORMAT_VERSION_1:
+
+ // Check for malformed MPQ header version 1.0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_1);
+ if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_1 || pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V1)
+ {
+ pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1;
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ }
+
+ //
+ // Note: The value of "dwArchiveSize" member in the MPQ header
+ // is ignored by Storm.dll and can contain garbage value
+ // ("w3xmaster" protector).
+ //
+
+ Label_ArchiveVersion1:
+ if(pHeader->dwBlockTableSize > 1) // Prevent empty MPQs being marked as malformed
+ {
+ if(pHeader->dwHashTablePos <= pHeader->dwHeaderSize || (pHeader->dwHashTablePos & 0x80000000))
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ if(pHeader->dwBlockTablePos <= pHeader->dwHeaderSize || (pHeader->dwBlockTablePos & 0x80000000))
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ }
+
+ // Only low byte of sector size is really used
+ if(pHeader->wSectorSize & 0xFF00)
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ pHeader->wSectorSize = pHeader->wSectorSize & 0xFF;
+
+ // Fill the rest of the header
+ memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V1, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V1);
+ pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash);
+ pHeader->ArchiveSize64 = pHeader->dwArchiveSize;
+
+ // Block table position must be calculated as 32-bit value
+ // Note: BOBA protector puts block table before the MPQ header, so it is negative
+ BlockTablePos64 = (ULONGLONG)((DWORD)MpqOffset + pHeader->dwBlockTablePos);
+ BlockTableMask = 0xFFFFFFF0;
+
+ // Determine the archive size on malformed MPQs
+ if(ha->dwFlags & MPQ_FLAG_MALFORMED)
+ {
+ // Calculate the archive size
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V1(ha, pHeader, MpqOffset, FileSize);
+ pHeader->dwArchiveSize = (DWORD)pHeader->ArchiveSize64;
+ }
+
+ // EWIX_v8_7.w3x: TMPQHeader::dwBlockTableSize = 0x00319601
+ // Size of TFileTable goes to ~200MB, so we artificially cut it
+ if(BlockTablePos64 + (pHeader->dwBlockTableSize * sizeof(TMPQBlock)) > FileSize)
+ {
+ pHeader->dwBlockTableSize = (DWORD)((FileSize - BlockTablePos64) / sizeof(TMPQBlock));
+ pHeader->BlockTableSize64 = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ }
+ break;
+
+ case MPQ_FORMAT_VERSION_2:
+
+ // Check for malformed MPQ header version 1.0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_2);
+ if(pHeader->wFormatVersion != MPQ_FORMAT_VERSION_2 || pHeader->dwHeaderSize != MPQ_HEADER_SIZE_V2)
+ {
+ pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1;
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ goto Label_ArchiveVersion1;
+ }
+
+ // Fill the rest of the header with zeros
+ memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V2, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V2);
+
+ // Calculate the expected hash table size
+ pHeader->HashTableSize64 = (pHeader->dwHashTableSize * sizeof(TMPQHash));
+ HashTablePos64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
+
+ // Calculate the expected block table size
+ pHeader->BlockTableSize64 = (pHeader->dwBlockTableSize * sizeof(TMPQBlock));
+ BlockTablePos64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+
+ // We require the block table to follow hash table
+ if(BlockTablePos64 >= HashTablePos64)
+ {
+ // HashTableSize64 may be less than TblSize * sizeof(TMPQHash).
+ // That means that the hash table is compressed.
+ pHeader->HashTableSize64 = BlockTablePos64 - HashTablePos64;
+
+ // Calculate the compressed block table size
+ if(pHeader->HiBlockTablePos64 != 0)
+ {
+ // BlockTableSize64 may be less than TblSize * sizeof(TMPQBlock).
+ // That means that the block table is compressed.
+ pHeader->BlockTableSize64 = pHeader->HiBlockTablePos64 - BlockTablePos64;
+ assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
+
+ // Determine real archive size
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, MpqOffset, FileSize);
+
+ // Calculate the size of the hi-block table
+ pHeader->HiBlockTableSize64 = pHeader->ArchiveSize64 - pHeader->HiBlockTablePos64;
+ assert(pHeader->HiBlockTableSize64 == (pHeader->dwBlockTableSize * sizeof(USHORT)));
+ }
+ else
+ {
+ // Determine real archive size
+ pHeader->ArchiveSize64 = DetermineArchiveSize_V2(pHeader, MpqOffset, FileSize);
+
+ // Calculate size of the block table
+ pHeader->BlockTableSize64 = pHeader->ArchiveSize64 - BlockTablePos64;
+ assert(pHeader->BlockTableSize64 <= (pHeader->dwBlockTableSize * sizeof(TMPQBlock)));
+ }
+ }
+ else
+ {
+ pHeader->ArchiveSize64 = pHeader->dwArchiveSize;
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ }
+
+ // Add the MPQ Offset
+ BlockTablePos64 += MpqOffset;
+ break;
+
+ case MPQ_FORMAT_VERSION_3:
+
+ // In MPQ format 3.0, the entire header is optional
+ // and the size of the header can actually be identical
+ // to size of header 2.0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_3);
+ if(pHeader->dwHeaderSize < MPQ_HEADER_SIZE_V3)
+ {
+ pHeader->ArchiveSize64 = pHeader->dwArchiveSize;
+ pHeader->HetTablePos64 = 0;
+ pHeader->BetTablePos64 = 0;
+ }
+
+ //
+ // We need to calculate the compressed size of each table. We assume the following order:
+ // 1) HET table
+ // 2) BET table
+ // 3) Classic hash table
+ // 4) Classic block table
+ // 5) Hi-block table
+ //
+
+ // Fill the rest of the header with zeros
+ memset((LPBYTE)pHeader + MPQ_HEADER_SIZE_V3, 0, sizeof(TMPQHeader) - MPQ_HEADER_SIZE_V3);
+ BlockTablePos64 = MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ HashTablePos64 = MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos);
+ ByteOffset = pHeader->ArchiveSize64;
+
+ // Size of the hi-block table
+ if(pHeader->HiBlockTablePos64)
+ {
+ pHeader->HiBlockTableSize64 = ByteOffset - pHeader->HiBlockTablePos64;
+ ByteOffset = pHeader->HiBlockTablePos64;
+ }
+
+ // Size of the block table
+ if(BlockTablePos64)
+ {
+ pHeader->BlockTableSize64 = ByteOffset - BlockTablePos64;
+ ByteOffset = BlockTablePos64;
+ }
+
+ // Size of the hash table
+ if(HashTablePos64)
+ {
+ pHeader->HashTableSize64 = ByteOffset - HashTablePos64;
+ ByteOffset = HashTablePos64;
+ }
+
+ // Size of the BET table
+ if(pHeader->BetTablePos64)
+ {
+ pHeader->BetTableSize64 = ByteOffset - pHeader->BetTablePos64;
+ ByteOffset = pHeader->BetTablePos64;
+ }
+
+ // Size of the HET table
+ if(pHeader->HetTablePos64)
+ {
+ pHeader->HetTableSize64 = ByteOffset - pHeader->HetTablePos64;
+// ByteOffset = pHeader->HetTablePos64;
+ }
+
+ // Add the MPQ Offset
+ BlockTablePos64 += MpqOffset;
+ break;
+
+ case MPQ_FORMAT_VERSION_4:
+
+ // Verify header MD5. Header MD5 is calculated from the MPQ header since the 'MPQ\x1A'
+ // signature until the position of header MD5 at offset 0xC0
+ BSWAP_TMPQHEADER(pHeader, MPQ_FORMAT_VERSION_4);
+ if(!VerifyDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader))
+ nError = ERROR_FILE_CORRUPT;
+
+ // Calculate the block table position
+ BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ break;
+
+ default:
+
+ // Check if it's a War of the Immortal data file (SQP)
+ // If not, we treat it as malformed MPQ version 1.0
+ if(ConvertSqpHeaderToFormat4(ha, FileSize, dwFlags) != ERROR_SUCCESS)
+ {
+ pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1;
+ pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ goto Label_ArchiveVersion1;
+ }
+
+ // Calculate the block table position
+ BlockTablePos64 = MpqOffset + MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos);
+ break;
+ }
+
+ // Handle case when block table is placed before the MPQ header
+ // Used by BOBA protector
+ if(BlockTablePos64 < MpqOffset)
+ ha->dwFlags |= MPQ_FLAG_MALFORMED;
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Support for hash table
+
+// Hash entry verification when the file table does not exist yet
+bool IsValidHashEntry(TMPQArchive * ha, TMPQHash * pHash)
+{
+ TFileEntry * pFileEntry = ha->pFileTable + MPQ_BLOCK_INDEX(pHash);
+
+ return ((MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize) && (pFileEntry->dwFlags & MPQ_FILE_EXISTS)) ? true : false;
+}
+
+// Hash entry verification when the file table does not exist yet
+static bool IsValidHashEntry1(TMPQArchive * ha, TMPQHash * pHash, TMPQBlock * pBlockTable)
+{
+ ULONGLONG ByteOffset;
+ TMPQBlock * pBlock;
+
+ // The block index is considered valid if it's less than block table size
+ if(MPQ_BLOCK_INDEX(pHash) < ha->pHeader->dwBlockTableSize)
+ {
+ // Calculate the block table position
+ pBlock = pBlockTable + MPQ_BLOCK_INDEX(pHash);
+
+ // Check whether this is an existing file
+ // Also we do not allow to be file size greater than 2GB
+ if((pBlock->dwFlags & MPQ_FILE_EXISTS) && (pBlock->dwFSize & 0x80000000) == 0)
+ {
+ // The begin of the file must be within the archive
+ ByteOffset = FileOffsetFromMpqOffset(ha, pBlock->dwFilePos);
+ return (ByteOffset < ha->FileSize);
+ }
+ }
+
+ return false;
+}
+
+// Returns a hash table entry in the following order:
+// 1) A hash table entry with the preferred locale and platform
+// 2) A hash table entry with the neutral|matching locale and neutral|matching platform
+// 3) NULL
+// Storm_2016.dll: 15020940
+static TMPQHash * GetHashEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale, BYTE Platform)
+{
+ TMPQHash * pFirstHash = GetFirstHashEntry(ha, szFileName);
+ TMPQHash * pBestEntry = NULL;
+ TMPQHash * pHash = pFirstHash;
+
+ // Parse the found hashes
+ while(pHash != NULL)
+ {
+ // Storm_2016.dll: 150209CB
+ // If the hash entry matches both locale and platform, return it immediately
+ // Note: We only succeed this check if the locale is non-neutral, because
+ // some Warcraft III maps have several items with neutral locale&platform, which leads
+ // to wrong item being returned
+ if((lcLocale || Platform) && pHash->lcLocale == lcLocale && pHash->Platform == Platform)
+ return pHash;
+
+ // Storm_2016.dll: 150209D9
+ // If (locale matches or is neutral) OR (platform matches or is neutral)
+ // remember this as the best entry
+ if(pHash->lcLocale == 0 || pHash->lcLocale == lcLocale)
+ {
+ if(pHash->Platform == 0 || pHash->Platform == Platform)
+ pBestEntry = pHash;
+ }
+
+ // Get the next hash entry for that file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // At the end, return neutral hash (if found), otherwise NULL
+ return pBestEntry;
+}
+
+// Returns a hash table entry in the following order:
+// 1) A hash table entry with the preferred locale
+// 2) NULL
+static TMPQHash * GetHashEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+{
+ TMPQHash * pFirstHash = GetFirstHashEntry(ha, szFileName);
+ TMPQHash * pHash = pFirstHash;
+
+ // Parse the found hashes
+ while(pHash != NULL)
+ {
+ // If the locales match, return it
+ if(pHash->lcLocale == lcLocale)
+ return pHash;
+
+ // Get the next hash entry for that file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // Not found
+ return NULL;
+}
+
+// Defragment the file table so it does not contain any gaps
+// Note: As long as all values of all TMPQHash::dwBlockIndex
+// are not HASH_ENTRY_FREE, the startup search index does not matter.
+// Hash table is circular, so as long as there is no terminator,
+// all entries will be found.
+static TMPQHash * DefragmentHashTable(
+ TMPQArchive * ha,
+ TMPQHash * pHashTable,
+ TMPQBlock * pBlockTable)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQHash * pHashTableEnd = pHashTable + pHeader->dwHashTableSize;
+ TMPQHash * pSource = pHashTable;
+ TMPQHash * pTarget = pHashTable;
+ DWORD dwFirstFreeEntry;
+ DWORD dwNewTableSize;
+
+ // Sanity checks
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1);
+ assert(pHeader->HiBlockTablePos64 == 0);
+
+ // Parse the hash table and move the entries to the begin of it
+ for(pSource = pHashTable; pSource < pHashTableEnd; pSource++)
+ {
+ // Check whether this is a valid hash table entry
+ if(IsValidHashEntry1(ha, pSource, pBlockTable))
+ {
+ // Copy the hash table entry back
+ if(pSource > pTarget)
+ pTarget[0] = pSource[0];
+
+ // Move the target
+ pTarget++;
+ }
+ }
+
+ // Calculate how many entries in the hash table we really need
+ dwFirstFreeEntry = (DWORD)(pTarget - pHashTable);
+ dwNewTableSize = GetNearestPowerOfTwo(dwFirstFreeEntry);
+
+ // Fill the rest with entries that look like deleted
+ pHashTableEnd = pHashTable + dwNewTableSize;
+ pSource = pHashTable + dwFirstFreeEntry;
+ memset(pSource, 0xFF, (dwNewTableSize - dwFirstFreeEntry) * sizeof(TMPQHash));
+
+ // Mark the block indexes as deleted
+ for(; pSource < pHashTableEnd; pSource++)
+ pSource->dwBlockIndex = HASH_ENTRY_DELETED;
+
+ // Free some of the space occupied by the hash table
+ if(dwNewTableSize < pHeader->dwHashTableSize)
+ {
+ pHashTable = STORM_REALLOC(TMPQHash, pHashTable, dwNewTableSize);
+ ha->pHeader->BlockTableSize64 = dwNewTableSize * sizeof(TMPQHash);
+ ha->pHeader->dwHashTableSize = dwNewTableSize;
+ }
+
+ return pHashTable;
+}
+
+static int BuildFileTableFromBlockTable(
+ TMPQArchive * ha,
+ TMPQBlock * pBlockTable)
+{
+ TFileEntry * pFileEntry;
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlock;
+ TMPQHash * pHashTableEnd;
+ TMPQHash * pHash;
+ LPDWORD DefragmentTable = NULL;
+ DWORD dwItemCount = 0;
+ DWORD dwFlagMask;
+
+ // Sanity checks
+ assert(ha->pFileTable != NULL);
+ assert(ha->dwFileTableSize >= ha->dwMaxFileCount);
+
+ // MPQs for Warcraft III doesn't know some flags, namely MPQ_FILE_SINGLE_UNIT and MPQ_FILE_PATCH_FILE
+ dwFlagMask = (ha->dwFlags & MPQ_FLAG_WAR3_MAP) ? MPQ_FILE_VALID_FLAGS_W3X : MPQ_FILE_VALID_FLAGS;
+
+ // Defragment the hash table, if needed
+ if(ha->dwFlags & MPQ_FLAG_HASH_TABLE_CUT)
+ {
+ ha->pHashTable = DefragmentHashTable(ha, ha->pHashTable, pBlockTable);
+ ha->dwMaxFileCount = pHeader->dwHashTableSize;
+ }
+
+ // If the hash table or block table is cut,
+ // we will defragment the block table
+ if(ha->dwFlags & (MPQ_FLAG_HASH_TABLE_CUT | MPQ_FLAG_BLOCK_TABLE_CUT))
+ {
+ // Sanity checks
+ assert(pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1);
+ assert(pHeader->HiBlockTablePos64 == 0);
+
+ // Allocate the translation table
+ DefragmentTable = STORM_ALLOC(DWORD, pHeader->dwBlockTableSize);
+ if(DefragmentTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the translation table
+ memset(DefragmentTable, 0xFF, pHeader->dwBlockTableSize * sizeof(DWORD));
+ }
+
+ // Parse the entire hash table
+ pHashTableEnd = ha->pHashTable + pHeader->dwHashTableSize;
+ for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
+ {
+ //
+ // We need to properly handle these cases:
+ // - Multiple hash entries (same file name) point to the same block entry
+ // - Multiple hash entries (different file name) point to the same block entry
+ //
+ // Ignore all hash table entries where:
+ // - Block Index >= BlockTableSize
+ // - Flags of the appropriate block table entry
+ //
+
+ if(IsValidHashEntry1(ha, pHash, pBlockTable))
+ {
+ DWORD dwOldIndex = MPQ_BLOCK_INDEX(pHash);
+ DWORD dwNewIndex = MPQ_BLOCK_INDEX(pHash);
+
+ // Determine the new block index
+ if(DefragmentTable != NULL)
+ {
+ // Need to handle case when multiple hash
+ // entries point to the same block entry
+ if(DefragmentTable[dwOldIndex] == HASH_ENTRY_FREE)
+ {
+ DefragmentTable[dwOldIndex] = dwItemCount;
+ dwNewIndex = dwItemCount++;
+ }
+ else
+ {
+ dwNewIndex = DefragmentTable[dwOldIndex];
+ }
+
+ // Fix the pointer in the hash entry
+ pHash->dwBlockIndex = dwNewIndex;
+
+ // Dump the relocation entry
+// printf("Relocating hash entry %08X-%08X: %08X -> %08X\n", pHash->dwName1, pHash->dwName2, dwBlockIndex, dwNewIndex);
+ }
+
+ // Get the pointer to the file entry and the block entry
+ pFileEntry = ha->pFileTable + dwNewIndex;
+ pBlock = pBlockTable + dwOldIndex;
+
+ // ByteOffset is only valid if file size is not zero
+ pFileEntry->ByteOffset = pBlock->dwFilePos;
+ if(pFileEntry->ByteOffset == 0 && pBlock->dwFSize == 0)
+ pFileEntry->ByteOffset = ha->pHeader->dwHeaderSize;
+
+ // Fill the rest of the file entry
+ pFileEntry->dwFileSize = pBlock->dwFSize;
+ pFileEntry->dwCmpSize = pBlock->dwCSize;
+ pFileEntry->dwFlags = pBlock->dwFlags & dwFlagMask;
+ }
+ }
+
+ // Free the translation table
+ if(DefragmentTable != NULL)
+ {
+ // If we defragmented the block table in the process,
+ // free some memory by shrinking the file table
+ if(ha->dwFileTableSize > ha->dwMaxFileCount)
+ {
+ ha->pFileTable = STORM_REALLOC(TFileEntry, ha->pFileTable, ha->dwMaxFileCount);
+ ha->pHeader->BlockTableSize64 = ha->dwMaxFileCount * sizeof(TMPQBlock);
+ ha->pHeader->dwBlockTableSize = ha->dwMaxFileCount;
+ ha->dwFileTableSize = ha->dwMaxFileCount;
+ }
+
+// DumpFileTable(ha->pFileTable, ha->dwFileTableSize);
+
+ // Free the translation table
+ STORM_FREE(DefragmentTable);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static TMPQHash * TranslateHashTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbTableSize)
+{
+ TMPQHash * pHashTable;
+ size_t HashTableSize;
+
+ // Allocate copy of the hash table
+ pHashTable = STORM_ALLOC(TMPQHash, ha->pHeader->dwHashTableSize);
+ if(pHashTable != NULL)
+ {
+ // Copy the hash table
+ HashTableSize = sizeof(TMPQHash) * ha->pHeader->dwHashTableSize;
+ memcpy(pHashTable, ha->pHashTable, HashTableSize);
+
+ // Give the size to the caller
+ if(pcbTableSize != NULL)
+ {
+ *pcbTableSize = (ULONGLONG)HashTableSize;
+ }
+ }
+
+ return pHashTable;
+}
+
+// Also used in SFileGetFileInfo
+TMPQBlock * TranslateBlockTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbTableSize,
+ bool * pbNeedHiBlockTable)
+{
+ TFileEntry * pFileEntry = ha->pFileTable;
+ TMPQBlock * pBlockTable;
+ TMPQBlock * pBlock;
+ DWORD NeedHiBlockTable = 0;
+ DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize;
+
+ // Allocate copy of the hash table
+ pBlockTable = pBlock = STORM_ALLOC(TMPQBlock, dwBlockTableSize);
+ if(pBlockTable != NULL)
+ {
+ // Convert the block table
+ for(DWORD i = 0; i < dwBlockTableSize; i++)
+ {
+ NeedHiBlockTable |= (DWORD)(pFileEntry->ByteOffset >> 32);
+ pBlock->dwFilePos = (DWORD)pFileEntry->ByteOffset;
+ pBlock->dwFSize = pFileEntry->dwFileSize;
+ pBlock->dwCSize = pFileEntry->dwCmpSize;
+ pBlock->dwFlags = pFileEntry->dwFlags;
+
+ pFileEntry++;
+ pBlock++;
+ }
+
+ // Give the size to the caller
+ if(pcbTableSize != NULL)
+ *pcbTableSize = (ULONGLONG)dwBlockTableSize * sizeof(TMPQBlock);
+
+ if(pbNeedHiBlockTable != NULL)
+ *pbNeedHiBlockTable = NeedHiBlockTable ? true : false;
+ }
+
+ return pBlockTable;
+}
+
+static USHORT * TranslateHiBlockTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbTableSize)
+{
+ TFileEntry * pFileEntry = ha->pFileTable;
+ USHORT * pHiBlockTable;
+ USHORT * pHiBlock;
+ DWORD dwBlockTableSize = ha->pHeader->dwBlockTableSize;
+
+ // Allocate copy of the hash table
+ pHiBlockTable = pHiBlock = STORM_ALLOC(USHORT, dwBlockTableSize);
+ if(pHiBlockTable != NULL)
+ {
+ // Copy the block table
+ for(DWORD i = 0; i < dwBlockTableSize; i++)
+ pHiBlock[i] = (USHORT)(pFileEntry[i].ByteOffset >> 0x20);
+
+ // Give the size to the caller
+ if(pcbTableSize != NULL)
+ *pcbTableSize = (ULONGLONG)dwBlockTableSize * sizeof(USHORT);
+ }
+
+ return pHiBlockTable;
+}
+
+//-----------------------------------------------------------------------------
+// General EXT table functions
+
+TMPQExtHeader * LoadExtTable(
+ TMPQArchive * ha,
+ ULONGLONG ByteOffset,
+ size_t Size,
+ DWORD dwSignature,
+ DWORD dwKey)
+{
+ TMPQExtHeader * pCompressed = NULL; // Compressed table
+ TMPQExtHeader * pExtTable = NULL; // Uncompressed table
+
+ // Do nothing if the size is zero
+ if(ByteOffset != 0 && Size != 0)
+ {
+ // Allocate size for the compressed table
+ pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, Size);
+ if(pExtTable != NULL)
+ {
+ // Load the table from the MPQ
+ ByteOffset += ha->MpqPos;
+ if(!FileStream_Read(ha->pStream, &ByteOffset, pExtTable, (DWORD)Size))
+ {
+ STORM_FREE(pExtTable);
+ return NULL;
+ }
+
+ // Swap the ext table header
+ BSWAP_ARRAY32_UNSIGNED(pExtTable, sizeof(TMPQExtHeader));
+ if(pExtTable->dwSignature != dwSignature)
+ {
+ STORM_FREE(pExtTable);
+ return NULL;
+ }
+
+ // Decrypt the block
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+ DecryptMpqBlock(pExtTable + 1, (DWORD)(Size - sizeof(TMPQExtHeader)), dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+
+ // If the table is compressed, decompress it
+ if((pExtTable->dwDataSize + sizeof(TMPQExtHeader)) > Size)
+ {
+ pCompressed = pExtTable;
+ pExtTable = (TMPQExtHeader *)STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + pCompressed->dwDataSize);
+ if(pExtTable != NULL)
+ {
+ int cbOutBuffer = (int)pCompressed->dwDataSize;
+ int cbInBuffer = (int)Size;
+
+ // Decompress the extended table
+ pExtTable->dwSignature = pCompressed->dwSignature;
+ pExtTable->dwVersion = pCompressed->dwVersion;
+ pExtTable->dwDataSize = pCompressed->dwDataSize;
+ if(!SCompDecompress2(pExtTable + 1, &cbOutBuffer, pCompressed + 1, cbInBuffer))
+ {
+ STORM_FREE(pExtTable);
+ pExtTable = NULL;
+ }
+ }
+
+ // Free the compressed block
+ STORM_FREE(pCompressed);
+ }
+ }
+ }
+
+ // Return the decompressed table to the caller
+ return pExtTable;
+}
+
+static int SaveMpqTable(
+ TMPQArchive * ha,
+ void * pMpqTable,
+ ULONGLONG ByteOffset,
+ size_t Size,
+ unsigned char * md5,
+ DWORD dwKey,
+ bool bCompress)
+{
+ ULONGLONG FileOffset;
+ void * pCompressed = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Do we have to compress the table?
+ if(bCompress)
+ {
+ int cbOutBuffer = (int)Size;
+ int cbInBuffer = (int)Size;
+
+ // Allocate extra space for compressed table
+ pCompressed = STORM_ALLOC(BYTE, Size);
+ if(pCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Compress the table
+ SCompCompress(pCompressed, &cbOutBuffer, pMpqTable, cbInBuffer, MPQ_COMPRESSION_ZLIB, 0, 0);
+
+ // If the compression failed, revert it. Otherwise, swap the tables
+ if(cbOutBuffer >= cbInBuffer)
+ {
+ STORM_FREE(pCompressed);
+ pCompressed = NULL;
+ }
+ else
+ {
+ pMpqTable = pCompressed;
+ }
+ }
+
+ // Encrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pMpqTable, Size);
+ EncryptMpqBlock(pMpqTable, (DWORD)Size, dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pMpqTable, Size);
+ }
+
+ // Calculate the MD5
+ if(md5 != NULL)
+ {
+ CalculateDataBlockHash(pMpqTable, (DWORD)Size, md5);
+ }
+
+ // Save the table to the MPQ
+ BSWAP_ARRAY32_UNSIGNED(pMpqTable, Size);
+ FileOffset = ha->MpqPos + ByteOffset;
+ if(!FileStream_Write(ha->pStream, &FileOffset, pMpqTable, (DWORD)Size))
+ nError = GetLastError();
+
+ // Free the compressed table, if any
+ if(pCompressed != NULL)
+ STORM_FREE(pCompressed);
+ return nError;
+}
+
+static int SaveExtTable(
+ TMPQArchive * ha,
+ TMPQExtHeader * pExtTable,
+ ULONGLONG ByteOffset,
+ DWORD dwTableSize,
+ unsigned char * md5,
+ DWORD dwKey,
+ bool bCompress,
+ LPDWORD pcbTotalSize)
+{
+ ULONGLONG FileOffset;
+ TMPQExtHeader * pCompressed = NULL;
+ DWORD cbTotalSize = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Do we have to compress the table?
+ if(bCompress)
+ {
+ int cbOutBuffer = (int)dwTableSize;
+ int cbInBuffer = (int)dwTableSize;
+
+ // Allocate extra space for compressed table
+ pCompressed = (TMPQExtHeader *)STORM_ALLOC(BYTE, dwTableSize);
+ if(pCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Compress the table
+ pCompressed->dwSignature = pExtTable->dwSignature;
+ pCompressed->dwVersion = pExtTable->dwVersion;
+ pCompressed->dwDataSize = pExtTable->dwDataSize;
+ SCompCompress((pCompressed + 1), &cbOutBuffer, (pExtTable + 1), cbInBuffer, MPQ_COMPRESSION_ZLIB, 0, 0);
+
+ // If the compression failed, revert it. Otherwise, swap the tables
+ if(cbOutBuffer >= cbInBuffer)
+ {
+ STORM_FREE(pCompressed);
+ pCompressed = NULL;
+ }
+ else
+ {
+ pExtTable = pCompressed;
+ }
+ }
+
+ // Encrypt the table
+ if(dwKey != 0)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+ EncryptMpqBlock(pExtTable + 1, (DWORD)(dwTableSize - sizeof(TMPQExtHeader)), dwKey);
+ BSWAP_ARRAY32_UNSIGNED(pExtTable + 1, pExtTable->dwDataSize);
+ }
+
+ // Calculate the MD5 of the table after
+ if(md5 != NULL)
+ {
+ CalculateDataBlockHash(pExtTable, dwTableSize, md5);
+ }
+
+ // Save the table to the MPQ
+ FileOffset = ha->MpqPos + ByteOffset;
+ if(FileStream_Write(ha->pStream, &FileOffset, pExtTable, dwTableSize))
+ cbTotalSize += dwTableSize;
+ else
+ nError = GetLastError();
+
+ // We have to write raw data MD5
+ if(nError == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0)
+ {
+ nError = WriteMemDataMD5(ha->pStream,
+ FileOffset,
+ pExtTable,
+ dwTableSize,
+ ha->pHeader->dwRawChunkSize,
+ &cbTotalSize);
+ }
+
+ // Give the total written size, if needed
+ if(pcbTotalSize != NULL)
+ *pcbTotalSize = cbTotalSize;
+
+ // Free the compressed table, if any
+ if(pCompressed != NULL)
+ STORM_FREE(pCompressed);
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Support for HET table
+
+static void CreateHetHeader(
+ TMPQHetTable * pHetTable,
+ TMPQHetHeader * pHetHeader)
+{
+ // Fill the common header
+ pHetHeader->ExtHdr.dwSignature = HET_TABLE_SIGNATURE;
+ pHetHeader->ExtHdr.dwVersion = 1;
+ pHetHeader->ExtHdr.dwDataSize = 0;
+
+ // Fill the HET header
+ pHetHeader->dwEntryCount = pHetTable->dwEntryCount;
+ pHetHeader->dwTotalCount = pHetTable->dwTotalCount;
+ pHetHeader->dwNameHashBitSize = pHetTable->dwNameHashBitSize;
+ pHetHeader->dwIndexSizeTotal = pHetTable->dwIndexSizeTotal;
+ pHetHeader->dwIndexSizeExtra = pHetTable->dwIndexSizeExtra;
+ pHetHeader->dwIndexSize = pHetTable->dwIndexSize;
+ pHetHeader->dwIndexTableSize = ((pHetHeader->dwIndexSizeTotal * pHetTable->dwTotalCount) + 7) / 8;
+
+ // Calculate the total size needed for holding HET table
+ pHetHeader->ExtHdr.dwDataSize =
+ pHetHeader->dwTableSize = sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) +
+ pHetHeader->dwTotalCount +
+ pHetHeader->dwIndexTableSize;
+}
+
+TMPQHetTable * CreateHetTable(DWORD dwEntryCount, DWORD dwTotalCount, DWORD dwNameHashBitSize, LPBYTE pbSrcData)
+{
+ TMPQHetTable * pHetTable;
+
+ pHetTable = STORM_ALLOC(TMPQHetTable, 1);
+ if(pHetTable != NULL)
+ {
+ // Zero the HET table
+ memset(pHetTable, 0, sizeof(TMPQHetTable));
+
+ // Hash sizes less than 0x40 bits are not tested
+ assert(dwNameHashBitSize == 0x40);
+
+ // Calculate masks
+ pHetTable->AndMask64 = ((dwNameHashBitSize != 0x40) ? ((ULONGLONG)1 << dwNameHashBitSize) : 0) - 1;
+ pHetTable->OrMask64 = (ULONGLONG)1 << (dwNameHashBitSize - 1);
+
+ // If the total count is not entered, use default
+ if(dwTotalCount == 0)
+ dwTotalCount = (dwEntryCount * 4) / 3;
+
+ // Store the HET table parameters
+ pHetTable->dwEntryCount = dwEntryCount;
+ pHetTable->dwTotalCount = dwTotalCount;
+ pHetTable->dwNameHashBitSize = dwNameHashBitSize;
+ pHetTable->dwIndexSizeTotal = GetNecessaryBitCount(dwEntryCount);
+ pHetTable->dwIndexSizeExtra = 0;
+ pHetTable->dwIndexSize = pHetTable->dwIndexSizeTotal;
+
+ // Allocate array of hashes
+ pHetTable->pNameHashes = STORM_ALLOC(BYTE, dwTotalCount);
+ if(pHetTable->pNameHashes != NULL)
+ {
+ // Make sure the data are initialized
+ memset(pHetTable->pNameHashes, 0, dwTotalCount);
+
+ // Allocate the bit array for file indexes
+ pHetTable->pBetIndexes = CreateBitArray(dwTotalCount * pHetTable->dwIndexSizeTotal, 0xFF);
+ if(pHetTable->pBetIndexes != NULL)
+ {
+ // Initialize the HET table from the source data (if given)
+ if(pbSrcData != NULL)
+ {
+ // Copy the name hashes
+ memcpy(pHetTable->pNameHashes, pbSrcData, dwTotalCount);
+
+ // Copy the file indexes
+ memcpy(pHetTable->pBetIndexes->Elements, pbSrcData + dwTotalCount, pHetTable->pBetIndexes->NumberOfBytes);
+ }
+
+ // Return the result HET table
+ return pHetTable;
+ }
+
+ // Free the name hashes
+ STORM_FREE(pHetTable->pNameHashes);
+ }
+
+ STORM_FREE(pHetTable);
+ }
+
+ // Failed
+ return NULL;
+}
+
+static int InsertHetEntry(TMPQHetTable * pHetTable, ULONGLONG FileNameHash, DWORD dwFileIndex)
+{
+ DWORD StartIndex;
+ DWORD Index;
+ BYTE NameHash1;
+
+ // Get the start index and the high 8 bits of the name hash
+ StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwTotalCount);
+ NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8));
+
+ // Find a place where to put it
+ for(;;)
+ {
+ // Did we find a free HET entry?
+ if(pHetTable->pNameHashes[Index] == HET_ENTRY_FREE)
+ {
+ // Set the entry in the name hash table
+ pHetTable->pNameHashes[Index] = NameHash1;
+
+ // Set the entry in the file index table
+ SetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ 4);
+ return ERROR_SUCCESS;
+ }
+
+ // Move to the next entry in the HET table
+ // If we came to the start index again, we are done
+ Index = (Index + 1) % pHetTable->dwTotalCount;
+ if(Index == StartIndex)
+ break;
+ }
+
+ // No space in the HET table. Should never happen,
+ // because the HET table is created according to the number of files
+ assert(false);
+ return ERROR_DISK_FULL;
+}
+
+static TMPQHetTable * TranslateHetTable(TMPQHetHeader * pHetHeader)
+{
+ TMPQHetTable * pHetTable = NULL;
+ LPBYTE pbSrcData = (LPBYTE)(pHetHeader + 1);
+
+ // Sanity check
+ assert(pHetHeader->ExtHdr.dwSignature == HET_TABLE_SIGNATURE);
+ assert(pHetHeader->ExtHdr.dwVersion == 1);
+
+ // Verify size of the HET table
+ if(pHetHeader->ExtHdr.dwDataSize >= (sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader)))
+ {
+ // Verify the size of the table in the header
+ if(pHetHeader->dwTableSize == pHetHeader->ExtHdr.dwDataSize)
+ {
+ // The size of the HET table must be sum of header, hash and index table size
+ assert((sizeof(TMPQHetHeader) - sizeof(TMPQExtHeader) + pHetHeader->dwTotalCount + pHetHeader->dwIndexTableSize) == pHetHeader->dwTableSize);
+
+ // So far, all MPQs with HET Table have had total number of entries equal to 4/3 of file count
+ // Exception: "2010 - Starcraft II\!maps\Tya's Zerg Defense (unprotected).SC2Map"
+// assert(((pHetHeader->dwEntryCount * 4) / 3) == pHetHeader->dwTotalCount);
+
+ // The size of one index is predictable as well
+ assert(GetNecessaryBitCount(pHetHeader->dwEntryCount) == pHetHeader->dwIndexSizeTotal);
+
+ // The size of index table (in entries) is expected
+ // to be the same like the hash table size (in bytes)
+ assert(((pHetHeader->dwTotalCount * pHetHeader->dwIndexSizeTotal) + 7) / 8 == pHetHeader->dwIndexTableSize);
+
+ // Create translated table
+ pHetTable = CreateHetTable(pHetHeader->dwEntryCount, pHetHeader->dwTotalCount, pHetHeader->dwNameHashBitSize, pbSrcData);
+ if(pHetTable != NULL)
+ {
+ // Now the sizes in the hash table should be already set
+ assert(pHetTable->dwEntryCount == pHetHeader->dwEntryCount);
+ assert(pHetTable->dwTotalCount == pHetHeader->dwTotalCount);
+ assert(pHetTable->dwIndexSizeTotal == pHetHeader->dwIndexSizeTotal);
+
+ // Copy the missing variables
+ pHetTable->dwIndexSizeExtra = pHetHeader->dwIndexSizeExtra;
+ pHetTable->dwIndexSize = pHetHeader->dwIndexSize;
+ }
+ }
+ }
+
+ return pHetTable;
+}
+
+static TMPQExtHeader * TranslateHetTable(TMPQHetTable * pHetTable, ULONGLONG * pcbHetTable)
+{
+ TMPQHetHeader * pHetHeader = NULL;
+ TMPQHetHeader HetHeader;
+ LPBYTE pbLinearTable = NULL;
+ LPBYTE pbTrgData;
+
+ // Prepare header of the HET table
+ CreateHetHeader(pHetTable, &HetHeader);
+
+ // Allocate space for the linear table
+ pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + HetHeader.dwTableSize);
+ if(pbLinearTable != NULL)
+ {
+ // Copy the table header
+ pHetHeader = (TMPQHetHeader *)pbLinearTable;
+ memcpy(pHetHeader, &HetHeader, sizeof(TMPQHetHeader));
+ pbTrgData = (LPBYTE)(pHetHeader + 1);
+
+ // Copy the array of name hashes
+ memcpy(pbTrgData, pHetTable->pNameHashes, pHetTable->dwTotalCount);
+ pbTrgData += pHetTable->dwTotalCount;
+
+ // Copy the bit array of BET indexes
+ memcpy(pbTrgData, pHetTable->pBetIndexes->Elements, HetHeader.dwIndexTableSize);
+
+ // Calculate the total size of the table, including the TMPQExtHeader
+ if(pcbHetTable != NULL)
+ {
+ *pcbHetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + HetHeader.dwTableSize);
+ }
+ }
+
+ // Keep Coverity happy
+ assert((TMPQExtHeader *)&pHetHeader->ExtHdr == (TMPQExtHeader *)pbLinearTable);
+ return (TMPQExtHeader *)pbLinearTable;
+}
+
+static DWORD GetFileIndex_Het(TMPQArchive * ha, const char * szFileName)
+{
+ TMPQHetTable * pHetTable = ha->pHetTable;
+ ULONGLONG FileNameHash;
+ DWORD StartIndex;
+ DWORD Index;
+ BYTE NameHash1; // Upper 8 bits of the masked file name hash
+
+ // If there are no entries in the HET table, do nothing
+ if(pHetTable->dwEntryCount == 0)
+ return HASH_ENTRY_FREE;
+
+ // Do nothing if the MPQ has no HET table
+ assert(ha->pHetTable != NULL);
+
+ // Calculate 64-bit hash of the file name
+ FileNameHash = (HashStringJenkins(szFileName) & pHetTable->AndMask64) | pHetTable->OrMask64;
+
+ // Split the file name hash into two parts:
+ // NameHash1: The highest 8 bits of the name hash
+ // NameHash2: File name hash limited to hash size
+ // Note: Our file table contains full name hash, no need to cut the high 8 bits before comparison
+ NameHash1 = (BYTE)(FileNameHash >> (pHetTable->dwNameHashBitSize - 8));
+
+ // Calculate the starting index to the hash table
+ StartIndex = Index = (DWORD)(FileNameHash % pHetTable->dwTotalCount);
+
+ // Go through HET table until we find a terminator
+ while(pHetTable->pNameHashes[Index] != HET_ENTRY_FREE)
+ {
+ // Did we find a match ?
+ if(pHetTable->pNameHashes[Index] == NameHash1)
+ {
+ DWORD dwFileIndex = 0;
+
+ // Get the file index
+ GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * Index,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ sizeof(DWORD));
+
+ // Verify the FileNameHash against the entry in the table of name hashes
+ if(dwFileIndex <= ha->dwFileTableSize && ha->pFileTable[dwFileIndex].FileNameHash == FileNameHash)
+ {
+ return dwFileIndex;
+ }
+ }
+
+ // Move to the next entry in the HET table
+ // If we came to the start index again, we are done
+ Index = (Index + 1) % pHetTable->dwTotalCount;
+ if(Index == StartIndex)
+ break;
+ }
+
+ // File not found
+ return HASH_ENTRY_FREE;
+}
+
+void FreeHetTable(TMPQHetTable * pHetTable)
+{
+ if(pHetTable != NULL)
+ {
+ if(pHetTable->pNameHashes != NULL)
+ STORM_FREE(pHetTable->pNameHashes);
+ if(pHetTable->pBetIndexes != NULL)
+ STORM_FREE(pHetTable->pBetIndexes);
+
+ STORM_FREE(pHetTable);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Support for BET table
+
+static void CreateBetHeader(
+ TMPQArchive * ha,
+ TMPQBetHeader * pBetHeader)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ ULONGLONG MaxByteOffset = 0;
+ DWORD FlagArray[MAX_FLAG_INDEX];
+ DWORD dwMaxFlagIndex = 0;
+ DWORD dwMaxFileSize = 0;
+ DWORD dwMaxCmpSize = 0;
+ DWORD dwFlagIndex;
+
+ // Initialize array of flag combinations
+ InitFileFlagArray(FlagArray);
+
+ // Fill the common header
+ pBetHeader->ExtHdr.dwSignature = BET_TABLE_SIGNATURE;
+ pBetHeader->ExtHdr.dwVersion = 1;
+ pBetHeader->ExtHdr.dwDataSize = 0;
+
+ // Get the maximum values for the BET table
+ pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize;
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ //
+ // Note: Deleted files must be counted as well
+ //
+
+ // Highest file position in the MPQ
+ if(pFileEntry->ByteOffset > MaxByteOffset)
+ MaxByteOffset = pFileEntry->ByteOffset;
+
+ // Biggest file size
+ if(pFileEntry->dwFileSize > dwMaxFileSize)
+ dwMaxFileSize = pFileEntry->dwFileSize;
+
+ // Biggest compressed size
+ if(pFileEntry->dwCmpSize > dwMaxCmpSize)
+ dwMaxCmpSize = pFileEntry->dwCmpSize;
+
+ // Check if this flag was there before
+ dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags);
+ if(dwFlagIndex > dwMaxFlagIndex)
+ dwMaxFlagIndex = dwFlagIndex;
+ }
+
+ // Now save bit count for every piece of file information
+ pBetHeader->dwBitIndex_FilePos = 0;
+ pBetHeader->dwBitCount_FilePos = GetNecessaryBitCount(MaxByteOffset);
+
+ pBetHeader->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FilePos + pBetHeader->dwBitCount_FilePos;
+ pBetHeader->dwBitCount_FileSize = GetNecessaryBitCount(dwMaxFileSize);
+
+ pBetHeader->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_FileSize + pBetHeader->dwBitCount_FileSize;
+ pBetHeader->dwBitCount_CmpSize = GetNecessaryBitCount(dwMaxCmpSize);
+
+ pBetHeader->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_CmpSize + pBetHeader->dwBitCount_CmpSize;
+ pBetHeader->dwBitCount_FlagIndex = GetNecessaryBitCount(dwMaxFlagIndex + 1);
+
+ pBetHeader->dwBitIndex_Unknown = pBetHeader->dwBitIndex_FlagIndex + pBetHeader->dwBitCount_FlagIndex;
+ pBetHeader->dwBitCount_Unknown = 0;
+
+ // Calculate the total size of one entry
+ pBetHeader->dwTableEntrySize = pBetHeader->dwBitCount_FilePos +
+ pBetHeader->dwBitCount_FileSize +
+ pBetHeader->dwBitCount_CmpSize +
+ pBetHeader->dwBitCount_FlagIndex +
+ pBetHeader->dwBitCount_Unknown;
+
+ // Save the file count and flag count
+ pBetHeader->dwEntryCount = ha->pHeader->dwBlockTableSize;
+ pBetHeader->dwFlagCount = dwMaxFlagIndex + 1;
+ pBetHeader->dwUnknown08 = 0x10;
+
+ // Save the total size of the BET hash
+ pBetHeader->dwBitTotal_NameHash2 = ha->pHetTable->dwNameHashBitSize - 0x08;
+ pBetHeader->dwBitExtra_NameHash2 = 0;
+ pBetHeader->dwBitCount_NameHash2 = pBetHeader->dwBitTotal_NameHash2;
+ pBetHeader->dwNameHashArraySize = ((pBetHeader->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount) + 7) / 8;
+
+ // Save the total table size
+ pBetHeader->ExtHdr.dwDataSize =
+ pBetHeader->dwTableSize = sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader) +
+ pBetHeader->dwFlagCount * sizeof(DWORD) +
+ ((pBetHeader->dwTableEntrySize * pBetHeader->dwEntryCount) + 7) / 8 +
+ pBetHeader->dwNameHashArraySize;
+}
+
+TMPQBetTable * CreateBetTable(DWORD dwEntryCount)
+{
+ TMPQBetTable * pBetTable;
+
+ // Allocate BET table
+ pBetTable = STORM_ALLOC(TMPQBetTable, 1);
+ if(pBetTable != NULL)
+ {
+ memset(pBetTable, 0, sizeof(TMPQBetTable));
+ pBetTable->dwEntryCount = dwEntryCount;
+ }
+
+ return pBetTable;
+}
+
+static TMPQBetTable * TranslateBetTable(
+ TMPQArchive * ha,
+ TMPQBetHeader * pBetHeader)
+{
+ TMPQBetTable * pBetTable = NULL;
+ LPBYTE pbSrcData = (LPBYTE)(pBetHeader + 1);
+ DWORD LengthInBytes = 0;
+
+ // Sanity check
+ assert(pBetHeader->ExtHdr.dwSignature == BET_TABLE_SIGNATURE);
+ assert(pBetHeader->ExtHdr.dwVersion == 1);
+ assert(ha->pHetTable != NULL);
+ ha = ha;
+
+ // Verify size of the HET table
+ if(pBetHeader->ExtHdr.dwDataSize >= (sizeof(TMPQBetHeader) - sizeof(TMPQExtHeader)))
+ {
+ // Verify the size of the table in the header
+ if(pBetHeader->dwTableSize == pBetHeader->ExtHdr.dwDataSize)
+ {
+ // The number of entries in the BET table must be the same like number of entries in the block table
+ assert(pBetHeader->dwEntryCount == ha->pHeader->dwBlockTableSize);
+ assert(pBetHeader->dwEntryCount <= ha->dwMaxFileCount);
+
+ // The number of entries in the BET table must be the same like number of entries in the HET table
+ // Note that if it's not, it is not a problem
+ //assert(pBetHeader->dwEntryCount == ha->pHetTable->dwEntryCount);
+
+ // Create translated table
+ pBetTable = CreateBetTable(pBetHeader->dwEntryCount);
+ if(pBetTable != NULL)
+ {
+ // Copy the variables from the header to the BetTable
+ pBetTable->dwTableEntrySize = pBetHeader->dwTableEntrySize;
+ pBetTable->dwBitIndex_FilePos = pBetHeader->dwBitIndex_FilePos;
+ pBetTable->dwBitIndex_FileSize = pBetHeader->dwBitIndex_FileSize;
+ pBetTable->dwBitIndex_CmpSize = pBetHeader->dwBitIndex_CmpSize;
+ pBetTable->dwBitIndex_FlagIndex = pBetHeader->dwBitIndex_FlagIndex;
+ pBetTable->dwBitIndex_Unknown = pBetHeader->dwBitIndex_Unknown;
+ pBetTable->dwBitCount_FilePos = pBetHeader->dwBitCount_FilePos;
+ pBetTable->dwBitCount_FileSize = pBetHeader->dwBitCount_FileSize;
+ pBetTable->dwBitCount_CmpSize = pBetHeader->dwBitCount_CmpSize;
+ pBetTable->dwBitCount_FlagIndex = pBetHeader->dwBitCount_FlagIndex;
+ pBetTable->dwBitCount_Unknown = pBetHeader->dwBitCount_Unknown;
+
+ // Since we don't know what the "unknown" is, we'll assert when it's zero
+ assert(pBetTable->dwBitCount_Unknown == 0);
+
+ // Allocate array for flags
+ if(pBetHeader->dwFlagCount != 0)
+ {
+ // Allocate array for file flags and load it
+ pBetTable->pFileFlags = STORM_ALLOC(DWORD, pBetHeader->dwFlagCount);
+ if(pBetTable->pFileFlags != NULL)
+ {
+ LengthInBytes = pBetHeader->dwFlagCount * sizeof(DWORD);
+ memcpy(pBetTable->pFileFlags, pbSrcData, LengthInBytes);
+ BSWAP_ARRAY32_UNSIGNED(pBetTable->pFileFlags, LengthInBytes);
+ pbSrcData += LengthInBytes;
+ }
+
+ // Save the number of flags
+ pBetTable->dwFlagCount = pBetHeader->dwFlagCount;
+ }
+
+ // Load the bit-based file table
+ pBetTable->pFileTable = CreateBitArray(pBetTable->dwTableEntrySize * pBetHeader->dwEntryCount, 0);
+ if(pBetTable->pFileTable != NULL)
+ {
+ LengthInBytes = (pBetTable->pFileTable->NumberOfBits + 7) / 8;
+ memcpy(pBetTable->pFileTable->Elements, pbSrcData, LengthInBytes);
+ pbSrcData += LengthInBytes;
+ }
+
+ // Fill the sizes of BET hash
+ pBetTable->dwBitTotal_NameHash2 = pBetHeader->dwBitTotal_NameHash2;
+ pBetTable->dwBitExtra_NameHash2 = pBetHeader->dwBitExtra_NameHash2;
+ pBetTable->dwBitCount_NameHash2 = pBetHeader->dwBitCount_NameHash2;
+
+ // Create and load the array of BET hashes
+ pBetTable->pNameHashes = CreateBitArray(pBetTable->dwBitTotal_NameHash2 * pBetHeader->dwEntryCount, 0);
+ if(pBetTable->pNameHashes != NULL)
+ {
+ LengthInBytes = (pBetTable->pNameHashes->NumberOfBits + 7) / 8;
+ memcpy(pBetTable->pNameHashes->Elements, pbSrcData, LengthInBytes);
+// pbSrcData += LengthInBytes;
+ }
+
+ // Dump both tables
+// DumpHetAndBetTable(ha->pHetTable, pBetTable);
+ }
+ }
+ }
+
+ return pBetTable;
+}
+
+TMPQExtHeader * TranslateBetTable(
+ TMPQArchive * ha,
+ ULONGLONG * pcbBetTable)
+{
+ TMPQBetHeader * pBetHeader = NULL;
+ TMPQBetHeader BetHeader;
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ TBitArray * pBitArray = NULL;
+ LPBYTE pbLinearTable = NULL;
+ LPBYTE pbTrgData;
+ DWORD LengthInBytes;
+ DWORD FlagArray[MAX_FLAG_INDEX];
+
+ // Calculate the bit sizes of various entries
+ InitFileFlagArray(FlagArray);
+ CreateBetHeader(ha, &BetHeader);
+
+ // Allocate space
+ pbLinearTable = STORM_ALLOC(BYTE, sizeof(TMPQExtHeader) + BetHeader.dwTableSize);
+ if(pbLinearTable != NULL)
+ {
+ // Copy the BET header to the linear buffer
+ pBetHeader = (TMPQBetHeader *)pbLinearTable;
+ memcpy(pBetHeader, &BetHeader, sizeof(TMPQBetHeader));
+ pbTrgData = (LPBYTE)(pBetHeader + 1);
+
+ // Save the bit-based block table
+ pBitArray = CreateBitArray(BetHeader.dwEntryCount * BetHeader.dwTableEntrySize, 0);
+ if(pBitArray != NULL)
+ {
+ DWORD dwFlagIndex = 0;
+ DWORD nBitOffset = 0;
+
+ // Construct the bit-based file table
+ pFileTableEnd = ha->pFileTable + BetHeader.dwEntryCount;
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ //
+ // Note: Missing files must be included as well
+ //
+
+ // Save the byte offset
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FilePos,
+ BetHeader.dwBitCount_FilePos,
+ &pFileEntry->ByteOffset,
+ 8);
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FileSize,
+ BetHeader.dwBitCount_FileSize,
+ &pFileEntry->dwFileSize,
+ 4);
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_CmpSize,
+ BetHeader.dwBitCount_CmpSize,
+ &pFileEntry->dwCmpSize,
+ 4);
+
+ // Save the flag index
+ dwFlagIndex = GetFileFlagIndex(FlagArray, pFileEntry->dwFlags);
+ SetBits(pBitArray, nBitOffset + BetHeader.dwBitIndex_FlagIndex,
+ BetHeader.dwBitCount_FlagIndex,
+ &dwFlagIndex,
+ 4);
+
+ // Move the bit offset
+ nBitOffset += BetHeader.dwTableEntrySize;
+ }
+
+ // Write the array of flags
+ LengthInBytes = BetHeader.dwFlagCount * sizeof(DWORD);
+ memcpy(pbTrgData, FlagArray, LengthInBytes);
+ BSWAP_ARRAY32_UNSIGNED(pbTrgData, LengthInBytes);
+ pbTrgData += LengthInBytes;
+
+ // Write the bit-based block table
+ LengthInBytes = (pBitArray->NumberOfBits + 7) / 8;
+ memcpy(pbTrgData, pBitArray->Elements, LengthInBytes);
+ pbTrgData += LengthInBytes;
+
+ // Free the bit array
+ STORM_FREE(pBitArray);
+ }
+
+ // Create bit array for name hashes
+ pBitArray = CreateBitArray(BetHeader.dwBitTotal_NameHash2 * BetHeader.dwEntryCount, 0);
+ if(pBitArray != NULL)
+ {
+ DWORD dwFileIndex = 0;
+
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Insert the name hash to the bit array
+ SetBits(pBitArray, BetHeader.dwBitTotal_NameHash2 * dwFileIndex,
+ BetHeader.dwBitCount_NameHash2,
+ &pFileEntry->FileNameHash,
+ 8);
+
+ assert(dwFileIndex < BetHeader.dwEntryCount);
+ dwFileIndex++;
+ }
+
+ // Write the array of BET hashes
+ LengthInBytes = (pBitArray->NumberOfBits + 7) / 8;
+ memcpy(pbTrgData, pBitArray->Elements, LengthInBytes);
+// pbTrgData += LengthInBytes;
+
+ // Free the bit array
+ STORM_FREE(pBitArray);
+ }
+
+ // Write the size of the BET table in the MPQ
+ if(pcbBetTable != NULL)
+ {
+ *pcbBetTable = (ULONGLONG)(sizeof(TMPQExtHeader) + BetHeader.dwTableSize);
+ }
+ }
+
+ // Keep Coverity happy
+ assert((TMPQExtHeader *)&pBetHeader->ExtHdr == (TMPQExtHeader *)pbLinearTable);
+ return (TMPQExtHeader *)pbLinearTable;
+}
+
+void FreeBetTable(TMPQBetTable * pBetTable)
+{
+ if(pBetTable != NULL)
+ {
+ if(pBetTable->pFileTable != NULL)
+ STORM_FREE(pBetTable->pFileTable);
+ if(pBetTable->pFileFlags != NULL)
+ STORM_FREE(pBetTable->pFileFlags);
+ if(pBetTable->pNameHashes != NULL)
+ STORM_FREE(pBetTable->pNameHashes);
+
+ STORM_FREE(pBetTable);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Support for file table
+
+TFileEntry * GetFileEntryLocale2(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex)
+{
+ TMPQHash * pHash;
+ DWORD dwFileIndex;
+
+ // First, we have to search the classic hash table
+ // This is because on renaming, deleting, or changing locale,
+ // we will need the pointer to hash table entry
+ if(ha->pHashTable != NULL)
+ {
+ pHash = GetHashEntryLocale(ha, szFileName, lcLocale, 0);
+ if(pHash != NULL && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
+ {
+ if(PtrHashIndex != NULL)
+ PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable);
+ return ha->pFileTable + MPQ_BLOCK_INDEX(pHash);
+ }
+ }
+
+ // If we have HET table in the MPQ, try to find the file in HET table
+ if(ha->pHetTable != NULL)
+ {
+ dwFileIndex = GetFileIndex_Het(ha, szFileName);
+ if(dwFileIndex != HASH_ENTRY_FREE)
+ return ha->pFileTable + dwFileIndex;
+ }
+
+ // Not found
+ return NULL;
+}
+
+TFileEntry * GetFileEntryLocale(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
+{
+ return GetFileEntryLocale2(ha, szFileName, lcLocale, NULL);
+}
+
+TFileEntry * GetFileEntryExact(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex)
+{
+ TMPQHash * pHash;
+ DWORD dwFileIndex;
+
+ // If the hash table is present, find the entry from hash table
+ if(ha->pHashTable != NULL)
+ {
+ pHash = GetHashEntryExact(ha, szFileName, lcLocale);
+ if(pHash != NULL && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
+ {
+ if(PtrHashIndex != NULL)
+ PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable);
+ return ha->pFileTable + MPQ_BLOCK_INDEX(pHash);
+ }
+ }
+
+ // If we have HET table in the MPQ, try to find the file in HET table
+ if(ha->pHetTable != NULL)
+ {
+ dwFileIndex = GetFileIndex_Het(ha, szFileName);
+ if(dwFileIndex != HASH_ENTRY_FREE)
+ {
+ if(PtrHashIndex != NULL)
+ PtrHashIndex[0] = HASH_ENTRY_FREE;
+ return ha->pFileTable + dwFileIndex;
+ }
+ }
+
+ // Not found
+ return NULL;
+}
+
+void AllocateFileName(TMPQArchive * ha, TFileEntry * pFileEntry, const char * szFileName)
+{
+ // Sanity check
+ assert(pFileEntry != NULL);
+
+ // If the file name is pseudo file name, free it at this point
+ if(IsPseudoFileName(pFileEntry->szFileName, NULL))
+ {
+ if(pFileEntry->szFileName != NULL)
+ STORM_FREE(pFileEntry->szFileName);
+ pFileEntry->szFileName = NULL;
+ }
+
+ // Only allocate new file name if it's not there yet
+ if(pFileEntry->szFileName == NULL)
+ {
+ pFileEntry->szFileName = STORM_ALLOC(char, strlen(szFileName) + 1);
+ if(pFileEntry->szFileName != NULL)
+ strcpy(pFileEntry->szFileName, szFileName);
+ }
+
+ // We also need to create the file name hash
+ if(ha->pHetTable != NULL)
+ {
+ ULONGLONG AndMask64 = ha->pHetTable->AndMask64;
+ ULONGLONG OrMask64 = ha->pHetTable->OrMask64;
+
+ pFileEntry->FileNameHash = (HashStringJenkins(szFileName) & AndMask64) | OrMask64;
+ }
+}
+
+TFileEntry * AllocateFileEntry(TMPQArchive * ha, const char * szFileName, LCID lcLocale, LPDWORD PtrHashIndex)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFreeEntry = NULL;
+ TFileEntry * pFileEntry;
+ TMPQHash * pHash = NULL;
+ DWORD dwReservedFiles = ha->dwReservedFiles;
+ DWORD dwFreeCount = 0;
+
+ // Sanity check: File table size must be greater or equal to max file count
+ assert(ha->dwFileTableSize >= ha->dwMaxFileCount);
+
+ // If we are saving MPQ tables, we don't tale number of reserved files into account
+ dwReservedFiles = (ha->dwFlags & MPQ_FLAG_SAVING_TABLES) ? 0 : ha->dwReservedFiles;
+
+ // Now find a free entry in the file table.
+ // Note that in the case when free entries are in the middle,
+ // we need to use these
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
+ {
+ // Remember the first free entry
+ if(pFreeEntry == NULL)
+ pFreeEntry = pFileEntry;
+ dwFreeCount++;
+
+ // If the number of free items is greater than number
+ // of reserved items, We can add the file
+ if(dwFreeCount > dwReservedFiles)
+ break;
+ }
+ }
+
+ // If the total number of free entries is less than number of reserved files,
+ // we cannot add the file to the archive
+ if(pFreeEntry == NULL || dwFreeCount <= dwReservedFiles)
+ return NULL;
+
+ // Initialize the file entry and set its file name
+ memset(pFreeEntry, 0, sizeof(TFileEntry));
+ AllocateFileName(ha, pFreeEntry, szFileName);
+
+ // If the archive has a hash table, we need to first free entry there
+ if(ha->pHashTable != NULL)
+ {
+ // Make sure that the entry is not there yet
+ assert(GetHashEntryExact(ha, szFileName, lcLocale) == NULL);
+
+ // Find a free hash table entry for the name
+ pHash = AllocateHashEntry(ha, pFreeEntry, lcLocale);
+ if(pHash == NULL)
+ return NULL;
+
+ // Set the file index to the hash table
+ pHash->dwBlockIndex = (DWORD)(pFreeEntry - ha->pFileTable);
+ PtrHashIndex[0] = (DWORD)(pHash - ha->pHashTable);
+ }
+
+ // If the archive has a HET table, just do some checks
+ // Note: Don't bother modifying the HET table. It will be rebuilt from scratch after, anyway
+ if(ha->pHetTable != NULL)
+ {
+ assert(GetFileIndex_Het(ha, szFileName) == HASH_ENTRY_FREE);
+ }
+
+ // Return the free table entry
+ return pFreeEntry;
+}
+
+int RenameFileEntry(
+ TMPQArchive * ha,
+ TMPQFile * hf,
+ const char * szNewFileName)
+{
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ TMPQHash * pHashEntry = hf->pHashEntry;
+ LCID lcLocale = 0;
+
+ // If the archive hash hash table, we need to free the hash table entry
+ if(ha->pHashTable != NULL)
+ {
+ // The file must have hash table entry assigned
+ // Will exit if there are multiple HASH entries pointing to the same file entry
+ if(pHashEntry == NULL)
+ return ERROR_NOT_SUPPORTED;
+
+ // Save the locale
+ lcLocale = pHashEntry->lcLocale;
+
+ // Mark the hash table entry as deleted
+ pHashEntry->dwName1 = 0xFFFFFFFF;
+ pHashEntry->dwName2 = 0xFFFFFFFF;
+ pHashEntry->lcLocale = 0xFFFF;
+ pHashEntry->Platform = 0xFF;
+ pHashEntry->Reserved = 0xFF;
+ pHashEntry->dwBlockIndex = HASH_ENTRY_DELETED;
+ }
+
+ // Free the old file name
+ if(pFileEntry->szFileName != NULL)
+ STORM_FREE(pFileEntry->szFileName);
+ pFileEntry->szFileName = NULL;
+
+ // Allocate new file name
+ AllocateFileName(ha, pFileEntry, szNewFileName);
+
+ // Allocate new hash entry
+ if(ha->pHashTable != NULL)
+ {
+ // Since we freed one hash entry before, this must succeed
+ hf->pHashEntry = AllocateHashEntry(ha, pFileEntry, lcLocale);
+ assert(hf->pHashEntry != NULL);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+int DeleteFileEntry(TMPQArchive * ha, TMPQFile * hf)
+{
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ TMPQHash * pHashEntry = hf->pHashEntry;
+
+ // If the archive hash hash table, we need to free the hash table entry
+ if(ha->pHashTable != NULL)
+ {
+ // The file must have hash table entry assigned
+ // Will exit if there are multiple HASH entries pointing to the same file entry
+ if(pHashEntry == NULL)
+ return ERROR_NOT_SUPPORTED;
+
+ // Mark the hash table entry as deleted
+ pHashEntry->dwName1 = 0xFFFFFFFF;
+ pHashEntry->dwName2 = 0xFFFFFFFF;
+ pHashEntry->lcLocale = 0xFFFF;
+ pHashEntry->Platform = 0xFF;
+ pHashEntry->Reserved = 0xFF;
+ pHashEntry->dwBlockIndex = HASH_ENTRY_DELETED;
+ }
+
+ // Free the file name, and set the file entry as deleted
+ if(pFileEntry->szFileName != NULL)
+ STORM_FREE(pFileEntry->szFileName);
+ pFileEntry->szFileName = NULL;
+
+ //
+ // Don't modify the HET table, because it gets recreated by the caller
+ // Don't decrement the number of entries in the file table
+ // Keep Byte Offset, file size, compressed size, CRC32 and MD5
+ // Clear the file name hash and the MPQ_FILE_EXISTS bit
+ //
+
+ pFileEntry->dwFlags &= ~MPQ_FILE_EXISTS;
+ pFileEntry->FileNameHash = 0;
+ return ERROR_SUCCESS;
+}
+
+DWORD InvalidateInternalFile(TMPQArchive * ha, const char * szFileName, DWORD dwFlagNone, DWORD dwFlagNew, DWORD dwForceAddTheFile = 0)
+{
+ TMPQFile * hf = NULL;
+ DWORD dwFileFlags = MPQ_FILE_DEFAULT_INTERNAL;
+ int nError = ERROR_FILE_NOT_FOUND;
+
+ // Open the file from the MPQ
+ if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
+ {
+ // Remember the file flags
+ dwFileFlags = hf->pFileEntry->dwFlags;
+
+ // Delete the file entry
+ nError = DeleteFileEntry(ha, hf);
+ if(nError == ERROR_SUCCESS)
+ dwForceAddTheFile = 1;
+
+ // Close the file
+ FreeFileHandle(hf);
+ }
+
+ // Are we going to add the file?
+ if(dwForceAddTheFile)
+ {
+ ha->dwFlags |= dwFlagNew;
+ ha->dwReservedFiles++;
+ }
+ else
+ {
+ ha->dwFlags |= dwFlagNone;
+ dwFileFlags = 0;
+ }
+
+ // Return the intended file flags
+ return dwFileFlags;
+}
+
+void InvalidateInternalFiles(TMPQArchive * ha)
+{
+ // Do nothing if we are in the middle of saving internal files
+ if(!(ha->dwFlags & MPQ_FLAG_SAVING_TABLES))
+ {
+ //
+ // We clear the file entries for (listfile), (attributes) and (signature)
+ // For each internal file cleared, we increment the number
+ // of reserved entries in the file table.
+ //
+
+ // Invalidate the (listfile), if not done yet
+ if((ha->dwFlags & (MPQ_FLAG_LISTFILE_NONE | MPQ_FLAG_LISTFILE_NEW)) == 0)
+ {
+ ha->dwFileFlags1 = InvalidateInternalFile(ha, LISTFILE_NAME, MPQ_FLAG_LISTFILE_NONE, MPQ_FLAG_LISTFILE_NEW, (ha->dwFlags & MPQ_FLAG_LISTFILE_FORCE));
+ }
+
+ // Invalidate the (attributes), if not done yet
+ if((ha->dwFlags & (MPQ_FLAG_ATTRIBUTES_NONE | MPQ_FLAG_ATTRIBUTES_NEW)) == 0)
+ {
+ ha->dwFileFlags2 = InvalidateInternalFile(ha, ATTRIBUTES_NAME, MPQ_FLAG_ATTRIBUTES_NONE, MPQ_FLAG_ATTRIBUTES_NEW);
+ }
+
+ // Invalidate the (signature), if not done yet
+ if((ha->dwFlags & (MPQ_FLAG_SIGNATURE_NONE | MPQ_FLAG_SIGNATURE_NEW)) == 0)
+ {
+ ha->dwFileFlags3 = InvalidateInternalFile(ha, SIGNATURE_NAME, MPQ_FLAG_SIGNATURE_NONE, MPQ_FLAG_SIGNATURE_NEW);
+ }
+
+ // Remember that the MPQ has been changed
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Support for file tables - hash table, block table, hi-block table
+
+int CreateHashTable(TMPQArchive * ha, DWORD dwHashTableSize)
+{
+ TMPQHash * pHashTable;
+
+ // Sanity checks
+ assert((dwHashTableSize & (dwHashTableSize - 1)) == 0);
+ assert(ha->pHashTable == NULL);
+
+ // If the required hash table size is zero, don't create anything
+ if(dwHashTableSize == 0)
+ dwHashTableSize = HASH_TABLE_SIZE_DEFAULT;
+
+ // Create the hash table
+ pHashTable = STORM_ALLOC(TMPQHash, dwHashTableSize);
+ if(pHashTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill it
+ memset(pHashTable, 0xFF, dwHashTableSize * sizeof(TMPQHash));
+ ha->pHeader->dwHashTableSize = dwHashTableSize;
+ ha->dwMaxFileCount = dwHashTableSize;
+ ha->pHashTable = pHashTable;
+ return ERROR_SUCCESS;
+}
+
+static TMPQHash * LoadHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ ULONGLONG ByteOffset;
+ TMPQHash * pHashTable = NULL;
+ DWORD dwTableSize;
+ DWORD dwCmpSize;
+ bool bHashTableIsCut = false;
+
+ // Note: It is allowed to load hash table if it is at offset 0.
+ // Example: MPQ_2016_v1_ProtectedMap_HashOffsIsZero.w3x
+// if(pHeader->dwHashTablePos == 0 && pHeader->wHashTablePosHi == 0)
+// return NULL;
+
+ // If the hash table size is zero, do nothing
+ if(pHeader->dwHashTableSize == 0)
+ return NULL;
+
+ // Load the hash table for MPQ variations
+ switch(ha->dwSubType)
+ {
+ case MPQ_SUBTYPE_MPQ:
+
+ // Calculate the position and size of the hash table
+ ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos));
+ dwTableSize = pHeader->dwHashTableSize * sizeof(TMPQHash);
+ dwCmpSize = (DWORD)pHeader->HashTableSize64;
+
+ // Read, decrypt and uncompress the hash table
+ pHashTable = (TMPQHash *)LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_HASH_TABLE, &bHashTableIsCut);
+// DumpHashTable(pHashTable, pHeader->dwHashTableSize);
+
+ // If the hash table was cut, we can/have to defragment it
+ if(pHashTable != NULL && bHashTableIsCut)
+ ha->dwFlags |= (MPQ_FLAG_MALFORMED | MPQ_FLAG_HASH_TABLE_CUT);
+ break;
+
+ case MPQ_SUBTYPE_SQP:
+ pHashTable = LoadSqpHashTable(ha);
+ break;
+
+ case MPQ_SUBTYPE_MPK:
+ pHashTable = LoadMpkHashTable(ha);
+ break;
+ }
+
+ // Remember the size of the hash table
+ return pHashTable;
+}
+
+int CreateFileTable(TMPQArchive * ha, DWORD dwFileTableSize)
+{
+ ha->pFileTable = STORM_ALLOC(TFileEntry, dwFileTableSize);
+ if(ha->pFileTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ memset(ha->pFileTable, 0x00, sizeof(TFileEntry) * dwFileTableSize);
+ ha->dwFileTableSize = dwFileTableSize;
+ return ERROR_SUCCESS;
+}
+
+TMPQBlock * LoadBlockTable(TMPQArchive * ha, bool /* bDontFixEntries */)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlockTable = NULL;
+ ULONGLONG ByteOffset;
+ DWORD dwTableSize;
+ DWORD dwCmpSize;
+ bool bBlockTableIsCut = false;
+
+ // Note: It is possible that the block table starts at offset 0
+ // Example: MPQ_2016_v1_ProtectedMap_HashOffsIsZero.w3x
+// if(pHeader->dwBlockTablePos == 0 && pHeader->wBlockTablePosHi == 0)
+// return NULL;
+
+ // Do nothing if the block table size is zero
+ if(pHeader->dwBlockTableSize == 0)
+ return NULL;
+
+ // Load the block table for MPQ variations
+ switch(ha->dwSubType)
+ {
+ case MPQ_SUBTYPE_MPQ:
+
+ // Calculate byte position of the block table
+ ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos));
+ dwTableSize = pHeader->dwBlockTableSize * sizeof(TMPQBlock);
+ dwCmpSize = (DWORD)pHeader->BlockTableSize64;
+
+ // Read, decrypt and uncompress the block table
+ pBlockTable = (TMPQBlock * )LoadMpqTable(ha, ByteOffset, dwCmpSize, dwTableSize, MPQ_KEY_BLOCK_TABLE, &bBlockTableIsCut);
+
+ // If the block table was cut, we need to remember it
+ if(pBlockTable != NULL && bBlockTableIsCut)
+ ha->dwFlags |= (MPQ_FLAG_MALFORMED | MPQ_FLAG_BLOCK_TABLE_CUT);
+ break;
+
+ case MPQ_SUBTYPE_SQP:
+
+ pBlockTable = LoadSqpBlockTable(ha);
+ break;
+
+ case MPQ_SUBTYPE_MPK:
+
+ pBlockTable = LoadMpkBlockTable(ha);
+ break;
+ }
+
+ return pBlockTable;
+}
+
+TMPQHetTable * LoadHetTable(TMPQArchive * ha)
+{
+ TMPQExtHeader * pExtTable;
+ TMPQHetTable * pHetTable = NULL;
+ TMPQHeader * pHeader = ha->pHeader;
+
+ // If the HET table position is not 0, we expect the table to be present
+ if(pHeader->HetTablePos64 != 0 && pHeader->HetTableSize64 != 0)
+ {
+ // Attempt to load the HET table (Hash Extended Table)
+ pExtTable = LoadExtTable(ha, pHeader->HetTablePos64, (size_t)pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
+ if(pExtTable != NULL)
+ {
+ // If loading HET table fails, we ignore the result.
+ pHetTable = TranslateHetTable((TMPQHetHeader *)pExtTable);
+ STORM_FREE(pExtTable);
+ }
+ }
+
+ return pHetTable;
+}
+
+TMPQBetTable * LoadBetTable(TMPQArchive * ha)
+{
+ TMPQExtHeader * pExtTable;
+ TMPQBetTable * pBetTable = NULL;
+ TMPQHeader * pHeader = ha->pHeader;
+
+ // If the BET table position is not 0, we expect the table to be present
+ if(pHeader->BetTablePos64 != 0 && pHeader->BetTableSize64 != 0)
+ {
+ // Attempt to load the HET table (Hash Extended Table)
+ pExtTable = LoadExtTable(ha, pHeader->BetTablePos64, (size_t)pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE);
+ if(pExtTable != NULL)
+ {
+ // If succeeded, we translate the BET table
+ // to more readable form
+ pBetTable = TranslateBetTable(ha, (TMPQBetHeader *)pExtTable);
+ STORM_FREE(pExtTable);
+ }
+ }
+
+ return pBetTable;
+}
+
+int LoadAnyHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+
+ // If the MPQ archive is empty, don't bother trying to load anything
+ if(pHeader->dwHashTableSize == 0 && pHeader->HetTableSize64 == 0)
+ return CreateHashTable(ha, HASH_TABLE_SIZE_DEFAULT);
+
+ // Try to load HET table
+ if(pHeader->HetTablePos64 != 0)
+ ha->pHetTable = LoadHetTable(ha);
+
+ // Try to load classic hash table
+ if(pHeader->dwHashTableSize)
+ ha->pHashTable = LoadHashTable(ha);
+
+ // At least one of the tables must be present
+ if(ha->pHetTable == NULL && ha->pHashTable == NULL)
+ return ERROR_FILE_CORRUPT;
+
+ // Set the maximum file count to the size of the hash table.
+ // Note: We don't care about HET table limits, because HET table is rebuilt
+ // after each file add/rename/delete.
+ ha->dwMaxFileCount = (ha->pHashTable != NULL) ? pHeader->dwHashTableSize : HASH_TABLE_SIZE_MAX;
+ return ERROR_SUCCESS;
+}
+
+static int BuildFileTable_Classic(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlockTable;
+ int nError = ERROR_SUCCESS;
+
+ // Sanity checks
+ assert(ha->pHashTable != NULL);
+ assert(ha->pFileTable != NULL);
+
+ // If the MPQ has no block table, do nothing
+ if(pHeader->dwBlockTableSize == 0)
+ return ERROR_SUCCESS;
+ assert(ha->dwFileTableSize >= pHeader->dwBlockTableSize);
+
+ // Load the block table
+ // WARNING! ha->pFileTable can change in the process!!
+ pBlockTable = (TMPQBlock *)LoadBlockTable(ha);
+ if(pBlockTable != NULL)
+ {
+ nError = BuildFileTableFromBlockTable(ha, pBlockTable);
+ STORM_FREE(pBlockTable);
+ }
+ else
+ {
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Load the hi-block table
+ if(nError == ERROR_SUCCESS && pHeader->HiBlockTablePos64 != 0)
+ {
+ ULONGLONG ByteOffset;
+ USHORT * pHiBlockTable = NULL;
+ DWORD dwTableSize = pHeader->dwBlockTableSize * sizeof(USHORT);
+
+ // Allocate space for the hi-block table
+ // Note: pHeader->dwBlockTableSize can be zero !!!
+ pHiBlockTable = STORM_ALLOC(USHORT, pHeader->dwBlockTableSize + 1);
+ if(pHiBlockTable != NULL)
+ {
+ // Load the hi-block table. It is not encrypted, nor compressed
+ ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64;
+ if(!FileStream_Read(ha->pStream, &ByteOffset, pHiBlockTable, dwTableSize))
+ nError = GetLastError();
+
+ // Now merge the hi-block table to the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ TFileEntry * pFileEntry = ha->pFileTable;
+
+ // Swap the hi-block table
+ BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, dwTableSize);
+
+ // Add the high file offset to the base file offset.
+ for(DWORD i = 0; i < pHeader->dwBlockTableSize; i++, pFileEntry++)
+ pFileEntry->ByteOffset = MAKE_OFFSET64(pHiBlockTable[i], pFileEntry->ByteOffset);
+ }
+
+ // Free the hi-block table
+ STORM_FREE(pHiBlockTable);
+ }
+ else
+ {
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return nError;
+}
+
+static int BuildFileTable_HetBet(TMPQArchive * ha)
+{
+ TMPQHetTable * pHetTable = ha->pHetTable;
+ TMPQBetTable * pBetTable;
+ TFileEntry * pFileEntry = ha->pFileTable;
+ TBitArray * pBitArray;
+ DWORD dwBitPosition = 0;
+ DWORD i;
+ int nError = ERROR_FILE_CORRUPT;
+
+ // Load the BET table from the MPQ
+ pBetTable = LoadBetTable(ha);
+ if(pBetTable != NULL)
+ {
+ // Verify the size of NameHash2 in the BET table.
+ // It has to be 8 bits less than the information in HET table
+ if((pBetTable->dwBitCount_NameHash2 + 8) != pHetTable->dwNameHashBitSize)
+ {
+ FreeBetTable(pBetTable);
+ return ERROR_FILE_CORRUPT;
+ }
+
+ // Step one: Fill the name indexes
+ for(i = 0; i < pHetTable->dwTotalCount; i++)
+ {
+ DWORD dwFileIndex = 0;
+
+ // Is the entry in the HET table occupied?
+ if(pHetTable->pNameHashes[i] != HET_ENTRY_FREE)
+ {
+ // Load the index to the BET table
+ GetBits(pHetTable->pBetIndexes, pHetTable->dwIndexSizeTotal * i,
+ pHetTable->dwIndexSize,
+ &dwFileIndex,
+ 4);
+ // Overflow test
+ if(dwFileIndex < pBetTable->dwEntryCount)
+ {
+ ULONGLONG NameHash1 = pHetTable->pNameHashes[i];
+ ULONGLONG NameHash2 = 0;
+
+ // Load the BET hash
+ GetBits(pBetTable->pNameHashes, pBetTable->dwBitTotal_NameHash2 * dwFileIndex,
+ pBetTable->dwBitCount_NameHash2,
+ &NameHash2,
+ 8);
+
+ // Combine both part of the name hash and put it to the file table
+ pFileEntry = ha->pFileTable + dwFileIndex;
+ pFileEntry->FileNameHash = (NameHash1 << pBetTable->dwBitCount_NameHash2) | NameHash2;
+ }
+ }
+ }
+
+ // Go through the entire BET table and convert it to the file table.
+ pFileEntry = ha->pFileTable;
+ pBitArray = pBetTable->pFileTable;
+ for(i = 0; i < pBetTable->dwEntryCount; i++)
+ {
+ DWORD dwFlagIndex = 0;
+
+ // Read the file position
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_FilePos,
+ pBetTable->dwBitCount_FilePos,
+ &pFileEntry->ByteOffset,
+ 8);
+
+ // Read the file size
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_FileSize,
+ pBetTable->dwBitCount_FileSize,
+ &pFileEntry->dwFileSize,
+ 4);
+
+ // Read the compressed size
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_CmpSize,
+ pBetTable->dwBitCount_CmpSize,
+ &pFileEntry->dwCmpSize,
+ 4);
+
+
+ // Read the flag index
+ if(pBetTable->dwFlagCount != 0)
+ {
+ GetBits(pBitArray, dwBitPosition + pBetTable->dwBitIndex_FlagIndex,
+ pBetTable->dwBitCount_FlagIndex,
+ &dwFlagIndex,
+ 4);
+ pFileEntry->dwFlags = pBetTable->pFileFlags[dwFlagIndex];
+ }
+
+ //
+ // TODO: Locale (?)
+ //
+
+ // Move the current bit position
+ dwBitPosition += pBetTable->dwTableEntrySize;
+ pFileEntry++;
+ }
+
+ // Set the current size of the file table
+ FreeBetTable(pBetTable);
+ nError = ERROR_SUCCESS;
+ }
+ else
+ {
+ nError = ERROR_FILE_CORRUPT;
+ }
+
+ return nError;
+}
+
+int BuildFileTable(TMPQArchive * ha)
+{
+ DWORD dwFileTableSize;
+ bool bFileTableCreated = false;
+
+ // Sanity checks
+ assert(ha->pFileTable == NULL);
+ assert(ha->dwFileTableSize == 0);
+ assert(ha->dwMaxFileCount != 0);
+
+ // Determine the allocation size for the file table
+ dwFileTableSize = STORMLIB_MAX(ha->pHeader->dwBlockTableSize, ha->dwMaxFileCount);
+
+ // Allocate the file table with size determined before
+ ha->pFileTable = STORM_ALLOC(TFileEntry, dwFileTableSize);
+ if(ha->pFileTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the table with zeros
+ memset(ha->pFileTable, 0, dwFileTableSize * sizeof(TFileEntry));
+ ha->dwFileTableSize = dwFileTableSize;
+
+ // If we have HET table, we load file table from the BET table
+ // Note: If BET table is corrupt or missing, we set the archive as read only
+ if(ha->pHetTable != NULL)
+ {
+ if(BuildFileTable_HetBet(ha) != ERROR_SUCCESS)
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ else
+ bFileTableCreated = true;
+ }
+
+ // If we have hash table, we load the file table from the block table
+ // Note: If block table is corrupt or missing, we set the archive as read only
+ if(ha->pHashTable != NULL)
+ {
+ if(BuildFileTable_Classic(ha) != ERROR_SUCCESS)
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ else
+ bFileTableCreated = true;
+ }
+
+ // Return result
+ return bFileTableCreated ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
+}
+
+/*
+void UpdateBlockTableSize(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ DWORD dwBlockTableSize = 0;
+
+ // Calculate the number of files
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // If the source table entry is valid,
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ dwBlockTableSize = (DWORD)(pFileEntry - ha->pFileTable) + 1;
+ }
+
+ // Save the block table size to the MPQ header
+ ha->pHeader->dwBlockTableSize = ha->dwReservedFiles + dwBlockTableSize;
+}
+*/
+
+// Defragment the file table so it does not contain any gaps
+int DefragmentFileTable(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pSource = ha->pFileTable;
+ TFileEntry * pTarget = ha->pFileTable;
+ LPDWORD DefragmentTable;
+ DWORD dwBlockTableSize = 0;
+ DWORD dwSrcIndex;
+ DWORD dwTrgIndex;
+
+ // Allocate brand new file table
+ DefragmentTable = STORM_ALLOC(DWORD, ha->dwFileTableSize);
+ if(DefragmentTable != NULL)
+ {
+ // Clear the file table
+ memset(DefragmentTable, 0xFF, sizeof(DWORD) * ha->dwFileTableSize);
+
+ // Parse the entire file table and defragment it
+ for(; pSource < pFileTableEnd; pSource++)
+ {
+ // If the source table entry is valid,
+ if(pSource->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // Remember the index conversion
+ dwSrcIndex = (DWORD)(pSource - ha->pFileTable);
+ dwTrgIndex = (DWORD)(pTarget - ha->pFileTable);
+ DefragmentTable[dwSrcIndex] = dwTrgIndex;
+
+ // Move the entry, if needed
+ if(pTarget != pSource)
+ pTarget[0] = pSource[0];
+ pTarget++;
+
+ // Update the block table size
+ dwBlockTableSize = (DWORD)(pTarget - ha->pFileTable);
+ }
+ else
+ {
+ // If there is file name left, free it
+ if(pSource->szFileName != NULL)
+ STORM_FREE(pSource->szFileName);
+ pSource->szFileName = NULL;
+ }
+ }
+
+ // Did we defragment something?
+ if(pTarget < pFileTableEnd)
+ {
+ // Clear the remaining file entries
+ memset(pTarget, 0, (pFileTableEnd - pTarget) * sizeof(TFileEntry));
+
+ // Go through the hash table and relocate the block indexes
+ if(ha->pHashTable != NULL)
+ {
+ TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ TMPQHash * pHash;
+ DWORD dwNewBlockIndex;
+
+ for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
+ {
+ if(MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
+ {
+ // If that block entry is there, set it to the hash entry
+ // If not, set it as DELETED
+ dwNewBlockIndex = DefragmentTable[MPQ_BLOCK_INDEX(pHash)];
+ pHash->dwBlockIndex = (dwNewBlockIndex != HASH_ENTRY_FREE) ? dwNewBlockIndex : HASH_ENTRY_DELETED;
+ }
+ }
+ }
+ }
+
+ // Save the block table size
+ ha->pHeader->dwBlockTableSize = ha->dwReservedFiles + dwBlockTableSize;
+
+ // Free the defragment table
+ STORM_FREE(DefragmentTable);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+// Rebuilds the HET table from scratch based on the file table
+// Used after a modifying operation (add, rename, delete)
+int RebuildHetTable(TMPQArchive * ha)
+{
+ TMPQHetTable * pOldHetTable = ha->pHetTable;
+ TFileEntry * pFileTableEnd;
+ TFileEntry * pFileEntry;
+ DWORD dwBlockTableSize = ha->dwFileTableSize;
+ int nError = ERROR_SUCCESS;
+
+ // If we are in the state of saving MPQ tables, the real size of block table
+ // must already have been calculated. Use that value instead
+ if(ha->dwFlags & MPQ_FLAG_SAVING_TABLES)
+ {
+ assert(ha->pHeader->dwBlockTableSize != 0);
+ dwBlockTableSize = ha->pHeader->dwBlockTableSize;
+ }
+
+ // Create new HET table based on the total number of entries in the file table
+ // Note that if we fail to create it, we just stop using HET table
+ ha->pHetTable = CreateHetTable(dwBlockTableSize, 0, 0x40, NULL);
+ if(ha->pHetTable != NULL)
+ {
+ // Go through the file table again and insert all existing files
+ pFileTableEnd = ha->pFileTable + dwBlockTableSize;
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // Get the high
+ nError = InsertHetEntry(ha->pHetTable, pFileEntry->FileNameHash, (DWORD)(pFileEntry - ha->pFileTable));
+ if(nError != ERROR_SUCCESS)
+ break;
+ }
+ }
+ }
+
+ // Free the old HET table
+ FreeHetTable(pOldHetTable);
+ return nError;
+}
+
+// Rebuilds the file table, removing all deleted file entries.
+// Used when compacting the archive
+int RebuildFileTable(TMPQArchive * ha, DWORD dwNewHashTableSize)
+{
+ TFileEntry * pFileEntry;
+ TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ TMPQHash * pOldHashTable = ha->pHashTable;
+ TMPQHash * pHashTable = NULL;
+ TMPQHash * pHash;
+ int nError = ERROR_SUCCESS;
+
+ // The new hash table size must be greater or equal to the current hash table size
+ assert(dwNewHashTableSize >= ha->pHeader->dwHashTableSize);
+ assert(dwNewHashTableSize >= ha->dwMaxFileCount);
+ assert((dwNewHashTableSize & (dwNewHashTableSize - 1)) == 0);
+ assert(ha->pHashTable != NULL);
+
+ // Reallocate the new file table, if needed
+ if(dwNewHashTableSize > ha->dwFileTableSize)
+ {
+ ha->pFileTable = STORM_REALLOC(TFileEntry, ha->pFileTable, dwNewHashTableSize);
+ if(ha->pFileTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ memset(ha->pFileTable + ha->dwFileTableSize, 0, (dwNewHashTableSize - ha->dwFileTableSize) * sizeof(TFileEntry));
+ }
+
+ // Allocate new hash table
+ if(nError == ERROR_SUCCESS)
+ {
+ pHashTable = STORM_ALLOC(TMPQHash, dwNewHashTableSize);
+ if(pHashTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // If both succeeded, we need to rebuild the file table
+ if(nError == ERROR_SUCCESS)
+ {
+ // Make sure that the hash table is properly filled
+ memset(pHashTable, 0xFF, sizeof(TMPQHash) * dwNewHashTableSize);
+ ha->pHashTable = pHashTable;
+
+ // Set the new limits to the MPQ archive
+ ha->pHeader->dwHashTableSize = dwNewHashTableSize;
+
+ // Parse the old hash table and copy all entries to the new table
+ for(pHash = pOldHashTable; pHash < pHashTableEnd; pHash++)
+ {
+ if(IsValidHashEntry(ha, pHash))
+ {
+ pFileEntry = ha->pFileTable + MPQ_BLOCK_INDEX(pHash);
+ AllocateHashEntry(ha, pFileEntry, pHash->lcLocale);
+ }
+ }
+
+ // Increment the max file count for the file
+ ha->dwFileTableSize = dwNewHashTableSize;
+ ha->dwMaxFileCount = dwNewHashTableSize;
+ ha->dwFlags |= MPQ_FLAG_CHANGED;
+ }
+
+ // Now free the remaining entries
+ if(pOldHashTable != NULL)
+ STORM_FREE(pOldHashTable);
+ return nError;
+}
+
+// Saves MPQ header, hash table, block table and hi-block table.
+int SaveMPQTables(TMPQArchive * ha)
+{
+ TMPQExtHeader * pHetTable = NULL;
+ TMPQExtHeader * pBetTable = NULL;
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQBlock * pBlockTable = NULL;
+ TMPQHash * pHashTable = NULL;
+ ULONGLONG HetTableSize64 = 0;
+ ULONGLONG BetTableSize64 = 0;
+ ULONGLONG HashTableSize64 = 0;
+ ULONGLONG BlockTableSize64 = 0;
+ ULONGLONG HiBlockTableSize64 = 0;
+ ULONGLONG TablePos = 0; // A table position, relative to the begin of the MPQ
+ USHORT * pHiBlockTable = NULL;
+ DWORD cbTotalSize;
+ bool bNeedHiBlockTable = false;
+ int nError = ERROR_SUCCESS;
+
+ // We expect this function to be called only when tables have been changed
+ assert(ha->dwFlags & MPQ_FLAG_CHANGED);
+
+ // Find the space where the MPQ tables will be saved
+ TablePos = FindFreeMpqSpace(ha);
+
+ // If the MPQ has HET table, we prepare a ready-to-save version
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ {
+ pHetTable = TranslateHetTable(ha->pHetTable, &HetTableSize64);
+ if(pHetTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // If the MPQ has HET table, we also must create BET table to be saved
+ if(nError == ERROR_SUCCESS && ha->pHetTable != NULL)
+ {
+ pBetTable = TranslateBetTable(ha, &BetTableSize64);
+ if(pBetTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Now create hash table
+ if(nError == ERROR_SUCCESS && ha->pHashTable != NULL)
+ {
+ pHashTable = TranslateHashTable(ha, &HashTableSize64);
+ if(pHashTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Create block table
+ if(nError == ERROR_SUCCESS && ha->pFileTable != NULL)
+ {
+ pBlockTable = TranslateBlockTable(ha, &BlockTableSize64, &bNeedHiBlockTable);
+ if(pBlockTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Create hi-block table, if needed
+ if(nError == ERROR_SUCCESS && bNeedHiBlockTable)
+ {
+ pHiBlockTable = TranslateHiBlockTable(ha, &HiBlockTableSize64);
+ if(pHiBlockTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Write the HET table, if any
+ if(nError == ERROR_SUCCESS && pHetTable != NULL)
+ {
+ pHeader->HetTableSize64 = HetTableSize64;
+ pHeader->HetTablePos64 = TablePos;
+ nError = SaveExtTable(ha, pHetTable, TablePos, (DWORD)HetTableSize64, pHeader->MD5_HetTable, MPQ_KEY_HASH_TABLE, false, &cbTotalSize);
+ TablePos += cbTotalSize;
+ }
+
+ // Write the BET table, if any
+ if(nError == ERROR_SUCCESS && pBetTable != NULL)
+ {
+ pHeader->BetTableSize64 = BetTableSize64;
+ pHeader->BetTablePos64 = TablePos;
+ nError = SaveExtTable(ha, pBetTable, TablePos, (DWORD)BetTableSize64, pHeader->MD5_BetTable, MPQ_KEY_BLOCK_TABLE, false, &cbTotalSize);
+ TablePos += cbTotalSize;
+ }
+
+ // Write the hash table, if we have any
+ if(nError == ERROR_SUCCESS && pHashTable != NULL)
+ {
+ pHeader->HashTableSize64 = HashTableSize64;
+ pHeader->wHashTablePosHi = (USHORT)(TablePos >> 32);
+ pHeader->dwHashTableSize = (DWORD)(HashTableSize64 / sizeof(TMPQHash));
+ pHeader->dwHashTablePos = (DWORD)TablePos;
+ nError = SaveMpqTable(ha, pHashTable, TablePos, (size_t)HashTableSize64, pHeader->MD5_HashTable, MPQ_KEY_HASH_TABLE, false);
+ TablePos += HashTableSize64;
+ }
+
+ // Write the block table, if we have any
+ if(nError == ERROR_SUCCESS && pBlockTable != NULL)
+ {
+ pHeader->BlockTableSize64 = BlockTableSize64;
+ pHeader->wBlockTablePosHi = (USHORT)(TablePos >> 32);
+ pHeader->dwBlockTableSize = (DWORD)(BlockTableSize64 / sizeof(TMPQBlock));
+ pHeader->dwBlockTablePos = (DWORD)TablePos;
+ nError = SaveMpqTable(ha, pBlockTable, TablePos, (size_t)BlockTableSize64, pHeader->MD5_BlockTable, MPQ_KEY_BLOCK_TABLE, false);
+ TablePos += BlockTableSize64;
+ }
+
+ // Write the hi-block table, if we have any
+ if(nError == ERROR_SUCCESS && pHiBlockTable != NULL)
+ {
+ ULONGLONG ByteOffset = ha->MpqPos + TablePos;
+
+ pHeader->HiBlockTableSize64 = HiBlockTableSize64;
+ pHeader->HiBlockTablePos64 = TablePos;
+ BSWAP_ARRAY16_UNSIGNED(pHiBlockTable, HiBlockTableSize64);
+
+ if(!FileStream_Write(ha->pStream, &ByteOffset, pHiBlockTable, (DWORD)HiBlockTableSize64))
+ nError = GetLastError();
+ TablePos += HiBlockTableSize64;
+ }
+
+ // Cut the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ ULONGLONG FileSize = ha->MpqPos + TablePos;
+
+ if(!FileStream_SetSize(ha->pStream, FileSize))
+ nError = GetLastError();
+ }
+
+ // Write the MPQ header
+ if(nError == ERROR_SUCCESS)
+ {
+ TMPQHeader SaveMpqHeader;
+
+ // Update the size of the archive
+ pHeader->ArchiveSize64 = TablePos;
+ pHeader->dwArchiveSize = (DWORD)TablePos;
+
+ // Update the MD5 of the archive header
+ CalculateDataBlockHash(pHeader, MPQ_HEADER_SIZE_V4 - MD5_DIGEST_SIZE, pHeader->MD5_MpqHeader);
+
+ // Write the MPQ header to the file
+ memcpy(&SaveMpqHeader, pHeader, 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(ha->pStream, &ha->MpqPos, &SaveMpqHeader, pHeader->dwHeaderSize))
+ nError = GetLastError();
+ }
+
+ // Clear the changed flag
+ if(nError == ERROR_SUCCESS)
+ ha->dwFlags &= ~MPQ_FLAG_CHANGED;
+
+ // Cleanup and exit
+ if(pHetTable != NULL)
+ STORM_FREE(pHetTable);
+ if(pBetTable != NULL)
+ STORM_FREE(pBetTable);
+ if(pHashTable != NULL)
+ STORM_FREE(pHashTable);
+ if(pBlockTable != NULL)
+ STORM_FREE(pBlockTable);
+ if(pHiBlockTable != NULL)
+ STORM_FREE(pHiBlockTable);
+ return nError;
+}
diff --git a/dep/StormLib/src/SBaseSubTypes.cpp b/dep/StormLib/src/SBaseSubTypes.cpp
new file mode 100644
index 000000000..47c205e47
--- /dev/null
+++ b/dep/StormLib/src/SBaseSubTypes.cpp
@@ -0,0 +1,618 @@
+/*****************************************************************************/
+/* SBaseSubTypes.cpp Copyright (c) Ladislav Zezula 2013 */
+/*---------------------------------------------------------------------------*/
+/* Conversion routines for archive formats that are similar to MPQ format */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 02.11.11 1.00 Lad The first version of SBaseSubTypes.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+/*****************************************************************************/
+/* */
+/* Support for SQP file format (War of the Immortals) */
+/* */
+/*****************************************************************************/
+
+typedef struct _TSQPHeader
+{
+ // The ID_MPQ ('MPQ\x1A') signature
+ DWORD dwID;
+
+ // Size of the archive header
+ DWORD dwHeaderSize;
+
+ // 32-bit size of MPQ archive
+ DWORD dwArchiveSize;
+
+ // Offset to the beginning of the hash table, relative to the beginning of the archive.
+ DWORD dwHashTablePos;
+
+ // Offset to the beginning of the block table, relative to the beginning of the archive.
+ DWORD dwBlockTablePos;
+
+ // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for
+ // the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
+ DWORD dwHashTableSize;
+
+ // Number of entries in the block table
+ DWORD dwBlockTableSize;
+
+ // Must be zero for SQP files
+ USHORT wFormatVersion;
+
+ // Power of two exponent specifying the number of 512-byte disk sectors in each file sector
+ // in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize.
+ USHORT wSectorSize;
+
+} TSQPHeader;
+
+typedef struct _TSQPHash
+{
+ // Most likely the lcLocale+wPlatform.
+ DWORD dwAlwaysZero;
+
+ // If the hash table entry is valid, this is the index into the block table of the file.
+ // Otherwise, one of the following two values:
+ // - FFFFFFFFh: Hash table entry is empty, and has always been empty.
+ // Terminates searches for a given file.
+ // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
+ // Does not terminate searches for a given file.
+ DWORD dwBlockIndex;
+
+ // The hash of the file path, using method A.
+ DWORD dwName1;
+
+ // The hash of the file path, using method B.
+ DWORD dwName2;
+
+} TSQPHash;
+
+typedef struct _TSQPBlock
+{
+ // Offset of the beginning of the file, relative to the beginning of the archive.
+ DWORD dwFilePos;
+
+ // Flags for the file. See MPQ_FILE_XXXX constants
+ DWORD dwFlags;
+
+ // Compressed file size
+ DWORD dwCSize;
+
+ // Uncompressed file size
+ DWORD dwFSize;
+
+} TSQPBlock;
+
+//-----------------------------------------------------------------------------
+// Functions - SQP file format
+
+// This function converts SQP file header into MPQ file header
+int ConvertSqpHeaderToFormat4(
+ TMPQArchive * ha,
+ ULONGLONG FileSize,
+ DWORD dwFlags)
+{
+ TSQPHeader * pSqpHeader = (TSQPHeader *)ha->HeaderData;
+ TMPQHeader Header;
+
+ // SQP files from War of the Immortal use MPQ file format with slightly
+ // modified structure. These fields have different position:
+ //
+ // Offset TMPQHeader TSQPHeader
+ // ------ ---------- -----------
+ // 000C wFormatVersion dwHashTablePos (lo)
+ // 000E wSectorSize dwHashTablePos (hi)
+ // 001C dwBlockTableSize (lo) wBlockSize
+ // 001E dwHashTableSize (hi) wFormatVersion
+
+ // Can't open the archive with certain flags
+ if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
+ return ERROR_FILE_CORRUPT;
+
+ // The file must not be greater than 4 GB
+ if((FileSize >> 0x20) != 0)
+ return ERROR_FILE_CORRUPT;
+
+ // Translate the SQP header into a MPQ header
+ memset(&Header, 0, sizeof(TMPQHeader));
+ Header.dwID = BSWAP_INT32_UNSIGNED(pSqpHeader->dwID);
+ Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHeaderSize);
+ Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwArchiveSize);
+ Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTablePos);
+ Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTablePos);
+ Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwHashTableSize);
+ Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pSqpHeader->dwBlockTableSize);
+ Header.wFormatVersion = BSWAP_INT16_UNSIGNED(pSqpHeader->wFormatVersion);
+ Header.wSectorSize = BSWAP_INT16_UNSIGNED(pSqpHeader->wSectorSize);
+
+ // Verify the SQP header
+ if(Header.dwID == ID_MPQ && Header.dwHeaderSize == sizeof(TSQPHeader) && Header.dwArchiveSize == FileSize)
+ {
+ // Check for fixed values of version and sector size
+ if(Header.wFormatVersion == MPQ_FORMAT_VERSION_1 && Header.wSectorSize == 3)
+ {
+ // Initialize the fields of 3.0 header
+ Header.ArchiveSize64 = Header.dwArchiveSize;
+ Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash);
+ Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock);
+
+ // Copy the converted MPQ header back
+ memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader));
+
+ // Mark this file as SQP file
+ ha->pfnHashString = HashStringSlash;
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ ha->dwSubType = MPQ_SUBTYPE_SQP;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ return ERROR_FILE_CORRUPT;
+}
+
+void * LoadSqpTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize, DWORD dwKey)
+{
+ ULONGLONG ByteOffset;
+ LPBYTE pbSqpTable;
+
+ // Allocate buffer for the table
+ pbSqpTable = STORM_ALLOC(BYTE, cbTableSize);
+ if(pbSqpTable != NULL)
+ {
+ // Load the table
+ ByteOffset = ha->MpqPos + dwByteOffset;
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbSqpTable, cbTableSize))
+ {
+ // Decrypt the SQP table
+ DecryptMpqBlock(pbSqpTable, cbTableSize, dwKey);
+ return pbSqpTable;
+ }
+
+ // Free the table
+ STORM_FREE(pbSqpTable);
+ }
+
+ return NULL;
+}
+
+TMPQHash * LoadSqpHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TSQPHash * pSqpHashTable;
+ TSQPHash * pSqpHashEnd;
+ TSQPHash * pSqpHash;
+ TMPQHash * pMpqHash;
+ int nError = ERROR_SUCCESS;
+
+ // Load the hash table
+ pSqpHashTable = (TSQPHash *)LoadSqpTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TSQPHash), MPQ_KEY_HASH_TABLE);
+ if(pSqpHashTable != NULL)
+ {
+ // Parse the entire hash table and convert it to MPQ hash table
+ pSqpHashEnd = pSqpHashTable + pHeader->dwHashTableSize;
+ pMpqHash = (TMPQHash *)pSqpHashTable;
+ for(pSqpHash = pSqpHashTable; pSqpHash < pSqpHashEnd; pSqpHash++, pMpqHash++)
+ {
+ // Ignore free entries
+ if(pSqpHash->dwBlockIndex != HASH_ENTRY_FREE)
+ {
+ // Check block index against the size of the block table
+ if(pHeader->dwBlockTableSize <= MPQ_BLOCK_INDEX(pSqpHash) && pSqpHash->dwBlockIndex < HASH_ENTRY_DELETED)
+ nError = ERROR_FILE_CORRUPT;
+
+ // We do not support nonzero locale and platform ID
+ if(pSqpHash->dwAlwaysZero != 0 && pSqpHash->dwAlwaysZero != HASH_ENTRY_FREE)
+ nError = ERROR_FILE_CORRUPT;
+
+ // Store the file name hash
+ pMpqHash->dwName1 = pSqpHash->dwName1;
+ pMpqHash->dwName2 = pSqpHash->dwName2;
+
+ // Store the rest. Note that this must be done last,
+ // because block index corresponds to pMpqHash->dwName2
+ pMpqHash->dwBlockIndex = MPQ_BLOCK_INDEX(pSqpHash);
+ pMpqHash->Platform = 0;
+ pMpqHash->lcLocale = 0;
+ }
+ }
+
+ // If an error occured, we need to free the hash table
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pSqpHashTable);
+ pSqpHashTable = NULL;
+ }
+ }
+
+ // Return the converted hash table (or NULL on failure)
+ return (TMPQHash *)pSqpHashTable;
+}
+
+// Loads the SQP Block table and converts it to a MPQ block table
+TMPQBlock * LoadSqpBlockTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TSQPBlock * pSqpBlockTable;
+ TSQPBlock * pSqpBlockEnd;
+ TSQPBlock * pSqpBlock;
+ TMPQBlock * pMpqBlock;
+ DWORD dwFlags;
+ int nError = ERROR_SUCCESS;
+
+ // Load the hash table
+ pSqpBlockTable = (TSQPBlock *)LoadSqpTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TSQPBlock), MPQ_KEY_BLOCK_TABLE);
+ if(pSqpBlockTable != NULL)
+ {
+ // Parse the entire hash table and convert it to MPQ hash table
+ pSqpBlockEnd = pSqpBlockTable + pHeader->dwBlockTableSize;
+ pMpqBlock = (TMPQBlock *)pSqpBlockTable;
+ for(pSqpBlock = pSqpBlockTable; pSqpBlock < pSqpBlockEnd; pSqpBlock++, pMpqBlock++)
+ {
+ // Check for valid flags
+ if(pSqpBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS)
+ nError = ERROR_FILE_CORRUPT;
+
+ // Convert SQP block table entry to MPQ block table entry
+ dwFlags = pSqpBlock->dwFlags;
+ pMpqBlock->dwCSize = pSqpBlock->dwCSize;
+ pMpqBlock->dwFSize = pSqpBlock->dwFSize;
+ pMpqBlock->dwFlags = dwFlags;
+ }
+
+ // If an error occured, we need to free the hash table
+ if(nError != ERROR_SUCCESS)
+ {
+ STORM_FREE(pSqpBlockTable);
+ pSqpBlockTable = NULL;
+ }
+ }
+
+ // Return the converted hash table (or NULL on failure)
+ return (TMPQBlock *)pSqpBlockTable;
+}
+
+/*****************************************************************************/
+/* */
+/* Support for MPK file format (Longwu Online) */
+/* */
+/*****************************************************************************/
+
+#define MPK_FILE_UNKNOWN_0001 0x00000001 // Seems to be always present
+#define MPK_FILE_UNKNOWN_0010 0x00000010 // Seems to be always present
+#define MPK_FILE_COMPRESSED 0x00000100 // Indicates a compressed file
+#define MPK_FILE_UNKNOWN_2000 0x00002000 // Seems to be always present
+#define MPK_FILE_EXISTS 0x01000000 // Seems to be always present
+
+typedef struct _TMPKHeader
+{
+ // The ID_MPK ('MPK\x1A') signature
+ DWORD dwID;
+
+ // Contains '2000'
+ DWORD dwVersion;
+
+ // 32-bit size of the archive
+ DWORD dwArchiveSize;
+
+ // Size of the archive header
+ DWORD dwHeaderSize;
+
+ DWORD dwHashTablePos;
+ DWORD dwHashTableSize;
+ DWORD dwBlockTablePos;
+ DWORD dwBlockTableSize;
+ DWORD dwUnknownPos;
+ DWORD dwUnknownSize;
+} TMPKHeader;
+
+
+typedef struct _TMPKHash
+{
+ // The hash of the file path, using method A.
+ DWORD dwName1;
+
+ // The hash of the file path, using method B.
+ DWORD dwName2;
+
+ // The hash of the file path, using method C.
+ DWORD dwName3;
+
+ // If the hash table entry is valid, this is the index into the block table of the file.
+ // Otherwise, one of the following two values:
+ // - FFFFFFFFh: Hash table entry is empty, and has always been empty.
+ // Terminates searches for a given file.
+ // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
+ // Does not terminate searches for a given file.
+ DWORD dwBlockIndex;
+
+} TMPKHash;
+
+typedef struct _TMPKBlock
+{
+ DWORD dwFlags; // 0x1121 - Compressed , 0x1120 - Not compressed
+ DWORD dwFilePos; // Offset of the beginning of the file, relative to the beginning of the archive.
+ DWORD dwFSize; // Uncompressed file size
+ DWORD dwCSize; // Compressed file size
+ DWORD dwUnknown; // 0x86364E6D
+} TMPKBlock;
+
+//-----------------------------------------------------------------------------
+// Local variables - MPK file format
+
+static const unsigned char MpkDecryptionKey[512] =
+{
+ 0x60, 0x20, 0x29, 0xE1, 0x01, 0xCE, 0xAA, 0xFE, 0xA3, 0xAB, 0x8E, 0x30, 0xAF, 0x02, 0xD1, 0x7D,
+ 0x41, 0x24, 0x06, 0xBD, 0xAE, 0xBE, 0x43, 0xC3, 0xBA, 0xB7, 0x08, 0x13, 0x51, 0xCF, 0xF8, 0xF7,
+ 0x25, 0x42, 0xA5, 0x4A, 0xDA, 0x0F, 0x52, 0x1C, 0x90, 0x3B, 0x63, 0x49, 0x36, 0xF6, 0xDD, 0x1B,
+ 0xEA, 0x58, 0xD4, 0x40, 0x70, 0x61, 0x55, 0x09, 0xCD, 0x0B, 0xA2, 0x4B, 0x68, 0x2C, 0x8A, 0xF1,
+ 0x3C, 0x3A, 0x65, 0xBB, 0xA1, 0xA8, 0x23, 0x97, 0xFD, 0x15, 0x00, 0x94, 0x88, 0x33, 0x59, 0xE9,
+ 0xFB, 0x69, 0x21, 0xEF, 0x85, 0x5B, 0x57, 0x6C, 0xFA, 0xB5, 0xEE, 0xB8, 0x71, 0xDC, 0xB1, 0x38,
+ 0x0C, 0x0A, 0x5C, 0x56, 0xC9, 0xB4, 0x84, 0x17, 0x1E, 0xE5, 0xD3, 0x5A, 0xCC, 0xFC, 0x11, 0x86,
+ 0x7F, 0x45, 0x4F, 0x54, 0xC8, 0x8D, 0x73, 0x89, 0x79, 0x5D, 0xB3, 0xBF, 0xB9, 0xE3, 0x93, 0xE4,
+ 0x6F, 0x35, 0x2D, 0x46, 0xF2, 0x76, 0xC5, 0x7E, 0xE2, 0xA4, 0xE6, 0xD9, 0x6E, 0x48, 0x34, 0x2B,
+ 0xC6, 0x5F, 0xBC, 0xA0, 0x6D, 0x0D, 0x47, 0x6B, 0x95, 0x96, 0x92, 0x91, 0xB2, 0x27, 0xEB, 0x9E,
+ 0xEC, 0x8F, 0xDF, 0x9C, 0x74, 0x99, 0x64, 0xF5, 0xFF, 0x28, 0xB6, 0x37, 0xF3, 0x7C, 0x81, 0x03,
+ 0x44, 0x62, 0x1F, 0xDB, 0x04, 0x7B, 0xB0, 0x9B, 0x31, 0xA7, 0xDE, 0x78, 0x9F, 0xAD, 0x0E, 0x3F,
+ 0x3E, 0x4D, 0xC7, 0xD7, 0x39, 0x19, 0x5E, 0xC2, 0xD0, 0xAC, 0xE8, 0x1A, 0x87, 0x8B, 0x07, 0x05,
+ 0x22, 0xED, 0x72, 0x2E, 0x1D, 0xC1, 0xA9, 0xD6, 0xE0, 0x83, 0xD5, 0xD8, 0xCB, 0x80, 0xF0, 0x66,
+ 0x7A, 0x9D, 0x50, 0xF9, 0x10, 0x4E, 0x16, 0x14, 0x77, 0x75, 0x6A, 0x67, 0xD2, 0xC0, 0xA6, 0xC4,
+ 0x53, 0x8C, 0x32, 0xCA, 0x82, 0x2A, 0x18, 0x9A, 0xF4, 0x4C, 0x3D, 0x26, 0x12, 0xE7, 0x98, 0x2F,
+ 0x4A, 0x04, 0x0D, 0xAF, 0xB4, 0xCF, 0x12, 0xCE, 0x1A, 0x37, 0x61, 0x39, 0x60, 0x95, 0xBE, 0x25,
+ 0xE4, 0x6E, 0xFC, 0x1B, 0xE7, 0x49, 0xE6, 0x67, 0xF6, 0xC5, 0xCB, 0x2F, 0x27, 0xD4, 0x68, 0xB2,
+ 0x01, 0x52, 0xD0, 0x46, 0x11, 0x20, 0xFB, 0x9D, 0xA9, 0x02, 0xF5, 0x8F, 0x3D, 0x82, 0xD3, 0xFF,
+ 0x0B, 0xB8, 0xF2, 0x4D, 0x8E, 0x81, 0x2C, 0xAB, 0x5F, 0xC4, 0x41, 0x29, 0x40, 0xFA, 0xC0, 0xBF,
+ 0x33, 0x10, 0x21, 0x16, 0xB0, 0x71, 0x83, 0x96, 0x8D, 0x2B, 0x23, 0x3B, 0xF9, 0xC1, 0xE5, 0x72,
+ 0xE2, 0x1C, 0x26, 0xF0, 0x73, 0x36, 0x63, 0x56, 0x31, 0x4E, 0x6B, 0x55, 0x62, 0x79, 0xC6, 0x91,
+ 0x00, 0x35, 0xB1, 0x2A, 0xA6, 0x42, 0xDF, 0xEB, 0x3C, 0x51, 0xEA, 0x97, 0x57, 0x94, 0x8C, 0x80,
+ 0x34, 0x5C, 0xD2, 0x76, 0xA4, 0xE9, 0x85, 0xE8, 0xBB, 0x78, 0xE0, 0xB5, 0xAD, 0x0F, 0x87, 0x70,
+ 0xDD, 0xAE, 0xF4, 0xD9, 0x66, 0x54, 0x6F, 0xCC, 0x4C, 0x77, 0x3E, 0xCD, 0xF1, 0x75, 0x0A, 0xA1,
+ 0x28, 0x9B, 0x9A, 0x7E, 0x4B, 0x98, 0x99, 0x47, 0xFE, 0xA5, 0xF7, 0xB7, 0xA3, 0xE1, 0x9F, 0xBC,
+ 0x93, 0x44, 0x3A, 0x08, 0x89, 0x22, 0xEE, 0xB9, 0x45, 0xD6, 0x06, 0x09, 0xC9, 0xBD, 0x14, 0x0C,
+ 0xB6, 0x5E, 0x9C, 0x7A, 0x65, 0x59, 0xAA, 0x19, 0x5B, 0x7C, 0x18, 0x43, 0x92, 0x13, 0x15, 0x7B,
+ 0xED, 0xD5, 0xC7, 0x17, 0xEF, 0x86, 0x90, 0xC2, 0x74, 0x64, 0xF3, 0xDC, 0x6C, 0x38, 0x05, 0x1D,
+ 0xC8, 0x0E, 0xEC, 0x6A, 0x32, 0xDA, 0xD7, 0xC3, 0xDB, 0x8B, 0x24, 0xB3, 0x5D, 0x2E, 0xBA, 0xA2,
+ 0xD8, 0x03, 0x88, 0x7D, 0x7F, 0x69, 0x8A, 0xFD, 0xCA, 0x4F, 0x30, 0x9E, 0xA0, 0xD1, 0x5A, 0x53,
+ 0xDE, 0x3F, 0x84, 0xAC, 0xF8, 0xA7, 0x2D, 0x1F, 0x1E, 0xE3, 0x58, 0x50, 0x6D, 0x48, 0x07, 0xA8
+};
+
+//-----------------------------------------------------------------------------
+// Functions - MPK file format
+
+// This function converts MPK file header into MPQ file header
+int ConvertMpkHeaderToFormat4(
+ TMPQArchive * ha,
+ ULONGLONG FileSize,
+ DWORD dwFlags)
+{
+ TMPKHeader * pMpkHeader = (TMPKHeader *)ha->HeaderData;
+ TMPQHeader Header;
+
+ // Can't open the archive with certain flags
+ if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
+ return ERROR_FILE_CORRUPT;
+
+ // Translate the MPK header into a MPQ header
+ // Note: Hash table size and block table size are in bytes, not in entries
+ memset(&Header, 0, sizeof(TMPQHeader));
+ Header.dwID = BSWAP_INT32_UNSIGNED(pMpkHeader->dwID);
+ Header.dwArchiveSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwArchiveSize);
+ Header.dwHeaderSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHeaderSize);
+ Header.dwHashTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTablePos);
+ Header.dwHashTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwHashTableSize) / sizeof(TMPKHash);
+ Header.dwBlockTablePos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTablePos);
+ Header.dwBlockTableSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwBlockTableSize) / sizeof(TMPKBlock);
+// Header.dwUnknownPos = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownPos);
+// Header.dwUnknownSize = BSWAP_INT32_UNSIGNED(pMpkHeader->dwUnknownSize);
+ assert(Header.dwHeaderSize == sizeof(TMPKHeader));
+
+ // Verify the MPK header
+ if(Header.dwID == ID_MPK && Header.dwHeaderSize == sizeof(TMPKHeader) && Header.dwArchiveSize == (DWORD)FileSize)
+ {
+ // The header ID must be ID_MPQ
+ Header.dwID = ID_MPQ;
+ Header.wFormatVersion = MPQ_FORMAT_VERSION_1;
+ Header.wSectorSize = 3;
+
+ // Initialize the fields of 3.0 header
+ Header.ArchiveSize64 = Header.dwArchiveSize;
+ Header.HashTableSize64 = Header.dwHashTableSize * sizeof(TMPQHash);
+ Header.BlockTableSize64 = Header.dwBlockTableSize * sizeof(TMPQBlock);
+
+ // Copy the converted MPQ header back
+ memcpy(ha->HeaderData, &Header, sizeof(TMPQHeader));
+
+ // Mark this file as MPK file
+ ha->pfnHashString = HashStringLower;
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ ha->dwSubType = MPQ_SUBTYPE_MPK;
+ return ERROR_SUCCESS;
+ }
+ return ERROR_FILE_CORRUPT;
+}
+
+// Attempts to search a free hash entry in the hash table being converted.
+// The created hash table must always be of nonzero size,
+// should have no duplicated items and no deleted entries
+TMPQHash * FindFreeHashEntry(TMPQHash * pHashTable, DWORD dwHashTableSize, DWORD dwStartIndex)
+{
+ TMPQHash * pHash;
+ DWORD dwIndex;
+
+ // Set the initial index
+ dwStartIndex = dwIndex = (dwStartIndex & (dwHashTableSize - 1));
+ assert(dwHashTableSize != 0);
+
+ // Search the hash table and return the found entries in the following priority:
+ for(;;)
+ {
+ // We are not expecting to find matching entry in the hash table being built
+ // We are not expecting to find deleted entry either
+ pHash = pHashTable + dwIndex;
+
+ // If we found a free entry, we need to stop searching
+ if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
+ return pHash;
+
+ // Move to the next hash entry.
+ // If we reached the starting entry, it's failure.
+ dwIndex = (dwIndex + 1) & (dwHashTableSize - 1);
+ if(dwIndex == dwStartIndex)
+ break;
+ }
+
+ // We haven't found anything
+ assert(false);
+ return NULL;
+}
+
+void DecryptMpkTable(void * pvMpkTable, size_t cbSize)
+{
+ LPBYTE pbMpkTable = (LPBYTE)pvMpkTable;
+
+ for(size_t i = 0; i < cbSize; i++)
+ pbMpkTable[i] = MpkDecryptionKey[pbMpkTable[i]];
+}
+
+void * LoadMpkTable(TMPQArchive * ha, DWORD dwByteOffset, DWORD cbTableSize)
+{
+ ULONGLONG ByteOffset;
+ LPBYTE pbMpkTable = NULL;
+
+ // Allocate space for the table
+ pbMpkTable = STORM_ALLOC(BYTE, cbTableSize);
+ if(pbMpkTable != NULL)
+ {
+ // Load and the MPK hash table
+ ByteOffset = ha->MpqPos + dwByteOffset;
+ if(FileStream_Read(ha->pStream, &ByteOffset, pbMpkTable, cbTableSize))
+ {
+ // Decrypt the table
+ DecryptMpkTable(pbMpkTable, cbTableSize);
+ return pbMpkTable;
+ }
+
+ // Free the MPK table
+ STORM_FREE(pbMpkTable);
+ pbMpkTable = NULL;
+ }
+
+ // Return the table
+ return pbMpkTable;
+}
+
+TMPQHash * LoadMpkHashTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQHash * pHashTable = NULL;
+ TMPKHash * pMpkHash;
+ TMPQHash * pHash = NULL;
+ DWORD dwHashTableSize = pHeader->dwHashTableSize;
+
+ // MPKs use different hash table searching.
+ // Instead of using MPQ_HASH_TABLE_INDEX hash as index,
+ // they store the value directly in the hash table.
+ // Also for faster searching, the hash table is sorted ascending by the value
+
+ // Load and decrypt the MPK hash table.
+ pMpkHash = (TMPKHash *)LoadMpkTable(ha, pHeader->dwHashTablePos, pHeader->dwHashTableSize * sizeof(TMPKHash));
+ if(pMpkHash != NULL)
+ {
+ // Calculate the hash table size as if it was real MPQ hash table
+ pHeader->dwHashTableSize = GetNearestPowerOfTwo(pHeader->dwHashTableSize);
+ pHeader->HashTableSize64 = pHeader->dwHashTableSize * sizeof(TMPQHash);
+
+ // Now allocate table that will serve like a true MPQ hash table,
+ // so we translate the MPK hash table to MPQ hash table
+ pHashTable = STORM_ALLOC(TMPQHash, pHeader->dwHashTableSize);
+ if(pHashTable != NULL)
+ {
+ // Set the entire hash table to free
+ memset(pHashTable, 0xFF, (size_t)pHeader->HashTableSize64);
+
+ // Copy the MPK hash table into MPQ hash table
+ for(DWORD i = 0; i < dwHashTableSize; i++)
+ {
+ // Finds the free hash entry in the hash table
+ // We don't expect any errors here, because we are putting files to empty hash table
+ pHash = FindFreeHashEntry(pHashTable, pHeader->dwHashTableSize, pMpkHash[i].dwName1);
+ assert(pHash->dwBlockIndex == HASH_ENTRY_FREE);
+
+ // Copy the MPK hash entry to the hash table
+ pHash->dwBlockIndex = pMpkHash[i].dwBlockIndex;
+ pHash->Platform = 0;
+ pHash->lcLocale = 0;
+ pHash->dwName1 = pMpkHash[i].dwName2;
+ pHash->dwName2 = pMpkHash[i].dwName3;
+ }
+ }
+
+ // Free the temporary hash table
+ STORM_FREE(pMpkHash);
+ }
+
+ return pHashTable;
+}
+
+static DWORD ConvertMpkFlagsToMpqFlags(DWORD dwMpkFlags)
+{
+ DWORD dwMpqFlags = MPQ_FILE_EXISTS;
+
+ // Check for flags that are always present
+ assert((dwMpkFlags & MPK_FILE_UNKNOWN_0001) != 0);
+ assert((dwMpkFlags & MPK_FILE_UNKNOWN_0010) != 0);
+ assert((dwMpkFlags & MPK_FILE_UNKNOWN_2000) != 0);
+ assert((dwMpkFlags & MPK_FILE_EXISTS) != 0);
+
+ // Append the compressed flag
+ dwMpqFlags |= (dwMpkFlags & MPK_FILE_COMPRESSED) ? MPQ_FILE_COMPRESS : 0;
+
+ // All files in the MPQ seem to be single unit files
+ dwMpqFlags |= MPQ_FILE_ENCRYPTED | MPQ_FILE_SINGLE_UNIT;
+
+ return dwMpqFlags;
+}
+
+TMPQBlock * LoadMpkBlockTable(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPKBlock * pMpkBlockTable;
+ TMPKBlock * pMpkBlockEnd;
+ TMPQBlock * pBlockTable = NULL;
+ TMPKBlock * pMpkBlock;
+ TMPQBlock * pMpqBlock;
+
+ // Load and decrypt the MPK block table
+ pMpkBlockTable = pMpkBlock = (TMPKBlock *)LoadMpkTable(ha, pHeader->dwBlockTablePos, pHeader->dwBlockTableSize * sizeof(TMPKBlock));
+ if(pMpkBlockTable != NULL)
+ {
+ // Allocate buffer for MPQ-like block table
+ pBlockTable = pMpqBlock = STORM_ALLOC(TMPQBlock, pHeader->dwBlockTableSize);
+ if(pBlockTable != NULL)
+ {
+ // Convert the MPK block table to MPQ block table
+ pMpkBlockEnd = pMpkBlockTable + pHeader->dwBlockTableSize;
+ while(pMpkBlock < pMpkBlockEnd)
+ {
+ // Translate the MPK block table entry to MPQ block table entry
+ pMpqBlock->dwFilePos = pMpkBlock->dwFilePos;
+ pMpqBlock->dwCSize = pMpkBlock->dwCSize;
+ pMpqBlock->dwFSize = pMpkBlock->dwFSize;
+ pMpqBlock->dwFlags = ConvertMpkFlagsToMpqFlags(pMpkBlock->dwFlags);
+
+ // Move both
+ pMpkBlock++;
+ pMpqBlock++;
+ }
+ }
+
+ // Free the MPK block table
+ STORM_FREE(pMpkBlockTable);
+ }
+
+ return pBlockTable;
+}
diff --git a/dep/StormLib/src/SCompression.cpp b/dep/StormLib/src/SCompression.cpp
new file mode 100644
index 000000000..d706a6f37
--- /dev/null
+++ b/dep/StormLib/src/SCompression.cpp
@@ -0,0 +1,1145 @@
+/*****************************************************************************/
+/* SCompression.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* This module serves as a bridge between StormLib code and (de)compression */
+/* functions. All (de)compression calls go (and should only go) through this */
+/* module. No system headers should be included in this module to prevent */
+/* compile-time problems. */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 01.04.03 1.00 Lad The first version of SCompression.cpp */
+/* 19.11.03 1.01 Dan Big endian handling */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+// Information about the input and output buffers for pklib
+typedef struct
+{
+ unsigned char * pbInBuff; // Pointer to input data buffer
+ unsigned char * pbInBuffEnd; // End of the input buffer
+ unsigned char * pbOutBuff; // Pointer to output data buffer
+ unsigned char * pbOutBuffEnd; // Pointer to output data buffer
+} TDataInfo;
+
+// Prototype of the compression function
+// Function doesn't return an error. A success means that the size of compressed buffer
+// is lower than size of uncompressed buffer.
+typedef void (*COMPRESS)(
+ void * pvOutBuffer, // [out] Pointer to the buffer where the compressed data will be stored
+ int * pcbOutBuffer, // [in] Pointer to length of the buffer pointed by pvOutBuffer
+ void * pvInBuffer, // [in] Pointer to the buffer with data to compress
+ int cbInBuffer, // [in] Length of the buffer pointer by pvInBuffer
+ int * pCmpType, // [in] Compression-method specific value. ADPCM Setups this for the following Huffman compression
+ int nCmpLevel); // [in] Compression specific value. ADPCM uses this. Should be set to zero.
+
+// Prototype of the decompression function
+// Returns 1 if success, 0 if failure
+typedef int (*DECOMPRESS)(
+ void * pvOutBuffer, // [out] Pointer to the buffer where to store decompressed data
+ int * pcbOutBuffer, // [in] Pointer to total size of the buffer pointed by pvOutBuffer
+ // [out] Contains length of the decompressed data
+ void * pvInBuffer, // [in] Pointer to data to be decompressed
+ int cbInBuffer); // [in] Length of the data to be decompressed
+
+// Table of compression functions
+typedef struct
+{
+ unsigned long uMask; // Compression mask
+ COMPRESS Compress; // Compression function
+} TCompressTable;
+
+// Table of decompression functions
+typedef struct
+{
+ unsigned long uMask; // Decompression bit
+ DECOMPRESS Decompress; // Decompression function
+} TDecompressTable;
+
+
+/*****************************************************************************/
+/* */
+/* Support for Huffman compression (0x01) */
+/* */
+/*****************************************************************************/
+
+void Compress_huff(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ THuffmannTree ht(true);
+ TOutputStream os(pvOutBuffer, *pcbOutBuffer);
+
+ STORMLIB_UNUSED(nCmpLevel);
+ *pcbOutBuffer = ht.Compress(&os, pvInBuffer, cbInBuffer, *pCmpType);
+}
+
+int Decompress_huff(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ THuffmannTree ht(false);
+ TInputStream is(pvInBuffer, cbInBuffer);
+
+ *pcbOutBuffer = ht.Decompress(pvOutBuffer, *pcbOutBuffer, &is);
+ return (*pcbOutBuffer == 0) ? 0 : 1;
+}
+
+/******************************************************************************/
+/* */
+/* Support for ZLIB compression (0x02) */
+/* */
+/******************************************************************************/
+
+void Compress_ZLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ z_stream z; // Stream information for zlib
+ int windowBits;
+ int nResult;
+
+ // Keep compilers happy
+ STORMLIB_UNUSED(pCmpType);
+ STORMLIB_UNUSED(nCmpLevel);
+
+ // Fill the stream structure for zlib
+ z.next_in = (Bytef *)pvInBuffer;
+ z.avail_in = (uInt)cbInBuffer;
+ z.total_in = cbInBuffer;
+ z.next_out = (Bytef *)pvOutBuffer;
+ z.avail_out = *pcbOutBuffer;
+ z.total_out = 0;
+ z.zalloc = NULL;
+ z.zfree = NULL;
+
+ // Determine the proper window bits (WoW.exe build 12694)
+ if(cbInBuffer <= 0x100)
+ windowBits = 8;
+ else if(cbInBuffer <= 0x200)
+ windowBits = 9;
+ else if(cbInBuffer <= 0x400)
+ windowBits = 10;
+ else if(cbInBuffer <= 0x800)
+ windowBits = 11;
+ else if(cbInBuffer <= 0x1000)
+ windowBits = 12;
+ else if(cbInBuffer <= 0x2000)
+ windowBits = 13;
+ else if(cbInBuffer <= 0x4000)
+ windowBits = 14;
+ else
+ windowBits = 15;
+
+ // Initialize the compression.
+ // Storm.dll uses zlib version 1.1.3
+ // Wow.exe uses zlib version 1.2.3
+ nResult = deflateInit2(&z,
+ 6, // Compression level used by WoW MPQs
+ Z_DEFLATED,
+ windowBits,
+ 8,
+ Z_DEFAULT_STRATEGY);
+ if(nResult == Z_OK)
+ {
+ // Call zlib to compress the data
+ nResult = deflate(&z, Z_FINISH);
+
+ if(nResult == Z_OK || nResult == Z_STREAM_END)
+ *pcbOutBuffer = z.total_out;
+
+ deflateEnd(&z);
+ }
+}
+
+int Decompress_ZLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ z_stream z; // Stream information for zlib
+ int nResult;
+
+ // Fill the stream structure for zlib
+ z.next_in = (Bytef *)pvInBuffer;
+ z.avail_in = (uInt)cbInBuffer;
+ z.total_in = cbInBuffer;
+ z.next_out = (Bytef *)pvOutBuffer;
+ z.avail_out = *pcbOutBuffer;
+ z.total_out = 0;
+ z.zalloc = NULL;
+ z.zfree = NULL;
+
+ // Initialize the decompression structure. Storm.dll uses zlib version 1.1.3
+ if((nResult = inflateInit(&z)) == 0)
+ {
+ // Call zlib to decompress the data
+ nResult = inflate(&z, Z_FINISH);
+ *pcbOutBuffer = z.total_out;
+ inflateEnd(&z);
+ }
+ return nResult;
+}
+
+/******************************************************************************/
+/* */
+/* Support functions for PKWARE Data Compression Library compression (0x08) */
+/* */
+/******************************************************************************/
+
+// Function loads data from the input buffer. Used by Pklib's "implode"
+// and "explode" function as user-defined callback
+// Returns number of bytes loaded
+//
+// char * buf - Pointer to a buffer where to store loaded data
+// unsigned int * size - Max. number of bytes to read
+// void * param - Custom pointer, parameter of implode/explode
+
+static unsigned int ReadInputData(char * buf, unsigned int * size, void * param)
+{
+ TDataInfo * pInfo = (TDataInfo *)param;
+ unsigned int nMaxAvail = (unsigned int)(pInfo->pbInBuffEnd - pInfo->pbInBuff);
+ unsigned int nToRead = *size;
+
+ // Check the case when not enough data available
+ if(nToRead > nMaxAvail)
+ nToRead = nMaxAvail;
+
+ // Load data and increment offsets
+ memcpy(buf, pInfo->pbInBuff, nToRead);
+ pInfo->pbInBuff += nToRead;
+ assert(pInfo->pbInBuff <= pInfo->pbInBuffEnd);
+ return nToRead;
+}
+
+// Function for store output data. Used by Pklib's "implode" and "explode"
+// as user-defined callback
+//
+// char * buf - Pointer to data to be written
+// unsigned int * size - Number of bytes to write
+// void * param - Custom pointer, parameter of implode/explode
+
+static void WriteOutputData(char * buf, unsigned int * size, void * param)
+{
+ TDataInfo * pInfo = (TDataInfo *)param;
+ unsigned int nMaxWrite = (unsigned int)(pInfo->pbOutBuffEnd - pInfo->pbOutBuff);
+ unsigned int nToWrite = *size;
+
+ // Check the case when not enough space in the output buffer
+ if(nToWrite > nMaxWrite)
+ nToWrite = nMaxWrite;
+
+ // Write output data and increments offsets
+ memcpy(pInfo->pbOutBuff, buf, nToWrite);
+ pInfo->pbOutBuff += nToWrite;
+ assert(pInfo->pbOutBuff <= pInfo->pbOutBuffEnd);
+}
+
+static void Compress_PKLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ TDataInfo Info; // Data information
+ char * work_buf = STORM_ALLOC(char, CMP_BUFFER_SIZE);// Pklib's work buffer
+ unsigned int dict_size; // Dictionary size
+ unsigned int ctype = CMP_BINARY; // Compression type
+
+ // Keep compilers happy
+ STORMLIB_UNUSED(pCmpType);
+ STORMLIB_UNUSED(nCmpLevel);
+
+ // Handle no-memory condition
+ if(work_buf != NULL)
+ {
+ // Fill data information structure
+ memset(work_buf, 0, CMP_BUFFER_SIZE);
+ Info.pbInBuff = (unsigned char *)pvInBuffer;
+ Info.pbInBuffEnd = (unsigned char *)pvInBuffer + cbInBuffer;
+ Info.pbOutBuff = (unsigned char *)pvOutBuffer;
+ Info.pbOutBuffEnd = (unsigned char *)pvOutBuffer + *pcbOutBuffer;
+
+ //
+ // Set the dictionary size
+ //
+ // Diablo I uses fixed dictionary size of CMP_IMPLODE_DICT_SIZE3
+ // Starcraft I uses the variable dictionary size based on algorithm below
+ //
+
+ if (cbInBuffer < 0x600)
+ dict_size = CMP_IMPLODE_DICT_SIZE1;
+ else if(0x600 <= cbInBuffer && cbInBuffer < 0xC00)
+ dict_size = CMP_IMPLODE_DICT_SIZE2;
+ else
+ dict_size = CMP_IMPLODE_DICT_SIZE3;
+
+ // Do the compression
+ if(implode(ReadInputData, WriteOutputData, work_buf, &Info, &ctype, &dict_size) == CMP_NO_ERROR)
+ *pcbOutBuffer = (int)(Info.pbOutBuff - (unsigned char *)pvOutBuffer);
+
+ STORM_FREE(work_buf);
+ }
+}
+
+static int Decompress_PKLIB(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ TDataInfo Info; // Data information
+ char * work_buf = STORM_ALLOC(char, EXP_BUFFER_SIZE);// Pklib's work buffer
+
+ // Handle no-memory condition
+ if(work_buf == NULL)
+ return 0;
+
+ // Fill data information structure
+ memset(work_buf, 0, EXP_BUFFER_SIZE);
+ Info.pbInBuff = (unsigned char *)pvInBuffer;
+ Info.pbInBuffEnd = (unsigned char *)pvInBuffer + cbInBuffer;
+ Info.pbOutBuff = (unsigned char *)pvOutBuffer;
+ Info.pbOutBuffEnd = (unsigned char *)pvOutBuffer + *pcbOutBuffer;
+
+ // Do the decompression
+ explode(ReadInputData, WriteOutputData, work_buf, &Info);
+
+ // If PKLIB is unable to decompress the data, return 0;
+ if(Info.pbOutBuff == pvOutBuffer)
+ {
+ STORM_FREE(work_buf);
+ return 0;
+ }
+
+ // Give away the number of decompressed bytes
+ *pcbOutBuffer = (int)(Info.pbOutBuff - (unsigned char *)pvOutBuffer);
+ STORM_FREE(work_buf);
+ return 1;
+}
+
+/******************************************************************************/
+/* */
+/* Support for Bzip2 compression (0x10) */
+/* */
+/******************************************************************************/
+
+static void Compress_BZIP2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ bz_stream strm;
+ int blockSize100k = 9;
+ int workFactor = 30;
+ int bzError;
+
+ // Keep compilers happy
+ STORMLIB_UNUSED(pCmpType);
+ STORMLIB_UNUSED(nCmpLevel);
+
+ // Initialize the BZIP2 compression
+ strm.bzalloc = NULL;
+ strm.bzfree = NULL;
+ strm.opaque = NULL;
+
+ // Blizzard uses 9 as blockSize100k, (0x30 as workFactor)
+ // Last checked on Starcraft II
+ if(BZ2_bzCompressInit(&strm, blockSize100k, 0, workFactor) == BZ_OK)
+ {
+ strm.next_in = (char *)pvInBuffer;
+ strm.avail_in = cbInBuffer;
+ strm.next_out = (char *)pvOutBuffer;
+ strm.avail_out = *pcbOutBuffer;
+
+ // Perform the compression
+ for(;;)
+ {
+ bzError = BZ2_bzCompress(&strm, (strm.avail_in != 0) ? BZ_RUN : BZ_FINISH);
+ if(bzError == BZ_STREAM_END || bzError < 0)
+ break;
+ }
+
+ // Put the stream into idle state
+ BZ2_bzCompressEnd(&strm);
+
+ if(bzError > 0)
+ *pcbOutBuffer = strm.total_out_lo32;
+ }
+}
+
+static int Decompress_BZIP2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ bz_stream strm;
+ int nResult = BZ_OK;
+
+ // Initialize the BZIP2 decompression
+ strm.bzalloc = NULL;
+ strm.bzfree = NULL;
+ strm.opaque = NULL;
+
+ // Initialize decompression
+ if(BZ2_bzDecompressInit(&strm, 0, 0) == BZ_OK)
+ {
+ strm.next_in = (char *)pvInBuffer;
+ strm.avail_in = cbInBuffer;
+ strm.next_out = (char *)pvOutBuffer;
+ strm.avail_out = *pcbOutBuffer;
+
+ // Perform the decompression
+ while(nResult != BZ_STREAM_END)
+ {
+ nResult = BZ2_bzDecompress(&strm);
+
+ // If any error there, break the loop
+ if(nResult < BZ_OK)
+ break;
+ }
+
+ // Put the stream into idle state
+ BZ2_bzDecompressEnd(&strm);
+
+ // If all succeeded, set the number of output bytes
+ if(nResult >= BZ_OK)
+ {
+ *pcbOutBuffer = strm.total_out_lo32;
+ return 1;
+ }
+ }
+
+ // Something failed, so set number of output bytes to zero
+ *pcbOutBuffer = 0;
+ return 1;
+}
+
+/******************************************************************************/
+/* */
+/* Support functions for LZMA compression (0x12) */
+/* */
+/******************************************************************************/
+
+#define LZMA_HEADER_SIZE (1 + LZMA_PROPS_SIZE + 8)
+
+static SRes LZMA_Callback_Progress(void * /* p */, UInt64 /* inSize */, UInt64 /* outSize */)
+{
+ return SZ_OK;
+}
+
+static void * LZMA_Callback_Alloc(void *p, size_t size)
+{
+ p = p;
+ return STORM_ALLOC(BYTE, size);
+}
+
+/* address can be 0 */
+static void LZMA_Callback_Free(void *p, void *address)
+{
+ p = p;
+ if(address != NULL)
+ STORM_FREE(address);
+}
+
+//
+// Note: So far, I haven't seen any files compressed by LZMA.
+// This code haven't been verified against code ripped from Starcraft II Beta,
+// but we know that Starcraft LZMA decompression code is able to decompress
+// the data compressed by StormLib.
+//
+
+static void Compress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ ICompressProgress Progress;
+ CLzmaEncProps props;
+ ISzAlloc SzAlloc;
+ Byte * pbOutBuffer = (Byte *)pvOutBuffer;
+ Byte * destBuffer;
+ SizeT destLen = *pcbOutBuffer;
+ SizeT srcLen = cbInBuffer;
+ Byte encodedProps[LZMA_PROPS_SIZE];
+ size_t encodedPropsSize = LZMA_PROPS_SIZE;
+ SRes nResult;
+
+ // Keep compilers happy
+ STORMLIB_UNUSED(pCmpType);
+ STORMLIB_UNUSED(nCmpLevel);
+
+ // Fill the callbacks in structures
+ Progress.Progress = LZMA_Callback_Progress;
+ SzAlloc.Alloc = LZMA_Callback_Alloc;
+ SzAlloc.Free = LZMA_Callback_Free;
+
+ // Initialize properties
+ LzmaEncProps_Init(&props);
+
+ // Perform compression
+ destBuffer = (Byte *)pvOutBuffer + LZMA_HEADER_SIZE;
+ destLen = *pcbOutBuffer - LZMA_HEADER_SIZE;
+ nResult = LzmaEncode(destBuffer,
+ &destLen,
+ (Byte *)pvInBuffer,
+ srcLen,
+ &props,
+ encodedProps,
+ &encodedPropsSize,
+ 0,
+ &Progress,
+ &SzAlloc,
+ &SzAlloc);
+ if(nResult != SZ_OK)
+ return;
+
+ // If we failed to compress the data
+ if(destLen >= (SizeT)(*pcbOutBuffer - LZMA_HEADER_SIZE))
+ return;
+
+ // Write "useFilter" variable. Blizzard MPQ must not use filter.
+ *pbOutBuffer++ = 0;
+
+ // Copy the encoded properties to the output buffer
+ memcpy(pvOutBuffer, encodedProps, encodedPropsSize);
+ pbOutBuffer += encodedPropsSize;
+
+ // Copy the size of the data
+ *pbOutBuffer++ = (unsigned char)(srcLen >> 0x00);
+ *pbOutBuffer++ = (unsigned char)(srcLen >> 0x08);
+ *pbOutBuffer++ = (unsigned char)(srcLen >> 0x10);
+ *pbOutBuffer++ = (unsigned char)(srcLen >> 0x18);
+ *pbOutBuffer++ = 0;
+ *pbOutBuffer++ = 0;
+ *pbOutBuffer++ = 0;
+ *pbOutBuffer++ = 0;
+
+ // Give the size of the data to the caller
+ *pcbOutBuffer = (unsigned int)(destLen + LZMA_HEADER_SIZE);
+}
+
+static int Decompress_LZMA(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ ELzmaStatus LzmaStatus;
+ ISzAlloc SzAlloc;
+ Byte * destBuffer = (Byte *)pvOutBuffer;
+ Byte * srcBuffer = (Byte *)pvInBuffer;
+ SizeT destLen = *pcbOutBuffer;
+ SizeT srcLen = cbInBuffer;
+ SRes nResult;
+
+ // There must be at least 0x0E bytes in the buffer
+ if(srcLen <= LZMA_HEADER_SIZE)
+ return 0;
+
+ // We only accept blocks that have no filter used
+ if(*srcBuffer != 0)
+ return 0;
+
+ // Fill the callbacks in structures
+ SzAlloc.Alloc = LZMA_Callback_Alloc;
+ SzAlloc.Free = LZMA_Callback_Free;
+
+ // Perform compression
+ srcLen = cbInBuffer - LZMA_HEADER_SIZE;
+ nResult = LzmaDecode(destBuffer,
+ &destLen,
+ srcBuffer + LZMA_HEADER_SIZE,
+ &srcLen,
+ srcBuffer + 1,
+ LZMA_PROPS_SIZE,
+ LZMA_FINISH_END,
+ &LzmaStatus,
+ &SzAlloc);
+ if(nResult != SZ_OK)
+ return 0;
+
+ *pcbOutBuffer = (unsigned int)destLen;
+ return 1;
+}
+
+static int Decompress_LZMA_MPK(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ ELzmaStatus LzmaStatus;
+ ISzAlloc SzAlloc;
+ Byte * destBuffer = (Byte *)pvOutBuffer;
+ Byte * srcBuffer = (Byte *)pvInBuffer;
+ SizeT destLen = *pcbOutBuffer;
+ SizeT srcLen = cbInBuffer;
+ SRes nResult;
+ BYTE LZMA_Props[] = {0x5D, 0x00, 0x00, 0x00, 0x01};
+
+ // There must be at least 0x0E bytes in the buffer
+ if(srcLen <= sizeof(LZMA_Props))
+ return 0;
+
+ // Verify the props header
+ if(memcmp(pvInBuffer, LZMA_Props, sizeof(LZMA_Props)))
+ return 0;
+
+ // Fill the callbacks in structures
+ SzAlloc.Alloc = LZMA_Callback_Alloc;
+ SzAlloc.Free = LZMA_Callback_Free;
+
+ // Perform compression
+ srcLen = cbInBuffer - sizeof(LZMA_Props);
+ nResult = LzmaDecode(destBuffer,
+ &destLen,
+ srcBuffer + sizeof(LZMA_Props),
+ &srcLen,
+ srcBuffer,
+ sizeof(LZMA_Props),
+ LZMA_FINISH_END,
+ &LzmaStatus,
+ &SzAlloc);
+ if(nResult != SZ_OK)
+ return 0;
+
+ *pcbOutBuffer = (unsigned int)destLen;
+ return 1;
+}
+
+/******************************************************************************/
+/* */
+/* Support functions for SPARSE compression (0x20) */
+/* */
+/******************************************************************************/
+
+void Compress_SPARSE(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ // Keep compilers happy
+ STORMLIB_UNUSED(pCmpType);
+ STORMLIB_UNUSED(nCmpLevel);
+
+ CompressSparse(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer);
+}
+
+int Decompress_SPARSE(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ return DecompressSparse(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer);
+}
+
+/******************************************************************************/
+/* */
+/* Support for ADPCM mono compression (0x40) */
+/* */
+/******************************************************************************/
+
+static void Compress_ADPCM_mono(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ // Prepare the compression level for Huffmann compression,
+ // which will be called as next step
+ if(0 < nCmpLevel && nCmpLevel <= 2)
+ {
+ nCmpLevel = 4;
+ *pCmpType = 6;
+ }
+ else if(nCmpLevel == 3)
+ {
+ nCmpLevel = 6;
+ *pCmpType = 8;
+ }
+ else
+ {
+ nCmpLevel = 5;
+ *pCmpType = 7;
+ }
+ *pcbOutBuffer = CompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 1, nCmpLevel);
+}
+
+static int Decompress_ADPCM_mono(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ *pcbOutBuffer = DecompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 1);
+ return 1;
+}
+
+/******************************************************************************/
+/* */
+/* Support for ADPCM stereo compression (0x80) */
+/* */
+/******************************************************************************/
+
+static void Compress_ADPCM_stereo(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, int * pCmpType, int nCmpLevel)
+{
+ // Prepare the compression level for Huffmann compression,
+ // which will be called as next step
+ if(0 < nCmpLevel && nCmpLevel <= 2)
+ {
+ nCmpLevel = 4;
+ *pCmpType = 6;
+ }
+ else if(nCmpLevel == 3)
+ {
+ nCmpLevel = 6;
+ *pCmpType = 8;
+ }
+ else
+ {
+ nCmpLevel = 5;
+ *pCmpType = 7;
+ }
+ *pcbOutBuffer = CompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 2, nCmpLevel);
+}
+
+static int Decompress_ADPCM_stereo(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ *pcbOutBuffer = DecompressADPCM(pvOutBuffer, *pcbOutBuffer, pvInBuffer, cbInBuffer, 2);
+ return 1;
+}
+
+/*****************************************************************************/
+/* */
+/* SCompImplode */
+/* */
+/*****************************************************************************/
+
+int WINAPI SCompImplode(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ int cbOutBuffer;
+
+ // Check for valid parameters
+ if(!pcbOutBuffer || *pcbOutBuffer < cbInBuffer || !pvOutBuffer || !pvInBuffer)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ // Perform the compression
+ cbOutBuffer = *pcbOutBuffer;
+ Compress_PKLIB(pvOutBuffer, &cbOutBuffer, pvInBuffer, cbInBuffer, NULL, 0);
+
+ // If the compression was unsuccessful, copy the data as-is
+ if(cbOutBuffer >= *pcbOutBuffer)
+ {
+ memcpy(pvOutBuffer, pvInBuffer, cbInBuffer);
+ cbOutBuffer = *pcbOutBuffer;
+ }
+
+ *pcbOutBuffer = cbOutBuffer;
+ return 1;
+}
+
+/*****************************************************************************/
+/* */
+/* SCompExplode */
+/* */
+/*****************************************************************************/
+
+int WINAPI SCompExplode(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ int cbOutBuffer;
+
+ // Check for valid parameters
+ if(!pcbOutBuffer || *pcbOutBuffer < cbInBuffer || !pvOutBuffer || !pvInBuffer)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ // If the input length is the same as output length, do nothing.
+ cbOutBuffer = *pcbOutBuffer;
+ if(cbInBuffer == cbOutBuffer)
+ {
+ // If the buffers are equal, don't copy anything.
+ if(pvInBuffer == pvOutBuffer)
+ return 1;
+
+ memcpy(pvOutBuffer, pvInBuffer, cbInBuffer);
+ return 1;
+ }
+
+ // Perform decompression
+ if(!Decompress_PKLIB(pvOutBuffer, &cbOutBuffer, pvInBuffer, cbInBuffer))
+ {
+ SetLastError(ERROR_FILE_CORRUPT);
+ return 0;
+ }
+
+ *pcbOutBuffer = cbOutBuffer;
+ return 1;
+}
+
+/*****************************************************************************/
+/* */
+/* SCompCompress */
+/* */
+/*****************************************************************************/
+
+// This table contains compress functions which can be applied to
+// uncompressed data. Each bit means the corresponding
+// compression method/function must be applied.
+//
+// WAVes compression Data compression
+// ------------------ -------------------
+// 1st sector - 0x08 0x08 (D, HF, W2, SC, D2)
+// Next sectors - 0x81 0x02 (W3)
+
+static TCompressTable cmp_table[] =
+{
+ {MPQ_COMPRESSION_SPARSE, Compress_SPARSE}, // Sparse compression
+ {MPQ_COMPRESSION_ADPCM_MONO, Compress_ADPCM_mono}, // IMA ADPCM mono compression
+ {MPQ_COMPRESSION_ADPCM_STEREO, Compress_ADPCM_stereo}, // IMA ADPCM stereo compression
+ {MPQ_COMPRESSION_HUFFMANN, Compress_huff}, // Huffmann compression
+ {MPQ_COMPRESSION_ZLIB, Compress_ZLIB}, // Compression with the "zlib" library
+ {MPQ_COMPRESSION_PKWARE, Compress_PKLIB}, // Compression with Pkware DCL
+ {MPQ_COMPRESSION_BZIP2, Compress_BZIP2} // Compression Bzip2 library
+};
+
+int WINAPI SCompCompress(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, unsigned uCompressionMask, int nCmpType, int nCmpLevel)
+{
+ COMPRESS CompressFuncArray[0x10]; // Array of compression functions, applied sequentially
+ unsigned char CompressByte[0x10]; // CompressByte for each method in the CompressFuncArray array
+ unsigned char * pbWorkBuffer = NULL; // Temporary storage for decompressed data
+ unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer;
+ unsigned char * pbOutput = (unsigned char *)pvOutBuffer;// Current output buffer
+ unsigned char * pbInput = (unsigned char *)pvInBuffer; // Current input buffer
+ int nCompressCount = 0;
+ int nCompressIndex = 0;
+ int nAtLeastOneCompressionDone = 0;
+ int cbOutBuffer = 0;
+ int cbInLength = cbInBuffer;
+ int nResult = 1;
+
+ // Check for valid parameters
+ if(!pcbOutBuffer || *pcbOutBuffer < cbInBuffer || !pvOutBuffer || !pvInBuffer)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ // Zero input length brings zero output length
+ if(cbInBuffer == 0)
+ {
+ *pcbOutBuffer = 0;
+ return true;
+ }
+
+ // Setup the compression function array
+ if(uCompressionMask == MPQ_COMPRESSION_LZMA)
+ {
+ CompressFuncArray[0] = Compress_LZMA;
+ CompressByte[0] = (char)uCompressionMask;
+ nCompressCount = 1;
+ }
+ else
+ {
+ // Fill the compressions array
+ for(size_t i = 0; i < (sizeof(cmp_table) / sizeof(TCompressTable)); i++)
+ {
+ // If the mask agrees, insert the compression function to the array
+ if(uCompressionMask & cmp_table[i].uMask)
+ {
+ CompressFuncArray[nCompressCount] = cmp_table[i].Compress;
+ CompressByte[nCompressCount] = (unsigned char)cmp_table[i].uMask;
+ uCompressionMask &= ~cmp_table[i].uMask;
+ nCompressCount++;
+ }
+ }
+
+ // If at least one of the compressions remaing unknown, return an error
+ if(uCompressionMask != 0)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return 0;
+ }
+ }
+
+ // If there is at least one compression, do it
+ if(nCompressCount > 0)
+ {
+ // If we need to do more than 1 compression, allocate intermediate buffer
+ if(nCompressCount > 1)
+ {
+ pbWorkBuffer = STORM_ALLOC(unsigned char, *pcbOutBuffer);
+ if(pbWorkBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return 0;
+ }
+ }
+
+ // Get the current compression index
+ nCompressIndex = nCompressCount - 1;
+
+ // Perform all compressions in the array
+ for(int i = 0; i < nCompressCount; i++)
+ {
+ // Choose the proper output buffer
+ pbOutput = (nCompressIndex & 1) ? pbWorkBuffer : pbOutBuffer;
+ nCompressIndex--;
+
+ // Perform the (next) compression
+ // Note that if the compression method is unable to compress the input data block
+ // by at least 2 bytes, we consider it as failure and will use source data instead
+ cbOutBuffer = *pcbOutBuffer - 1;
+ CompressFuncArray[i](pbOutput + 1, &cbOutBuffer, pbInput, cbInLength, &nCmpType, nCmpLevel);
+
+ // If the compression failed, we copy the input buffer as-is.
+ // Note that there is one extra byte at the end of the intermediate buffer, so it should be OK
+ if(cbOutBuffer > (cbInLength - 2))
+ {
+ memcpy(pbOutput + nAtLeastOneCompressionDone, pbInput, cbInLength);
+ cbOutBuffer = cbInLength;
+ }
+ else
+ {
+ // Remember that we have done at least one compression
+ nAtLeastOneCompressionDone = 1;
+ uCompressionMask |= CompressByte[i];
+ }
+
+ // Now point input buffer to the output buffer
+ pbInput = pbOutput + nAtLeastOneCompressionDone;
+ cbInLength = cbOutBuffer;
+ }
+
+ // If at least one compression succeeded, put the compression
+ // mask to the begin of the output buffer
+ if(nAtLeastOneCompressionDone)
+ *pbOutBuffer = (unsigned char)uCompressionMask;
+ *pcbOutBuffer = cbOutBuffer + nAtLeastOneCompressionDone;
+ }
+ else
+ {
+ memcpy(pvOutBuffer, pvInBuffer, cbInBuffer);
+ *pcbOutBuffer = cbInBuffer;
+ }
+
+ // Cleanup and return
+ if(pbWorkBuffer != NULL)
+ STORM_FREE(pbWorkBuffer);
+ return nResult;
+}
+
+/*****************************************************************************/
+/* */
+/* SCompDecompress */
+/* */
+/*****************************************************************************/
+
+// This table contains decompress functions which can be applied to
+// uncompressed data. The compression mask is stored in the first byte
+// of compressed data
+static TDecompressTable dcmp_table[] =
+{
+ {MPQ_COMPRESSION_BZIP2, Decompress_BZIP2}, // Decompression with Bzip2 library
+ {MPQ_COMPRESSION_PKWARE, Decompress_PKLIB}, // Decompression with Pkware Data Compression Library
+ {MPQ_COMPRESSION_ZLIB, Decompress_ZLIB}, // Decompression with the "zlib" library
+ {MPQ_COMPRESSION_HUFFMANN, Decompress_huff}, // Huffmann decompression
+ {MPQ_COMPRESSION_ADPCM_STEREO, Decompress_ADPCM_stereo}, // IMA ADPCM stereo decompression
+ {MPQ_COMPRESSION_ADPCM_MONO, Decompress_ADPCM_mono}, // IMA ADPCM mono decompression
+ {MPQ_COMPRESSION_SPARSE, Decompress_SPARSE} // Sparse decompression
+};
+
+int WINAPI SCompDecompress(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ unsigned char * pbWorkBuffer = NULL;
+ unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer;
+ unsigned char * pbInBuffer = (unsigned char *)pvInBuffer;
+ unsigned char * pbOutput = (unsigned char *)pvOutBuffer;
+ unsigned char * pbInput;
+ unsigned uCompressionMask; // Decompressions applied to the data
+ unsigned uCompressionCopy; // Decompressions applied to the data
+ int cbOutBuffer = *pcbOutBuffer; // Current size of the output buffer
+ int cbInLength; // Current size of the input buffer
+ int nCompressCount = 0; // Number of compressions to be applied
+ int nCompressIndex = 0;
+ int nResult = 1;
+
+ // Verify buffer sizes
+ if(cbOutBuffer < cbInBuffer || cbInBuffer < 1)
+ return 0;
+
+ // If the input length is the same as output length, do nothing.
+ if(cbOutBuffer == cbInBuffer)
+ {
+ // If the buffers are equal, don't copy anything.
+ if(pvInBuffer != pvOutBuffer)
+ memcpy(pvOutBuffer, pvInBuffer, cbInBuffer);
+ return 1;
+ }
+
+ // Get applied compression types and decrement data length
+ uCompressionMask = uCompressionCopy = (unsigned char)*pbInBuffer++;
+ cbInBuffer--;
+
+ // Get current compressed data and length of it
+ pbInput = pbInBuffer;
+ cbInLength = cbInBuffer;
+
+ // This compression function doesn't support LZMA
+ assert(uCompressionMask != MPQ_COMPRESSION_LZMA);
+
+ // Parse the compression mask
+ for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++)
+ {
+ // If the mask agrees, insert the compression function to the array
+ if(uCompressionMask & dcmp_table[i].uMask)
+ {
+ uCompressionCopy &= ~dcmp_table[i].uMask;
+ nCompressCount++;
+ }
+ }
+
+ // If at least one of the compressions remaing unknown, return an error
+ if(nCompressCount == 0 || uCompressionCopy != 0)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return 0;
+ }
+
+ // If there is more than one compression, we have to allocate extra buffer
+ if(nCompressCount > 1)
+ {
+ pbWorkBuffer = STORM_ALLOC(unsigned char, cbOutBuffer);
+ if(pbWorkBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return 0;
+ }
+ }
+
+ // Get the current compression index
+ nCompressIndex = nCompressCount - 1;
+
+ // Apply all decompressions
+ for(size_t i = 0; i < (sizeof(dcmp_table) / sizeof(TDecompressTable)); i++)
+ {
+ // Perform the (next) decompression
+ if(uCompressionMask & dcmp_table[i].uMask)
+ {
+ // Get the correct output buffer
+ pbOutput = (nCompressIndex & 1) ? pbWorkBuffer : pbOutBuffer;
+ nCompressIndex--;
+
+ // Perform the decompression
+ cbOutBuffer = *pcbOutBuffer;
+ nResult = dcmp_table[i].Decompress(pbOutput, &cbOutBuffer, pbInput, cbInLength);
+ if(nResult == 0 || cbOutBuffer == 0)
+ {
+ SetLastError(ERROR_FILE_CORRUPT);
+ nResult = 0;
+ break;
+ }
+
+ // Switch buffers
+ cbInLength = cbOutBuffer;
+ pbInput = pbOutput;
+ }
+ }
+
+ // Put the length of the decompressed data to the output buffer
+ *pcbOutBuffer = cbOutBuffer;
+
+ // Cleanup and return
+ if(pbWorkBuffer != NULL)
+ STORM_FREE(pbWorkBuffer);
+ return nResult;
+}
+
+int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ DECOMPRESS pfnDecompress1 = NULL;
+ DECOMPRESS pfnDecompress2 = NULL;
+ unsigned char * pbWorkBuffer = (unsigned char *)pvOutBuffer;
+ unsigned char * pbInBuffer = (unsigned char *)pvInBuffer;
+ int cbWorkBuffer = *pcbOutBuffer;
+ int nResult;
+ char CompressionMethod;
+
+ // Verify buffer sizes
+ if(*pcbOutBuffer < cbInBuffer || cbInBuffer < 1)
+ return 0;
+
+ // If the outputbuffer is as big as input buffer, just copy the block
+ if(*pcbOutBuffer == cbInBuffer)
+ {
+ if(pvOutBuffer != pvInBuffer)
+ memcpy(pvOutBuffer, pvInBuffer, cbInBuffer);
+ return 1;
+ }
+
+ // Get the compression methods
+ CompressionMethod = *pbInBuffer++;
+ cbInBuffer--;
+
+ // We only recognize a fixed set of compression methods
+ switch((unsigned char)CompressionMethod)
+ {
+ case MPQ_COMPRESSION_ZLIB:
+ pfnDecompress1 = Decompress_ZLIB;
+ break;
+
+ case MPQ_COMPRESSION_PKWARE:
+ pfnDecompress1 = Decompress_PKLIB;
+ break;
+
+ case MPQ_COMPRESSION_BZIP2:
+ pfnDecompress1 = Decompress_BZIP2;
+ break;
+
+ case MPQ_COMPRESSION_LZMA:
+ pfnDecompress1 = Decompress_LZMA;
+ break;
+
+ case MPQ_COMPRESSION_SPARSE:
+ pfnDecompress1 = Decompress_SPARSE;
+ break;
+
+ case (MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_ZLIB):
+ pfnDecompress1 = Decompress_ZLIB;
+ pfnDecompress2 = Decompress_SPARSE;
+ break;
+
+ case (MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_BZIP2):
+ pfnDecompress1 = Decompress_BZIP2;
+ pfnDecompress2 = Decompress_SPARSE;
+ break;
+
+ //
+ // Note: Any combination including MPQ_COMPRESSION_ADPCM_MONO,
+ // MPQ_COMPRESSION_ADPCM_STEREO or MPQ_COMPRESSION_HUFFMANN
+ // is not supported by newer MPQs.
+ //
+
+ case (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN):
+ pfnDecompress1 = Decompress_huff;
+ pfnDecompress2 = Decompress_ADPCM_mono;
+ break;
+
+ case (MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN):
+ pfnDecompress1 = Decompress_huff;
+ pfnDecompress2 = Decompress_ADPCM_stereo;
+ break;
+
+ default:
+ SetLastError(ERROR_FILE_CORRUPT);
+ return 0;
+ }
+
+ // If we have to use two decompressions, allocate temporary buffer
+ if(pfnDecompress2 != NULL)
+ {
+ pbWorkBuffer = STORM_ALLOC(unsigned char, *pcbOutBuffer);
+ if(pbWorkBuffer == NULL)
+ {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ return 0;
+ }
+ }
+
+ // Apply the first decompression method
+ nResult = pfnDecompress1(pbWorkBuffer, &cbWorkBuffer, pbInBuffer, cbInBuffer);
+
+ // Apply the second decompression method, if any
+ if(pfnDecompress2 != NULL && nResult != 0)
+ {
+ cbInBuffer = cbWorkBuffer;
+ cbWorkBuffer = *pcbOutBuffer;
+ nResult = pfnDecompress2(pvOutBuffer, &cbWorkBuffer, pbWorkBuffer, cbInBuffer);
+ }
+
+ // Supply the output buffer size
+ *pcbOutBuffer = cbWorkBuffer;
+
+ // Free temporary buffer
+ if(pbWorkBuffer != pvOutBuffer)
+ STORM_FREE(pbWorkBuffer);
+
+ if(nResult == 0)
+ SetLastError(ERROR_FILE_CORRUPT);
+ return nResult;
+}
+
+/*****************************************************************************/
+/* */
+/* File decompression for MPK archives */
+/* */
+/*****************************************************************************/
+
+int SCompDecompressMpk(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ return Decompress_LZMA_MPK(pvOutBuffer, pcbOutBuffer, pvInBuffer, cbInBuffer);
+}
+
diff --git a/dep/StormLib/src/SFileAddFile.cpp b/dep/StormLib/src/SFileAddFile.cpp
new file mode 100644
index 000000000..4572d89b8
--- /dev/null
+++ b/dep/StormLib/src/SFileAddFile.cpp
@@ -0,0 +1,1312 @@
+/*****************************************************************************/
+/* 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
+ {
+ // Attempt to allocate new file entry
+ pFileEntry = AllocateFileEntry(ha, szFileName, lcLocale, &dwHashIndex);
+ if(pFileEntry != NULL)
+ InvalidateInternalFiles(ha);
+ else
+ 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 &= (ha->dwFlags & MPQ_FLAG_WAR3_MAP) ? MPQ_FILE_VALID_FLAGS_W3X : 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/dep/StormLib/src/SFileAttributes.cpp b/dep/StormLib/src/SFileAttributes.cpp
new file mode 100644
index 000000000..d565be738
--- /dev/null
+++ b/dep/StormLib/src/SFileAttributes.cpp
@@ -0,0 +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_DEFAULT_INTERNAL)
+ 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/dep/StormLib/src/SFileCompactArchive.cpp b/dep/StormLib/src/SFileCompactArchive.cpp
new file mode 100644
index 000000000..7b3fcd6f4
--- /dev/null
+++ b/dep/StormLib/src/SFileCompactArchive.cpp
@@ -0,0 +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 TCHAR * 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 = GetNearestPowerOfTwo(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 TCHAR * 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
+ StringCopy(szTempFile, _countof(szTempFile), FileStream_GetFileName(ha->pStream));
+ StringCat(szTempFile, _countof(szTempFile), _T(".tmp"));
+
+ // 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/dep/StormLib/src/SFileCreateArchive.cpp b/dep/StormLib/src/SFileCreateArchive.cpp
new file mode 100644
index 000000000..de4ab36f9
--- /dev/null
+++ b/dep/StormLib/src/SFileCreateArchive.cpp
@@ -0,0 +1,274 @@
+/*****************************************************************************/
+/* SFileCreateArchive.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* MPQ Editing functions */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 24.03.03 1.00 Lad Splitted from SFileOpenArchive.cpp */
+/* 08.06.10 1.00 Lad Renamed to SFileCreateArchive.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+static const DWORD MpqHeaderSizes[] =
+{
+ MPQ_HEADER_SIZE_V1,
+ MPQ_HEADER_SIZE_V2,
+ MPQ_HEADER_SIZE_V3,
+ MPQ_HEADER_SIZE_V4
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static USHORT GetSectorSizeShift(DWORD dwSectorSize)
+{
+ USHORT wSectorSizeShift = 0;
+
+ while(dwSectorSize > 0x200)
+ {
+ dwSectorSize >>= 1;
+ wSectorSizeShift++;
+ }
+
+ return wSectorSizeShift;
+}
+
+static int WriteNakedMPQHeader(TMPQArchive * ha)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TMPQHeader Header;
+ DWORD dwBytesToWrite = pHeader->dwHeaderSize;
+ int nError = ERROR_SUCCESS;
+
+ // Prepare the naked MPQ header
+ memset(&Header, 0, sizeof(TMPQHeader));
+ Header.dwID = pHeader->dwID;
+ Header.dwHeaderSize = pHeader->dwHeaderSize;
+ Header.dwArchiveSize = pHeader->dwHeaderSize;
+ Header.wFormatVersion = pHeader->wFormatVersion;
+ Header.wSectorSize = pHeader->wSectorSize;
+
+ // Write it to the file
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_1);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_2);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_3);
+ BSWAP_TMPQHEADER(&Header, MPQ_FORMAT_VERSION_4);
+ if(!FileStream_Write(ha->pStream, &ha->MpqPos, &Header, dwBytesToWrite))
+ nError = GetLastError();
+
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a new MPQ archive.
+
+bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq)
+{
+ SFILE_CREATE_MPQ CreateInfo;
+
+ // Fill the create structure
+ memset(&CreateInfo, 0, sizeof(SFILE_CREATE_MPQ));
+ CreateInfo.cbSize = sizeof(SFILE_CREATE_MPQ);
+ CreateInfo.dwMpqVersion = (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_DEFAULT_INTERNAL : 0;
+ CreateInfo.dwFileFlags2 = (dwCreateFlags & MPQ_CREATE_ATTRIBUTES) ? MPQ_FILE_DEFAULT_INTERNAL : 0;
+ CreateInfo.dwFileFlags3 = (dwCreateFlags & MPQ_CREATE_SIGNATURE) ? MPQ_FILE_DEFAULT_INTERNAL : 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;
+
+ // Set the proper attribute parts
+ if((CreateInfo.dwMpqVersion >= MPQ_FORMAT_VERSION_3) && (dwCreateFlags & MPQ_CREATE_ATTRIBUTES))
+ CreateInfo.dwAttrFlags |= MPQ_ATTRIBUTE_PATCH_BIT;
+
+ // Backward compatibility: SFileCreateArchive always used to add (listfile)
+ // We would break loads of applications if we change that
+ CreateInfo.dwFileFlags1 = MPQ_FILE_DEFAULT_INTERNAL;
+
+ // Let the main function create the archive
+ return SFileCreateArchive2(szMpqName, &CreateInfo, phMpq);
+}
+
+bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq)
+{
+ TFileStream * pStream = NULL; // File stream
+ TMPQArchive * ha = NULL; // MPQ archive handle
+ TMPQHeader * pHeader;
+ ULONGLONG MpqPos = 0; // Position of MPQ header in the file
+ HANDLE hMpq = NULL;
+ DWORD dwBlockTableSize = 0; // Initial block table size
+ DWORD dwHashTableSize = 0;
+ DWORD dwReservedFiles = 0; // Number of reserved file entries
+ DWORD dwMpqFlags = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Check the parameters, if they are valid
+ if(szMpqName == NULL || *szMpqName == 0 || pCreateInfo == NULL || phMpq == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // Verify if all variables in SFILE_CREATE_MPQ are correct
+ if((pCreateInfo->cbSize == 0 || pCreateInfo->cbSize > sizeof(SFILE_CREATE_MPQ)) ||
+ (pCreateInfo->dwMpqVersion > MPQ_FORMAT_VERSION_4) ||
+ (pCreateInfo->pvUserData != NULL || pCreateInfo->cbUserData != 0) ||
+ (pCreateInfo->dwAttrFlags & ~MPQ_ATTRIBUTE_ALL) ||
+ (pCreateInfo->dwSectorSize & (pCreateInfo->dwSectorSize - 1)) ||
+ (pCreateInfo->dwRawChunkSize & (pCreateInfo->dwRawChunkSize - 1)))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // One time initialization of MPQ cryptography
+ InitializeMpqCryptography();
+
+ // We verify if the file already exists and if it's a MPQ archive.
+ // If yes, we won't allow to overwrite it.
+ if(SFileOpenArchive(szMpqName, 0, STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE | MPQ_OPEN_NO_ATTRIBUTES | MPQ_OPEN_NO_LISTFILE, &hMpq))
+ {
+ SFileCloseArchive(hMpq);
+ SetLastError(ERROR_ALREADY_EXISTS);
+ return false;
+ }
+
+ //
+ // At this point, we have to create the archive.
+ // - If the file exists, convert it to MPQ archive.
+ // - If the file doesn't exist, create new empty file
+ //
+
+ pStream = FileStream_OpenFile(szMpqName, pCreateInfo->dwStreamFlags);
+ if(pStream == NULL)
+ {
+ pStream = FileStream_CreateFile(szMpqName, pCreateInfo->dwStreamFlags);
+ if(pStream == NULL)
+ return false;
+ }
+
+ // Increment the maximum amount of files to have space for (listfile)
+ if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags1)
+ {
+ dwMpqFlags |= MPQ_FLAG_LISTFILE_NEW;
+ dwReservedFiles++;
+ }
+
+ // Increment the maximum amount of files to have space for (attributes)
+ if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags2 && pCreateInfo->dwAttrFlags)
+ {
+ dwMpqFlags |= MPQ_FLAG_ATTRIBUTES_NEW;
+ dwReservedFiles++;
+ }
+
+ // Increment the maximum amount of files to have space for (signature)
+ if(pCreateInfo->dwMaxFileCount && pCreateInfo->dwFileFlags3)
+ {
+ dwMpqFlags |= MPQ_FLAG_SIGNATURE_NEW;
+ dwReservedFiles++;
+ }
+
+ // If file count is not zero, initialize the hash table size
+ dwHashTableSize = GetNearestPowerOfTwo(pCreateInfo->dwMaxFileCount + dwReservedFiles);
+
+ // Retrieve the file size and round it up to 0x200 bytes
+ FileStream_GetSize(pStream, &MpqPos);
+ MpqPos = (MpqPos + 0x1FF) & (ULONGLONG)0xFFFFFFFFFFFFFE00ULL;
+ if(!FileStream_SetSize(pStream, MpqPos))
+ nError = GetLastError();
+
+#ifdef _DEBUG
+ // Debug code, used for testing StormLib
+// dwBlockTableSize = dwHashTableSize * 2;
+#endif
+
+ // Create the archive handle
+ if(nError == ERROR_SUCCESS)
+ {
+ if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Fill the MPQ archive handle structure
+ if(nError == ERROR_SUCCESS)
+ {
+ memset(ha, 0, sizeof(TMPQArchive));
+ ha->pfnHashString = HashStringSlash;
+ ha->pStream = pStream;
+ ha->dwSectorSize = pCreateInfo->dwSectorSize;
+ ha->UserDataPos = MpqPos;
+ ha->MpqPos = MpqPos;
+ ha->pHeader = pHeader = (TMPQHeader *)ha->HeaderData;
+ ha->dwMaxFileCount = dwHashTableSize;
+ ha->dwFileTableSize = 0;
+ ha->dwReservedFiles = dwReservedFiles;
+ ha->dwFileFlags1 = pCreateInfo->dwFileFlags1;
+ ha->dwFileFlags2 = pCreateInfo->dwFileFlags2;
+ ha->dwFileFlags3 = pCreateInfo->dwFileFlags3 ? MPQ_FILE_EXISTS : 0;
+ ha->dwAttrFlags = pCreateInfo->dwAttrFlags;
+ ha->dwFlags = dwMpqFlags | MPQ_FLAG_CHANGED;
+ pStream = NULL;
+
+ // Fill the MPQ header
+ memset(pHeader, 0, sizeof(ha->HeaderData));
+ pHeader->dwID = ID_MPQ;
+ pHeader->dwHeaderSize = MpqHeaderSizes[pCreateInfo->dwMpqVersion];
+ pHeader->dwArchiveSize = pHeader->dwHeaderSize + dwHashTableSize * sizeof(TMPQHash);
+ pHeader->wFormatVersion = (USHORT)pCreateInfo->dwMpqVersion;
+ pHeader->wSectorSize = GetSectorSizeShift(ha->dwSectorSize);
+ pHeader->dwHashTablePos = pHeader->dwHeaderSize;
+ pHeader->dwHashTableSize = dwHashTableSize;
+ pHeader->dwBlockTablePos = pHeader->dwHashTablePos + dwHashTableSize * sizeof(TMPQHash);
+ pHeader->dwBlockTableSize = dwBlockTableSize;
+
+ // For MPQs version 4 and higher, we set the size of raw data block
+ // for calculating MD5
+ if(pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_4)
+ pHeader->dwRawChunkSize = pCreateInfo->dwRawChunkSize;
+
+ // Write the naked MPQ header
+ nError = WriteNakedMPQHeader(ha);
+ }
+
+ // Create initial HET table, if the caller required an MPQ format 3.0 or newer
+ if(nError == ERROR_SUCCESS && pCreateInfo->dwMpqVersion >= MPQ_FORMAT_VERSION_3 && pCreateInfo->dwMaxFileCount != 0)
+ {
+ ha->pHetTable = CreateHetTable(ha->dwFileTableSize, 0, 0x40, NULL);
+ if(ha->pHetTable == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Create initial hash table
+ if(nError == ERROR_SUCCESS && dwHashTableSize != 0)
+ {
+ nError = CreateHashTable(ha, dwHashTableSize);
+ }
+
+ // Create initial file table
+ if(nError == ERROR_SUCCESS && ha->dwMaxFileCount != 0)
+ {
+ nError = CreateFileTable(ha, ha->dwMaxFileCount);
+ }
+
+ // Cleanup : If an error, delete all buffers and return
+ if(nError != ERROR_SUCCESS)
+ {
+ FileStream_Close(pStream);
+ FreeArchiveHandle(ha);
+ SetLastError(nError);
+ ha = NULL;
+ }
+
+ // Return the values
+ *phMpq = (HANDLE)ha;
+ return (nError == ERROR_SUCCESS);
+}
diff --git a/dep/StormLib/src/SFileExtractFile.cpp b/dep/StormLib/src/SFileExtractFile.cpp
new file mode 100644
index 000000000..cabde4927
--- /dev/null
+++ b/dep/StormLib/src/SFileExtractFile.cpp
@@ -0,0 +1,64 @@
+/*****************************************************************************/
+/* SFileExtractFile.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Simple extracting utility */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 20.06.03 1.00 Lad The first version of SFileExtractFile.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR * szExtracted, DWORD dwSearchScope)
+{
+ TFileStream * pLocalFile = NULL;
+ HANDLE hMpqFile = NULL;
+ int nError = ERROR_SUCCESS;
+
+ // Open the MPQ file
+ if(nError == ERROR_SUCCESS)
+ {
+ if(!SFileOpenFileEx(hMpq, szToExtract, dwSearchScope, &hMpqFile))
+ nError = GetLastError();
+ }
+
+ // Create the local file
+ if(nError == ERROR_SUCCESS)
+ {
+ pLocalFile = FileStream_CreateFile(szExtracted, 0);
+ if(pLocalFile == NULL)
+ nError = GetLastError();
+ }
+
+ // Copy the file's content
+ while(nError == ERROR_SUCCESS)
+ {
+ char szBuffer[0x1000];
+ DWORD dwTransferred = 0;
+
+ // dwTransferred is only set to nonzero if something has been read.
+ // nError can be ERROR_SUCCESS or ERROR_HANDLE_EOF
+ if(!SFileReadFile(hMpqFile, szBuffer, sizeof(szBuffer), &dwTransferred, NULL))
+ nError = GetLastError();
+ if(nError == ERROR_HANDLE_EOF)
+ nError = ERROR_SUCCESS;
+ if(dwTransferred == 0)
+ break;
+
+ // If something has been actually read, write it
+ if(!FileStream_Write(pLocalFile, NULL, szBuffer, dwTransferred))
+ nError = GetLastError();
+ }
+
+ // Close the files
+ if(hMpqFile != NULL)
+ SFileCloseFile(hMpqFile);
+ if(pLocalFile != NULL)
+ FileStream_Close(pLocalFile);
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
diff --git a/dep/StormLib/src/SFileFindFile.cpp b/dep/StormLib/src/SFileFindFile.cpp
new file mode 100644
index 000000000..a27cf02f7
--- /dev/null
+++ b/dep/StormLib/src/SFileFindFile.cpp
@@ -0,0 +1,479 @@
+/*****************************************************************************/
+/* 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 = ha->pfnHashString(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 = pFileEntry;
+ TFileEntry * pTempEntry;
+ char szFileName[MAX_PATH+1];
+
+ // Can't find patch entry for a file that doesn't have name
+ if(pFileEntry->szFileName != NULL && pFileEntry->szFileName[0] != 0)
+ {
+ // 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 && ha->pPatchPrefix->nLength)
+ StringCopy(szFileName, _countof(szFileName), ha->pPatchPrefix->szPatchPrefix);
+ StringCat(szFileName, _countof(szFileName), pFileEntry->szFileName);
+
+ // 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
+ // Note: This either succeeds or returns pFileEntry
+ pPatchEntry = FindPatchEntry(ha, 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
+ StringCopy(lpFindFileData->cFileName, _countof(lpFindFileData->cFileName), szFileName + nPrefixLength);
+ 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 + MPQ_BLOCK_INDEX(pHash)))
+ 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 TCHAR * 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/dep/StormLib/src/SFileGetFileInfo.cpp b/dep/StormLib/src/SFileGetFileInfo.cpp
new file mode 100644
index 000000000..226e8b8d6
--- /dev/null
+++ b/dep/StormLib/src/SFileGetFileInfo.cpp
@@ -0,0 +1,1003 @@
+/*****************************************************************************/
+/* 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) < ha->FileSize)
+ {
+ 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;
+
+ case SFileInfoCRC32:
+ hf = IsValidFileHandle(hMpqOrFile);
+ if(hf != NULL && hf->pFileEntry != NULL)
+ {
+ dwInt32Value = hf->pFileEntry->dwCrc32;
+ 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 '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);
+ StringCopy(szFileName, MAX_PATH, szStreamName);
+ }
+ nError = ERROR_SUCCESS;
+ }
+ }
+
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
diff --git a/dep/StormLib/src/SFileListFile.cpp b/dep/StormLib/src/SFileListFile.cpp
new file mode 100644
index 000000000..777bba3cf
--- /dev/null
+++ b/dep/StormLib/src/SFileListFile.cpp
@@ -0,0 +1,668 @@
+/*****************************************************************************/
+/* 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
+
+//-----------------------------------------------------------------------------
+// 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
+
+union TListFileHandle
+{
+ TFileStream * pStream; // Opened local file
+ HANDLE hFile; // Opened MPQ file
+};
+
+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
+ DWORD dwFlags; // Flags from TMPQArchive
+
+// char szWildCard[wildcard_length]; // Followed by the name mask (if any)
+// char szListFile[listfile_length]; // Followed by the listfile (if any)
+};
+
+typedef bool (*LOAD_LISTFILE)(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead);
+
+//-----------------------------------------------------------------------------
+// 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 LoadListFile_Stream(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead)
+{
+ ULONGLONG ByteOffset = 0;
+ bool bResult;
+
+ bResult = FileStream_Read(pHandle->pStream, &ByteOffset, pvBuffer, cbBuffer);
+ if(bResult)
+ *pdwBytesRead = cbBuffer;
+ return bResult;
+}
+
+static bool LoadListFile_MPQ(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead)
+{
+ return SFileReadFile(pHandle->hFile, pvBuffer, cbBuffer, pdwBytesRead, NULL);
+}
+
+static bool FreeListFileCache(TListFileCache * pCache)
+{
+ // Valid parameter check
+ if(pCache != NULL)
+ STORM_FREE(pCache);
+ return true;
+}
+
+static TListFileCache * CreateListFileCache(
+ LOAD_LISTFILE PfnLoadFile,
+ TListFileHandle * pHandle,
+ const char * szWildCard,
+ DWORD dwFileSize,
+ DWORD dwMaxSize,
+ DWORD dwFlags)
+{
+ TListFileCache * pCache = NULL;
+ size_t cchWildCard = 0;
+ DWORD dwBytesRead = 0;
+
+ // Get the amount of bytes that need to be allocated
+ if(dwFileSize == 0 || dwFileSize > dwMaxSize)
+ 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);
+ pCache->dwFlags = dwFlags;
+
+ // 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
+ PfnLoadFile(pHandle, pCache->pBegin, dwFileSize, &dwBytesRead);
+ if(dwBytesRead != 0)
+ {
+ // Allocate pointers
+ pCache->pPos = pCache->pBegin;
+ pCache->pEnd = pCache->pBegin + dwBytesRead;
+ }
+ else
+ {
+ FreeListFileCache(pCache);
+ pCache = NULL;
+ }
+ }
+
+ // Return the cache
+ return pCache;
+}
+
+static TListFileCache * CreateListFileCache(
+ HANDLE hMpq,
+ const TCHAR * szListFile,
+ const char * szWildCard,
+ DWORD dwMaxSize,
+ DWORD dwFlags)
+{
+ TListFileCache * pCache = NULL;
+ TListFileHandle ListHandle = {NULL};
+
+ // Internal listfile: hMPQ must be non NULL and szListFile must be NULL.
+ // We load the MPQ::(listfile) file
+ if(hMpq != NULL && szListFile == NULL)
+ {
+ DWORD dwFileSize = 0;
+
+ // Open the file from the MPQ
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &ListHandle.hFile))
+ {
+ // Get the file size and create the listfile cache
+ dwFileSize = SFileGetFileSize(ListHandle.hFile, NULL);
+ pCache = CreateListFileCache(LoadListFile_MPQ, &ListHandle, szWildCard, dwFileSize, dwMaxSize, dwFlags);
+
+ // Close the MPQ file
+ SFileCloseFile(ListHandle.hFile);
+ }
+
+ // Return the loaded cache
+ return pCache;
+ }
+
+ // External listfile: hMpq must be NULL and szListFile must be non-NULL.
+ // We load the file using TFileStream
+ if(hMpq == NULL && szListFile != NULL)
+ {
+ ULONGLONG FileSize = 0;
+
+ // Open the local file
+ ListHandle.pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY);
+ if(ListHandle.pStream != NULL)
+ {
+ // Verify the file size
+ FileStream_GetSize(ListHandle.pStream, &FileSize);
+ if(0 < FileSize && FileSize < MAX_LISTFILE_SIZE)
+ {
+ pCache = CreateListFileCache(LoadListFile_Stream, &ListHandle, szWildCard, (DWORD)FileSize, dwMaxSize, dwFlags);
+ }
+
+ // Close the stream
+ FileStream_Close(ListHandle.pStream);
+ }
+
+ // Return the loaded cache
+ return pCache;
+ }
+
+ // This combination should never happen
+ SetLastError(ERROR_INVALID_PARAMETER);
+ assert(false);
+ return NULL;
+}
+
+#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;
+
+ // Skip newlines. Keep spaces and tabs, as they can be a legal part of the file name
+ while(pCache->pPos < pCache->pEnd && (pCache->pPos[0] == 0x0A || pCache->pPos[0] == 0x0D))
+ pCache->pPos++;
+
+ // Set the line begin and end
+ if(pCache->pPos >= pCache->pEnd)
+ return NULL;
+ pbLineBegin = pbLineEnd = pCache->pPos;
+
+ // Find the end of the line
+ while(pCache->pPos < pCache->pEnd && pCache->pPos[0] != 0x0A && pCache->pPos[0] != 0x0D)
+ pCache->pPos++;
+
+ // Remember the end of the line
+ 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 + MPQ_BLOCK_INDEX(pHash), 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_DEFAULT_INTERNAL)
+ 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 hMpq,
+ const TCHAR * szListFile,
+ DWORD dwMaxSize)
+{
+ TListFileCache * pCache = NULL;
+
+ // Create the listfile cache for that file
+ pCache = CreateListFileCache(hMpq, szListFile, NULL, dwMaxSize, ha->dwFlags);
+ 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 SFileAddInternalListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq)
+{
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+ LCID lcSaveLocale = lcFileLocale;
+ DWORD dwMaxSize = MAX_LISTFILE_SIZE;
+ int nError = ERROR_SUCCESS;
+
+ // If there is hash table, we need to support multiple listfiles
+ // with different locales (BrooDat.mpq)
+ if(ha->pHashTable != NULL)
+ {
+ // If the archive is a malformed map, ignore too large listfiles
+ if(ha->dwFlags & MPQ_FLAG_MALFORMED)
+ dwMaxSize = 0x40000;
+
+ pFirstHash = pHash = GetFirstHashEntry(ha, LISTFILE_NAME);
+ while(nError == ERROR_SUCCESS && pHash != NULL)
+ {
+ // Set the prefered locale to that from list file
+ SFileSetLocale(pHash->lcLocale);
+
+ // Add that listfile
+ nError = SFileAddArbitraryListFile(ha, hMpq, NULL, dwMaxSize);
+
+ // Move to the next hash
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // Restore the original locale
+ SFileSetLocale(lcSaveLocale);
+ }
+ else
+ {
+ // Add the single listfile
+ nError = SFileAddArbitraryListFile(ha, hMpq, NULL, dwMaxSize);
+ }
+
+ // 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 TCHAR * 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 = SFileAddArbitraryListFile(ha, NULL, szListFile, MAX_LISTFILE_SIZE);
+ 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 TCHAR * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
+{
+ TListFileCache * pCache = NULL;
+
+ // Initialize the structure with zeros
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+
+ // Open the local/internal listfile
+ pCache = CreateListFileCache(hMpq, szListFile, szMask, 0, 0);
+ if(pCache != NULL)
+ {
+ 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/dep/StormLib/src/SFileOpenArchive.cpp b/dep/StormLib/src/SFileOpenArchive.cpp
new file mode 100644
index 000000000..bba0aad95
--- /dev/null
+++ b/dep/StormLib/src/SFileOpenArchive.cpp
@@ -0,0 +1,600 @@
+/*****************************************************************************/
+/* SFileOpenArchive.cpp Copyright Ladislav Zezula 1999 */
+/* */
+/* Author : Ladislav Zezula */
+/* E-mail : ladik@zezula.net */
+/* WWW : www.zezula.net */
+/*---------------------------------------------------------------------------*/
+/* Archive functions of Storm.dll */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.xx 1.00 Lad The first version of SFileOpenArchive.cpp */
+/* 19.11.03 1.01 Dan Big endian handling */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+#define HEADER_SEARCH_BUFFER_SIZE 0x1000
+
+/*****************************************************************************/
+/* Local functions */
+/*****************************************************************************/
+
+static bool IsAviFile(DWORD * HeaderData)
+{
+ DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderData[0]);
+ DWORD DwordValue2 = BSWAP_INT32_UNSIGNED(HeaderData[2]);
+ DWORD DwordValue3 = BSWAP_INT32_UNSIGNED(HeaderData[3]);
+
+ // Test for 'RIFF', 'AVI ' or 'LIST'
+ return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C);
+}
+
+static bool IsWarcraft3Map(DWORD * HeaderData)
+{
+ DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderData[0]);
+ DWORD DwordValue1 = BSWAP_INT32_UNSIGNED(HeaderData[1]);
+
+ return (DwordValue0 == 0x57334D48 && DwordValue1 == 0x00000000);
+}
+
+static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData)
+{
+ TMPQUserData * pUserData;
+
+ // BSWAP the source data and copy them to our buffer
+ BSWAP_ARRAY32_UNSIGNED(&pvUserData, sizeof(TMPQUserData));
+ pUserData = (TMPQUserData *)pvUserData;
+
+ // Check the sizes
+ if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs)
+ {
+ // Move to the position given by the userdata
+ ByteOffset += pUserData->dwHeaderOffs;
+
+ // The MPQ header should be within range of the file size
+ if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize)
+ {
+ // Note: We should verify if there is the MPQ header.
+ // However, the header could be at any position below that
+ // that is multiplier of 0x200
+ return (TMPQUserData *)pvUserData;
+ }
+ }
+
+ return NULL;
+}
+
+// This function gets the right positions of the hash table and the block table.
+static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ ULONGLONG ByteOffset;
+
+ // Check the begin of HET table
+ if(pHeader->HetTablePos64)
+ {
+ ByteOffset = ha->MpqPos + pHeader->HetTablePos64;
+ if(ByteOffset > FileSize)
+ return ERROR_BAD_FORMAT;
+ }
+
+ // Check the begin of BET table
+ if(pHeader->BetTablePos64)
+ {
+ ByteOffset = ha->MpqPos + pHeader->BetTablePos64;
+ if(ByteOffset > FileSize)
+ return ERROR_BAD_FORMAT;
+ }
+
+ // Check the begin of hash table
+ if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos)
+ {
+ ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos));
+ if(ByteOffset > FileSize)
+ return ERROR_BAD_FORMAT;
+ }
+
+ // Check the begin of block table
+ if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos)
+ {
+ ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos));
+ if(ByteOffset > FileSize)
+ return ERROR_BAD_FORMAT;
+ }
+
+ // Check the begin of hi-block table
+ if(pHeader->HiBlockTablePos64 != 0)
+ {
+ ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64;
+ if(ByteOffset > FileSize)
+ return ERROR_BAD_FORMAT;
+ }
+
+ // All OK.
+ return ERROR_SUCCESS;
+}
+
+/*****************************************************************************/
+/* Public functions */
+/*****************************************************************************/
+
+//-----------------------------------------------------------------------------
+// SFileGetLocale and SFileSetLocale
+// Set the locale for all newly opened files
+
+LCID WINAPI SFileGetLocale()
+{
+ return lcFileLocale;
+}
+
+LCID WINAPI SFileSetLocale(LCID lcNewLocale)
+{
+ lcFileLocale = lcNewLocale;
+ return lcFileLocale;
+}
+
+//-----------------------------------------------------------------------------
+// SFileOpenArchive
+//
+// szFileName - MPQ archive file name to open
+// dwPriority - When SFileOpenFileEx called, this contains the search priority for searched archives
+// dwFlags - See MPQ_OPEN_XXX in StormLib.h
+// phMpq - Pointer to store open archive handle
+
+bool WINAPI SFileOpenArchive(
+ const TCHAR * szMpqName,
+ DWORD dwPriority,
+ DWORD dwFlags,
+ HANDLE * phMpq)
+{
+ TMPQUserData * pUserData;
+ TFileStream * pStream = NULL; // Open file stream
+ TMPQArchive * ha = NULL; // Archive handle
+ TFileEntry * pFileEntry;
+ ULONGLONG FileSize = 0; // Size of the file
+ LPBYTE pbHeaderBuffer = NULL; // Buffer for searching MPQ header
+ DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK);
+ bool bIsWarcraft3Map = false;
+ int nError = ERROR_SUCCESS;
+
+ // Verify the parameters
+ if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // One time initialization of MPQ cryptography
+ InitializeMpqCryptography();
+
+ // If not forcing MPQ v 1.0, also use file bitmap
+ dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP;
+
+ // Open the MPQ archive file
+ pStream = FileStream_OpenFile(szMpqName, dwStreamFlags);
+ if(pStream == NULL)
+ return false;
+
+ // Check the file size. There must be at least 0x20 bytes
+ if(nError == ERROR_SUCCESS)
+ {
+ FileStream_GetSize(pStream, &FileSize);
+ if(FileSize < MPQ_HEADER_SIZE_V1)
+ nError = ERROR_BAD_FORMAT;
+ }
+
+ // Allocate the MPQhandle
+ if(nError == ERROR_SUCCESS)
+ {
+ if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Allocate buffer for searching MPQ header
+ if(nError == ERROR_SUCCESS)
+ {
+ pbHeaderBuffer = STORM_ALLOC(BYTE, HEADER_SEARCH_BUFFER_SIZE);
+ if(pbHeaderBuffer == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Find the position of MPQ header
+ if(nError == ERROR_SUCCESS)
+ {
+ ULONGLONG SearchOffset = 0;
+ ULONGLONG EndOfSearch = FileSize;
+ DWORD dwStrmFlags = 0;
+ DWORD dwHeaderSize;
+ DWORD dwHeaderID;
+ bool bSearchComplete = false;
+
+ memset(ha, 0, sizeof(TMPQArchive));
+ ha->pfnHashString = HashStringSlash;
+ ha->pStream = pStream;
+ pStream = NULL;
+
+ // Set the archive read only if the stream is read-only
+ FileStream_GetFlags(ha->pStream, &dwStrmFlags);
+ ha->dwFlags |= (dwStrmFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0;
+
+ // Also remember if we shall check sector CRCs when reading file
+ ha->dwFlags |= (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ? MPQ_FLAG_CHECK_SECTOR_CRC : 0;
+
+ // Also remember if this MPQ is a patch
+ ha->dwFlags |= (dwFlags & MPQ_OPEN_PATCH) ? MPQ_FLAG_PATCH : 0;
+
+ // Limit the header searching to about 130 MB of data
+ if(EndOfSearch > 0x08000000)
+ EndOfSearch = 0x08000000;
+
+ // Find the offset of MPQ header within the file
+ while(bSearchComplete == false && SearchOffset < EndOfSearch)
+ {
+ // Always read at least 0x1000 bytes for performance.
+ // This is what Storm.dll (2002) does.
+ DWORD dwBytesAvailable = HEADER_SEARCH_BUFFER_SIZE;
+ DWORD dwInBufferOffset = 0;
+
+ // Cut the bytes available, if needed
+ if((FileSize - SearchOffset) < HEADER_SEARCH_BUFFER_SIZE)
+ dwBytesAvailable = (DWORD)(FileSize - SearchOffset);
+
+ // Read the eventual MPQ header
+ if(!FileStream_Read(ha->pStream, &SearchOffset, pbHeaderBuffer, dwBytesAvailable))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // There are AVI files from Warcraft III with 'MPQ' extension.
+ if(SearchOffset == 0)
+ {
+ if(IsAviFile((DWORD *)pbHeaderBuffer))
+ {
+ nError = ERROR_AVI_FILE;
+ break;
+ }
+
+ bIsWarcraft3Map = IsWarcraft3Map((DWORD *)pbHeaderBuffer);
+ }
+
+ // Search the header buffer
+ while(dwInBufferOffset < dwBytesAvailable)
+ {
+ // Copy the data from the potential header buffer to the MPQ header
+ memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData));
+
+ // If there is the MPQ user data, process it
+ // Note that Warcraft III does not check for user data, which is abused by many map protectors
+ dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]);
+ if(bIsWarcraft3Map == false && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
+ {
+ if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA)
+ {
+ // Verify if this looks like a valid user data
+ pUserData = IsValidMpqUserData(SearchOffset, FileSize, ha->HeaderData);
+ if(pUserData != NULL)
+ {
+ // Fill the user data header
+ ha->UserDataPos = SearchOffset;
+ ha->pUserData = &ha->UserData;
+ memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData));
+
+ // Continue searching from that position
+ SearchOffset += ha->pUserData->dwHeaderOffs;
+ break;
+ }
+ }
+ }
+
+ // There must be MPQ header signature. Note that STORM.dll from Warcraft III actually
+ // tests the MPQ header size. It must be at least 0x20 bytes in order to load it
+ // Abused by Spazzler Map protector. Note that the size check is not present
+ // in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway.
+ dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]);
+ if(dwHeaderID == ID_MPQ && dwHeaderSize >= MPQ_HEADER_SIZE_V1)
+ {
+ // Now convert the header to version 4
+ nError = ConvertMpqHeaderToFormat4(ha, SearchOffset, FileSize, dwFlags, bIsWarcraft3Map);
+ bSearchComplete = true;
+ break;
+ }
+
+ // Check for MPK archives (Longwu Online - MPQ fork)
+ if(bIsWarcraft3Map == false && dwHeaderID == ID_MPK)
+ {
+ // Now convert the MPK header to MPQ Header version 4
+ nError = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags);
+ bSearchComplete = true;
+ break;
+ }
+
+ // If searching for the MPQ header is disabled, return an error
+ if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH)
+ {
+ nError = ERROR_NOT_SUPPORTED;
+ bSearchComplete = true;
+ break;
+ }
+
+ // Move the pointers
+ SearchOffset += 0x200;
+ dwInBufferOffset += 0x200;
+ }
+ }
+
+ // Did we identify one of the supported headers?
+ if(nError == ERROR_SUCCESS)
+ {
+ // Set the user data position to the MPQ header, if none
+ if(ha->pUserData == NULL)
+ ha->UserDataPos = SearchOffset;
+
+ // Set the position of the MPQ header
+ ha->pHeader = (TMPQHeader *)ha->HeaderData;
+ ha->MpqPos = SearchOffset;
+ ha->FileSize = FileSize;
+
+ // Sector size must be nonzero.
+ if(SearchOffset >= FileSize || ha->pHeader->wSectorSize == 0)
+ nError = ERROR_BAD_FORMAT;
+ }
+ }
+
+ // Fix table positions according to format
+ if(nError == ERROR_SUCCESS)
+ {
+ // Dump the header
+// DumpMpqHeader(ha->pHeader);
+
+ // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data,
+ // and ignores the MPQ format version as well. The trick is to
+ // fake MPQ format 2, with an improper hi-word position of hash table and block table
+ // We can overcome such protectors by forcing opening the archive as MPQ v 1.0
+ if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
+ {
+ ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1;
+ ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ ha->pUserData = NULL;
+ }
+
+ // Anti-overflow. If the hash table size in the header is
+ // higher than 0x10000000, it would overflow in 32-bit version
+ // Observed in the malformed Warcraft III maps
+ // Example map: MPQ_2016_v1_ProtectedMap_TableSizeOverflow.w3x
+ ha->pHeader->dwBlockTableSize = (ha->pHeader->dwBlockTableSize & BLOCK_INDEX_MASK);
+ ha->pHeader->dwHashTableSize = (ha->pHeader->dwHashTableSize & BLOCK_INDEX_MASK);
+
+ // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode
+ if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES))
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+
+ // Check if the caller wants to force adding listfile
+ if(dwFlags & MPQ_OPEN_FORCE_LISTFILE)
+ ha->dwFlags |= MPQ_FLAG_LISTFILE_FORCE;
+
+ // Remember whether whis is a map for Warcraft III
+ if(bIsWarcraft3Map)
+ ha->dwFlags |= MPQ_FLAG_WAR3_MAP;
+
+ // Set the size of file sector
+ ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize);
+
+ // Verify if any of the tables doesn't start beyond the end of the file
+ nError = VerifyMpqTablePositions(ha, FileSize);
+ }
+
+ // Read the hash table. Ignore the result, as hash table is no longer required
+ // Read HET table. Ignore the result, as HET table is no longer required
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = LoadAnyHashTable(ha);
+ }
+
+ // Now, build the file table. It will be built by combining
+ // the block table, BET table, hi-block table, (attributes) and (listfile).
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = BuildFileTable(ha);
+ }
+
+ // Load the internal listfile and include it to the file table
+ if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0)
+ {
+ // Quick check for (listfile)
+ pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ // Ignore result of the operation. (listfile) is optional.
+ SFileAddListFile((HANDLE)ha, NULL);
+ ha->dwFileFlags1 = pFileEntry->dwFlags;
+ }
+ }
+
+ // Load the "(attributes)" file and merge it to the file table
+ if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0 && (ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0)
+ {
+ // Quick check for (attributes)
+ pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ // Ignore result of the operation. (attributes) is optional.
+ SAttrLoadAttributes(ha);
+ ha->dwFileFlags2 = pFileEntry->dwFlags;
+ }
+ }
+
+ // Remember whether the archive has weak signature. Only for MPQs format 1.0.
+ if(nError == ERROR_SUCCESS)
+ {
+ // Quick check for (signature)
+ pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ {
+ // Just remember that the archive is weak-signed
+ assert((pFileEntry->dwFlags & MPQ_FILE_EXISTS) != 0);
+ ha->dwFileFlags3 = pFileEntry->dwFlags;
+ }
+
+ // Finally, set the MPQ_FLAG_READ_ONLY if the MPQ was found malformed
+ ha->dwFlags |= (ha->dwFlags & MPQ_FLAG_MALFORMED) ? MPQ_FLAG_READ_ONLY : 0;
+ }
+
+ // Cleanup and exit
+ if(nError != ERROR_SUCCESS)
+ {
+ FileStream_Close(pStream);
+ FreeArchiveHandle(ha);
+ SetLastError(nError);
+ ha = NULL;
+ }
+
+ // Free the header buffer
+ if(pbHeaderBuffer != NULL)
+ STORM_FREE(pbHeaderBuffer);
+ if(phMpq != NULL)
+ *phMpq = ha;
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// bool WINAPI SFileSetDownloadCallback(HANDLE, SFILE_DOWNLOAD_CALLBACK, void *);
+//
+// Sets a callback that is called when content is downloaded from the master MPQ
+//
+
+bool WINAPI SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK DownloadCB, void * pvUserData)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ // Do nothing if 'hMpq' is bad parameter
+ if(!IsValidMpqHandle(hMpq))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ return FileStream_SetCallback(ha->pStream, DownloadCB, pvUserData);
+}
+
+//-----------------------------------------------------------------------------
+// bool SFileFlushArchive(HANDLE hMpq)
+//
+// Saves all dirty data into MPQ archive.
+// Has similar effect like SFileCloseArchive, but the archive is not closed.
+// Use on clients who keep MPQ archive open even for write operations,
+// and terminating without calling SFileCloseArchive might corrupt the archive.
+//
+
+bool WINAPI SFileFlushArchive(HANDLE hMpq)
+{
+ TMPQArchive * ha;
+ int nResultError = ERROR_SUCCESS;
+ int nError;
+
+ // Do nothing if 'hMpq' is bad parameter
+ if((ha = IsValidMpqHandle(hMpq)) == NULL)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ // Only if the MPQ was changed
+ if(ha->dwFlags & MPQ_FLAG_CHANGED)
+ {
+ // Indicate that we are saving MPQ internal structures
+ ha->dwFlags |= MPQ_FLAG_SAVING_TABLES;
+
+ // Defragment the file table. This will allow us to put the internal files to the end
+ DefragmentFileTable(ha);
+
+ //
+ // Create each internal file
+ // Note that the (signature) file is usually before (listfile) in the file table
+ //
+
+ if(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW)
+ {
+ nError = SSignFileCreate(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+ }
+
+ if(ha->dwFlags & (MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_FORCE))
+ {
+ nError = SListFileSaveToMpq(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+ }
+
+ if(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW)
+ {
+ nError = SAttrFileSaveToMpq(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+ }
+
+ // Save HET table, BET table, hash table, block table, hi-block table
+ if(ha->dwFlags & MPQ_FLAG_CHANGED)
+ {
+ // Rebuild the HET table
+ if(ha->pHetTable != NULL)
+ RebuildHetTable(ha);
+
+ // Save all MPQ tables first
+ nError = SaveMPQTables(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+
+ // If the archive has weak signature, we need to finish it
+ if(ha->dwFileFlags3 != 0)
+ {
+ nError = SSignFileFinish(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+ }
+ }
+
+ // We are no longer saving internal MPQ structures
+ ha->dwFlags &= ~MPQ_FLAG_SAVING_TABLES;
+ }
+
+ // Return the error
+ if(nResultError != ERROR_SUCCESS)
+ SetLastError(nResultError);
+ return (nResultError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// bool SFileCloseArchive(HANDLE hMpq);
+//
+
+bool WINAPI SFileCloseArchive(HANDLE hMpq)
+{
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
+ bool bResult = false;
+
+ // Only if the handle is valid
+ if(ha == NULL)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ // Invalidate the add file callback so it won't be called
+ // when saving (listfile) and (attributes)
+ ha->pfnAddFileCB = NULL;
+ ha->pvAddFileUserData = NULL;
+
+ // Flush all unsaved data to the storage
+ bResult = SFileFlushArchive(hMpq);
+
+ // Free all memory used by MPQ archive
+ FreeArchiveHandle(ha);
+ return bResult;
+}
diff --git a/dep/StormLib/src/SFileOpenFileEx.cpp b/dep/StormLib/src/SFileOpenFileEx.cpp
new file mode 100644
index 000000000..72eb15d9f
--- /dev/null
+++ b/dep/StormLib/src/SFileOpenFileEx.cpp
@@ -0,0 +1,398 @@
+/*****************************************************************************/
+/* SFileOpenFileEx.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Description : */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.99 1.00 Lad The first version of SFileOpenFileEx.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+/*****************************************************************************/
+/* Local functions */
+/*****************************************************************************/
+
+static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex)
+{
+ TMPQHash * pHashTableEnd;
+ TMPQHash * pHash;
+ DWORD dwFirstIndex = HASH_ENTRY_FREE;
+
+ // Should only be called if the archive has hash table
+ assert(ha->pHashTable != NULL);
+
+ // Multiple hash table entries can point to the file table entry.
+ // We need to search all of them
+ pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
+ for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
+ {
+ if(MPQ_BLOCK_INDEX(pHash) == dwFileIndex)
+ {
+ // Duplicate hash entry found
+ if(dwFirstIndex != HASH_ENTRY_FREE)
+ return HASH_ENTRY_FREE;
+ dwFirstIndex = (DWORD)(pHash - ha->pHashTable);
+ }
+ }
+
+ // Return the hash table entry index
+ return dwFirstIndex;
+}
+
+static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
+{
+ TMPQNamePrefix * pPrefix;
+
+ // Are there patches in the current MPQ?
+ if(ha->dwFlags & MPQ_FLAG_PATCH)
+ {
+ // The patch prefix must be already known here
+ assert(ha->pPatchPrefix != NULL);
+ pPrefix = ha->pPatchPrefix;
+
+ // The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY"
+ // We need to remove the "OldWorld\\" prefix
+ if(!_strnicmp(szFileName, "OldWorld\\", 9))
+ szFileName += 9;
+
+ // Create the file name from the known patch entry
+ memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength);
+ strcpy(szBuffer + pPrefix->nLength, szFileName);
+ szFileName = szBuffer;
+ }
+
+ return szFileName;
+}
+
+static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile)
+{
+ TFileStream * pStream;
+ TMPQFile * hf = NULL;
+ TCHAR szFileNameT[MAX_PATH];
+
+ // Convert the file name to UNICODE (if needed)
+ StringCopy(szFileNameT, _countof(szFileNameT), szFileName);
+
+ // Open the file and create the TMPQFile structure
+ pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY);
+ if(pStream != NULL)
+ {
+ // Allocate and initialize file handle
+ hf = CreateFileHandle(NULL, NULL);
+ if(hf != NULL)
+ {
+ hf->pStream = pStream;
+ *PtrFile = hf;
+ return true;
+ }
+ else
+ {
+ FileStream_Close(pStream);
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ }
+ }
+ *PtrFile = NULL;
+ return false;
+}
+
+bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile)
+{
+ TMPQArchive * haBase = NULL;
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TFileEntry * pFileEntry;
+ TMPQFile * hfPatch; // Pointer to patch file
+ TMPQFile * hfBase = NULL; // Pointer to base open file
+ TMPQFile * hf = NULL;
+ HANDLE hPatchFile;
+ char szNameBuffer[MAX_PATH];
+
+ // First of all, find the latest archive where the file is in base version
+ // (i.e. where the original, unpatched version of the file exists)
+ while(ha != NULL)
+ {
+ // If the file is there, then we remember the archive
+ pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL);
+ if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
+ haBase = ha;
+
+ // Move to the patch archive
+ ha = ha->haPatch;
+ }
+
+ // If we couldn't find the base file in any of the patches, it doesn't exist
+ if((ha = haBase) == NULL)
+ {
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return false;
+ }
+
+ // Now open the base file
+ if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
+ {
+ // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
+ assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0);
+ hf = hfBase;
+
+ // Now open all patches and attach them on top of the base file
+ for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch)
+ {
+ // Prepare the file name with a correct prefix
+ if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
+ {
+ // Remember the new version
+ hfPatch = (TMPQFile *)hPatchFile;
+
+ // We should not find patch file
+ assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0);
+
+ // Attach the patch to the base file
+ hf->hfPatch = hfPatch;
+ hf = hfPatch;
+ }
+ }
+ }
+
+ // Give the updated base MPQ
+ if(PtrFile != NULL)
+ *PtrFile = (HANDLE)hfBase;
+ return (hfBase != NULL);
+}
+
+/*****************************************************************************/
+/* Public functions */
+/*****************************************************************************/
+
+//-----------------------------------------------------------------------------
+// SFileEnumLocales enums all locale versions within MPQ.
+// Functions fills all available language identifiers on a file into the buffer
+// pointed by plcLocales. There must be enough entries to copy the localed,
+// otherwise the function returns ERROR_INSUFFICIENT_BUFFER.
+
+int WINAPI SFileEnumLocales(
+ HANDLE hMpq,
+ const char * szFileName,
+ LCID * PtrLocales,
+ LPDWORD PtrMaxLocales,
+ DWORD dwSearchScope)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+ DWORD dwFileIndex = 0;
+ DWORD dwMaxLocales;
+ DWORD dwLocales = 0;
+
+ // Test the parameters
+ if(!IsValidMpqHandle(hMpq))
+ return ERROR_INVALID_HANDLE;
+ if(szFileName == NULL || *szFileName == 0)
+ return ERROR_INVALID_PARAMETER;
+ if(ha->pHashTable == NULL)
+ return ERROR_NOT_SUPPORTED;
+ if(PtrMaxLocales == NULL)
+ return ERROR_INVALID_PARAMETER;
+ if(IsPseudoFileName(szFileName, &dwFileIndex))
+ return ERROR_INVALID_PARAMETER;
+
+ // Keep compiler happy
+ dwMaxLocales = PtrMaxLocales[0];
+
+ // Parse all files with that name
+ pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
+ while(pHash != NULL)
+ {
+ // Put the locales to the buffer
+ if(PtrLocales != NULL && dwLocales < dwMaxLocales)
+ *PtrLocales++ = pHash->lcLocale;
+ dwLocales++;
+
+ // Get the next locale
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // Give the caller the number of locales and return
+ PtrMaxLocales[0] = dwLocales;
+ return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER;
+}
+
+//-----------------------------------------------------------------------------
+// SFileOpenFileEx
+//
+// hMpq - Handle of opened MPQ archive
+// szFileName - Name of file to open
+// dwSearchScope - Where to search
+// PtrFile - Pointer to store opened file handle
+
+bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile)
+{
+ TMPQArchive * ha = IsValidMpqHandle(hMpq);
+ TFileEntry * pFileEntry = NULL;
+ TMPQFile * hf = NULL;
+ DWORD dwHashIndex = HASH_ENTRY_FREE;
+ DWORD dwFileIndex = 0;
+ bool bOpenByIndex = false;
+ int nError = ERROR_SUCCESS;
+
+ // Don't accept NULL pointer to file handle
+ if(szFileName == NULL || *szFileName == 0)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // When opening a file from MPQ, the handle must be valid
+ if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL)
+ nError = ERROR_INVALID_HANDLE;
+
+ // When not checking for existence, the pointer to file handle must be valid
+ if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // Prepare the file opening
+ if(nError == ERROR_SUCCESS)
+ {
+ switch(dwSearchScope)
+ {
+ case SFILE_OPEN_FROM_MPQ:
+ case SFILE_OPEN_BASE_FILE:
+ case SFILE_OPEN_CHECK_EXISTS:
+
+ // If this MPQ has no patches, open the file from this MPQ directly
+ if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE)
+ {
+ pFileEntry = GetFileEntryLocale2(ha, szFileName, lcFileLocale, &dwHashIndex);
+ }
+
+ // If this MPQ is a patched archive, open the file as patched
+ else
+ {
+ return OpenPatchedFile(hMpq, szFileName, PtrFile);
+ }
+ break;
+
+ case SFILE_OPEN_ANY_LOCALE:
+
+ // This open option is reserved for opening MPQ internal listfile.
+ // No argument validation. Tries to open file with neutral locale first,
+ // then any other available.
+ pFileEntry = GetFileEntryLocale2(ha, szFileName, 0, &dwHashIndex);
+ break;
+
+ case SFILE_OPEN_LOCAL_FILE:
+
+ // Open a local file
+ return OpenLocalFile(szFileName, PtrFile);
+
+ default:
+
+ // Don't accept any other value
+ nError = ERROR_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ // Check whether the file really exists in the MPQ
+ if(nError == ERROR_SUCCESS)
+ {
+ if(pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
+ {
+ // Check the pseudo-file name
+ if((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true)
+ {
+ // Get the file entry for the file
+ if(dwFileIndex < ha->dwFileTableSize)
+ {
+ pFileEntry = ha->pFileTable + dwFileIndex;
+ }
+ }
+
+ if(pFileEntry == NULL)
+ {
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+ }
+
+ // Ignore unknown loading flags (example: MPQ_2016_v1_WME4_4.w3x)
+// if(pFileEntry != NULL && pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS)
+// nError = ERROR_NOT_SUPPORTED;
+ }
+
+ // Did the caller just wanted to know if the file exists?
+ if(nError == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS)
+ {
+ // Allocate file handle
+ hf = CreateFileHandle(ha, pFileEntry);
+ if(hf != NULL)
+ {
+ // Get the hash index for the file
+ if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE)
+ dwHashIndex = FindHashIndex(ha, dwFileIndex);
+ if(dwHashIndex != HASH_ENTRY_FREE)
+ hf->pHashEntry = ha->pHashTable + dwHashIndex;
+ hf->dwHashIndex = dwHashIndex;
+
+ // If the MPQ has sector CRC enabled, enable if for the file
+ if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC)
+ hf->bCheckSectorCRCs = true;
+
+ // If we know the real file name, copy it to the file entry
+ if(bOpenByIndex == false)
+ {
+ // If there is no file name yet, allocate it
+ AllocateFileName(ha, pFileEntry, szFileName);
+
+ // If the file is encrypted, we should detect the file key
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ hf->dwFileKey = DecryptFileKey(szFileName,
+ pFileEntry->ByteOffset,
+ pFileEntry->dwFileSize,
+ pFileEntry->dwFlags);
+ }
+ }
+ }
+ else
+ {
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Give the file entry
+ if(PtrFile != NULL)
+ PtrFile[0] = hf;
+
+ // Return error code
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// SFileHasFile
+//
+// hMpq - Handle of opened MPQ archive
+// szFileName - Name of file to look for
+
+bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
+{
+ return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL);
+}
+
+//-----------------------------------------------------------------------------
+// bool WINAPI SFileCloseFile(HANDLE hFile);
+
+bool WINAPI SFileCloseFile(HANDLE hFile)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+
+ if(!IsValidFileHandle(hFile))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ // Free the structure
+ FreeFileHandle(hf);
+ return true;
+}
diff --git a/dep/StormLib/src/SFilePatchArchives.cpp b/dep/StormLib/src/SFilePatchArchives.cpp
new file mode 100644
index 000000000..dc16631bc
--- /dev/null
+++ b/dep/StormLib/src/SFilePatchArchives.cpp
@@ -0,0 +1,1175 @@
+/*****************************************************************************/
+/* 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 MAX_SC2_PATCH_PREFIX 0x80
+
+#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;
+ if((dwNewOffset + dwCombineSize) > dwNewSize || (dwNewOffset + dwCombineSize) < dwNewOffset)
+ return ERROR_FILE_CORRUPT;
+
+ // 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 length of the patch prefix was not entered, find it
+ // Not that the patch prefix must always begin with backslash
+ if(szFileName != NULL && nLength == 0)
+ nLength = strlen(szFileName);
+
+ // Create the patch prefix
+ pNewPrefix = (TMPQNamePrefix *)STORM_ALLOC(BYTE, sizeof(TMPQNamePrefix) + nLength + 1);
+ if(pNewPrefix != NULL)
+ {
+ // Fill the name prefix. Also add the backslash
+ if(szFileName && nLength)
+ {
+ memcpy(pNewPrefix->szPatchPrefix, szFileName, nLength);
+ if(pNewPrefix->szPatchPrefix[nLength - 1] != '\\')
+ pNewPrefix->szPatchPrefix[nLength++] = '\\';
+ }
+
+ // Terminate the string and fill the length
+ pNewPrefix->szPatchPrefix[nLength] = 0;
+ pNewPrefix->nLength = nLength;
+ }
+
+ ha->pPatchPrefix = pNewPrefix;
+ return (pNewPrefix != NULL);
+}
+
+static bool CheckAndCreatePatchPrefix(TMPQArchive * ha, const char * szPatchPrefix, size_t nLength)
+{
+ char szTempName[MAX_SC2_PATCH_PREFIX + 0x41];
+ bool bResult = false;
+
+ // Prepare the patch file name
+ if(nLength > MAX_SC2_PATCH_PREFIX)
+ return false;
+
+ // Prepare the patched file name
+ memcpy(szTempName, szPatchPrefix, nLength);
+ memcpy(&szTempName[nLength], "\\(patch_metadata)", 18);
+
+ // Verifywhether that file exists
+ if(GetFileEntryLocale(ha, szTempName, 0) != NULL)
+ bResult = CreatePatchPrefix(ha, szPatchPrefix, nLength);
+
+ return bResult;
+}
+
+static bool IsMatchingPatchFile(
+ TMPQArchive * ha,
+ const char * szFileName,
+ LPBYTE pbBaseFileMd5)
+{
+ MPQ_PATCH_HEADER PatchHeader = {0};
+ HANDLE hFile = NULL;
+ DWORD dwTransferred = 0;
+ DWORD dwFlags = 0;
+ bool bResult = false;
+
+ // Open the file and load the patch header
+ if(SFileOpenFileEx((HANDLE)ha, szFileName, SFILE_OPEN_BASE_FILE, &hFile))
+ {
+ // Retrieve the flags. We need to know whether the file is a patch or not
+ SFileGetFileInfo(hFile, SFileInfoFlags, &dwFlags, sizeof(DWORD), &dwTransferred);
+ if(dwFlags & MPQ_FILE_PATCH_FILE)
+ {
+ // 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, pbBaseFileMd5, MD5_DIGEST_SIZE));
+ }
+ else
+ {
+ // TODO: How to match it if it's not an incremental patch?
+ // Example: StarCraft II\Updates\enGB\s2-update-enGB-23258.MPQ:
+ // Mods\Core.SC2Mod\enGB.SC2Assets\StreamingBuckets.txt"
+ bResult = false;
+ }
+
+ // 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;
+}
+
+//-----------------------------------------------------------------------------
+// Finding ratch prefix for an temporary build of WoW (Pre-Cataclysm)
+
+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);
+}
+
+//-----------------------------------------------------------------------------
+// Finding patch prefix for Starcraft II (Pre-Legacy of the Void)
+
+//
+// This method tries to match the patch by placement of the archive (in the game subdirectory)
+//
+// Archive Path: %GAME_DIR%\Mods\SwarmMulti.SC2Mod\Base.SC2Data
+// Patch Prefix: Mods\SwarmMulti.SC2Mod\Base.SC2Data
+//
+// Archive Path: %ANY_DIR%\MPQ_2013_v4_Mods#Liberty.SC2Mod#enGB.SC2Data
+// Patch Prefix: Mods\Liberty.SC2Mod\enGB.SC2Data
+//
+
+static bool CheckPatchPrefix_SC2_ArchiveName(
+ TMPQArchive * haPatch,
+ const TCHAR * szPathPtr,
+ const TCHAR * szSeparator,
+ const TCHAR * szPathEnd,
+ const TCHAR * szExpectedString,
+ size_t cchExpectedString)
+{
+ char szPatchPrefix[MAX_SC2_PATCH_PREFIX+0x41];
+ size_t nLength = 0;
+ bool bResult = false;
+
+ // Check whether the length is equal to the length of the expected string
+ if((size_t)(szSeparator - szPathPtr) == cchExpectedString)
+ {
+ // Now check the string itself
+ if(!_tcsnicmp(szPathPtr, szExpectedString, szSeparator - szPathPtr))
+ {
+ // Copy the name string
+ for(; szPathPtr < szPathEnd; szPathPtr++)
+ {
+ if(szPathPtr[0] != _T('/') && szPathPtr[0] != _T('#'))
+ szPatchPrefix[nLength++] = (char)szPathPtr[0];
+ else
+ szPatchPrefix[nLength++] = '\\';
+ }
+
+ // Check and create the patch prefix
+ bResult = CheckAndCreatePatchPrefix(haPatch, szPatchPrefix, nLength);
+ }
+ }
+
+ return bResult;
+}
+
+static bool FindPatchPrefix_SC2_ArchiveName(TMPQArchive * haBase, TMPQArchive * haPatch)
+{
+ const TCHAR * szPathBegin = FileStream_GetFileName(haBase->pStream);
+ const TCHAR * szSeparator = NULL;
+ const TCHAR * szPathEnd = szPathBegin + _tcslen(szPathBegin);
+ const TCHAR * szPathPtr;
+ int nSlashCount = 0;
+ int nDotCount = 0;
+
+ // Skip the part where the patch prefix would be too long
+ if((szPathEnd - szPathBegin) > MAX_SC2_PATCH_PREFIX)
+ szPathBegin = szPathEnd - MAX_SC2_PATCH_PREFIX;
+
+ // Search for the file extension
+ for(szPathPtr = szPathEnd; szPathPtr > szPathBegin; szPathPtr--)
+ {
+ if(szPathPtr[0] == _T('.'))
+ {
+ nDotCount++;
+ break;
+ }
+ }
+
+ // Search for the possible begin of the prefix name
+ for(/* NOTHING */; szPathPtr > szPathBegin; szPathPtr--)
+ {
+ // Check the slashes, backslashes and hashes
+ if(szPathPtr[0] == _T('\\') || szPathPtr[0] == _T('/') || szPathPtr[0] == _T('#'))
+ {
+ if(nDotCount == 0)
+ return false;
+ szSeparator = szPathPtr;
+ nSlashCount++;
+ }
+
+ // Check the path parts
+ if(szSeparator != NULL && nSlashCount >= nDotCount)
+ {
+ if(CheckPatchPrefix_SC2_ArchiveName(haPatch, szPathPtr, szSeparator, szPathEnd, _T("Battle.net"), 10))
+ return true;
+ if(CheckPatchPrefix_SC2_ArchiveName(haPatch, szPathPtr, szSeparator, szPathEnd, _T("Campaigns"), 9))
+ return true;
+ if(CheckPatchPrefix_SC2_ArchiveName(haPatch, szPathPtr, szSeparator, szPathEnd, _T("Mods"), 4))
+ return true;
+ }
+ }
+
+ // Not matched, sorry
+ return false;
+}
+
+//
+// This method tries to read the patch prefix from a helper file
+//
+// Example
+// =========================================================
+// MPQ File Name: MPQ_2013_v4_Base1.SC2Data
+// Helper File : MPQ_2013_v4_Base1.SC2Data-PATCH
+// File Contains: PatchPrefix=Mods\Core.SC2Mod\Base.SC2Data
+// Patch Prefix : Mods\Core.SC2Mod\Base.SC2Data
+//
+
+static bool ExtractPatchPrefixFromFile(const TCHAR * szHelperFile, char * szPatchPrefix, size_t nMaxChars, size_t * PtrLength)
+{
+ TFileStream * pStream;
+ ULONGLONG FileSize = 0;
+ size_t nLength;
+ char szFileData[MAX_PATH+1];
+ bool bResult = false;
+
+ pStream = FileStream_OpenFile(szHelperFile, STREAM_FLAG_READ_ONLY);
+ if(pStream != NULL)
+ {
+ // Retrieve and check the file size
+ FileStream_GetSize(pStream, &FileSize);
+ if(12 <= FileSize && FileSize < MAX_PATH)
+ {
+ // Read the entire file to memory
+ if(FileStream_Read(pStream, NULL, szFileData, (DWORD)FileSize))
+ {
+ // Terminate the buffer with zero
+ szFileData[(DWORD)FileSize] = 0;
+
+ // The file data must begin with the "PatchPrefix" variable
+ if(!_strnicmp(szFileData, "PatchPrefix", 11))
+ {
+ char * szLinePtr = szFileData + 11;
+ char * szLineEnd;
+
+ // Skip spaces or '='
+ while(szLinePtr[0] == ' ' || szLinePtr[0] == '=')
+ szLinePtr++;
+ szLineEnd = szLinePtr;
+
+ // Find the end
+ while(szLineEnd[0] != 0 && szLineEnd[0] != 0x0A && szLineEnd[0] != 0x0D)
+ szLineEnd++;
+ nLength = (size_t)(szLineEnd - szLinePtr);
+
+ // Copy the variable
+ if(szLineEnd > szLinePtr && nLength <= nMaxChars)
+ {
+ memcpy(szPatchPrefix, szLinePtr, nLength);
+ szPatchPrefix[nLength] = 0;
+ PtrLength[0] = nLength;
+ bResult = true;
+ }
+ }
+ }
+ }
+
+ // Close the stream
+ FileStream_Close(pStream);
+ }
+
+ return bResult;
+}
+
+
+static bool FindPatchPrefix_SC2_HelperFile(TMPQArchive * haBase, TMPQArchive * haPatch)
+{
+ TCHAR szHelperFile[MAX_PATH+1];
+ char szPatchPrefix[MAX_SC2_PATCH_PREFIX+0x41];
+ size_t nLength = 0;
+ bool bResult = false;
+
+ // Create the name of the patch helper file
+ _tcscpy(szHelperFile, FileStream_GetFileName(haBase->pStream));
+ if(_tcslen(szHelperFile) + 6 > MAX_PATH)
+ return false;
+ _tcscat(szHelperFile, _T("-PATCH"));
+
+ // Open the patch helper file and read the line
+ if(ExtractPatchPrefixFromFile(szHelperFile, szPatchPrefix, MAX_SC2_PATCH_PREFIX, &nLength))
+ bResult = CheckAndCreatePatchPrefix(haPatch, szPatchPrefix, nLength);
+
+ return bResult;
+}
+
+//
+// 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:
+// File in the base MPQ: enGB-md5.lst
+// File in the patch MPQ: 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_MatchFiles(TMPQArchive * haBase, TMPQArchive * haPatch, TFileEntry * pBaseEntry)
+{
+ TMPQNamePrefix * pPatchPrefix;
+ char * szPatchFileName;
+ 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
+ szPatchFileName = STORM_ALLOC(char, cchWorkBuffer);
+ if(szPatchFileName != NULL)
+ {
+ // 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(szPatchFileName, pFileEntry->szFileName);
+ szPlainName = (char *)GetPlainFileName(szPatchFileName);
+ strcpy(szPlainName, pBaseEntry->szFileName);
+
+ // Check for matching MD5 file
+ if(IsMatchingPatchFile(haPatch, szPatchFileName, pBaseEntry->md5))
+ {
+ bResult = CreatePatchPrefix(haPatch, szPatchFileName, (size_t)(szPlainName - szPatchFileName));
+ break;
+ }
+ }
+ }
+
+ // Delete the merge buffer
+ STORM_FREE(szPatchFileName);
+ }
+ }
+
+ // 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;
+}
+
+// Note: pBaseEntry is the file entry of the base version of "StreamingBuckets.txt"
+static bool FindPatchPrefix_SC2(TMPQArchive * haBase, TMPQArchive * haPatch, TFileEntry * pBaseEntry)
+{
+ // Method 1: Try it by the placement of the archive.
+ // Works when someone is opening an archive in the game (sub)directory
+ if(FindPatchPrefix_SC2_ArchiveName(haBase, haPatch))
+ return true;
+
+ // Method 2: Try to locate the Name.Ext-PATCH file and read the patch prefix from it
+ if(FindPatchPrefix_SC2_HelperFile(haBase, haPatch))
+ return true;
+
+ // Method 3: Try to pair any version of "StreamingBuckets.txt" from the patch MPQ
+ // with the "StreamingBuckets.txt" in the base MPQ. Does not always work
+ if(FindPatchPrefix_SC2_MatchFiles(haBase, haPatch, pBaseEntry))
+ return true;
+
+ return false;
+}
+
+//
+// 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
+//
+// Example 3:
+// Main MPQ: %GAME%\Battle.net\Battle.net.MPQ
+// Patch MPQ: s2-update-base-26147.MPQ
+// File in main MPQ: Battle.net\i18n\deDE\String\CLIENT_ACHIEVEMENTS.xml
+// File in patch MPQ: Battle.net\Battle.net.MPQ\Battle.net\i18n\deDE\String\CLIENT_ACHIEVEMENTS.xml
+// Path prefix: Battle.net\Battle.net.MPQ
+//
+// Example 4:
+// Main MPQ: %GAME%\Campaigns\Liberty.SC2Campaign\enGB.SC2Data
+// *OR* %ANY_DIR%\%ANY_NAME%Campaigns#Liberty.SC2Campaign#enGB.SC2Data
+// Patch MPQ: s2-update-enGB-23258.MPQ
+// File in main MPQ: LocalizedData\GameHotkeys.txt
+// File in patch MPQ: Campaigns\Liberty.SC2Campaign\enGB.SC2Data\LocalizedData\GameHotkeys.txt
+// Patch Prefix: Campaigns\Liberty.SC2Campaign\enGB.SC2Data
+//
+
+static bool FindPatchPrefix(TMPQArchive * haBase, TMPQArchive * haPatch, const char * szPatchPathPrefix)
+{
+ TFileEntry * pFileEntry;
+
+ // 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
+ pFileEntry = GetFileEntryLocale(haBase, "StreamingBuckets.txt", 0);
+ if(pFileEntry != NULL)
+ return FindPatchPrefix_SC2(haBase, haPatch, pFileEntry);
+
+ // 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((cbMaxFileData + (DWORD)sizeof(MPQ_PATCH_HEADER)) < 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
+
+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))
+ {
+ // Cast the archive handle to structure pointer
+ haPatch = (TMPQArchive *)hPatchMpq;
+
+ // We need to remember the proper patch prefix to match names of patched files
+ if(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;
+ }
+ }
+
+ // Close the archive
+ SFileCloseArchive(hPatchMpq);
+ nError = ERROR_CANT_FIND_PATCH_PREFIX;
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+ }
+
+ 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/dep/StormLib/src/SFileReadFile.cpp b/dep/StormLib/src/SFileReadFile.cpp
new file mode 100644
index 000000000..719064b0a
--- /dev/null
+++ b/dep/StormLib/src/SFileReadFile.cpp
@@ -0,0 +1,898 @@
+/*****************************************************************************/
+/* SFileReadFile.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Description : */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.99 1.00 Lad The first version of SFileReadFile.cpp */
+/* 24.03.99 1.00 Lad Added the SFileGetFileInfo function */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+// hf - MPQ File handle.
+// pbBuffer - Pointer to target buffer to store sectors.
+// dwByteOffset - Position of sector in the file (relative to file begin)
+// dwBytesToRead - Number of bytes to read. Must be multiplier of sector size.
+// pdwBytesRead - Stored number of bytes loaded
+static int ReadMpqSectors(TMPQFile * hf, LPBYTE pbBuffer, DWORD dwByteOffset, DWORD dwBytesToRead, LPDWORD pdwBytesRead)
+{
+ ULONGLONG RawFilePos;
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ LPBYTE pbRawSector = NULL;
+ LPBYTE pbOutSector = pbBuffer;
+ LPBYTE pbInSector = pbBuffer;
+ DWORD dwRawBytesToRead;
+ DWORD dwRawSectorOffset = dwByteOffset;
+ DWORD dwSectorsToRead = dwBytesToRead / ha->dwSectorSize;
+ DWORD dwSectorIndex = dwByteOffset / ha->dwSectorSize;
+ DWORD dwSectorsDone = 0;
+ DWORD dwBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Note that dwByteOffset must be aligned to size of one sector
+ // Note that dwBytesToRead must be a multiplier of one sector size
+ // This is local function, so we won't check if that's true.
+ // Note that files stored in single units are processed by a separate function
+
+ // If there is not enough bytes remaining, cut dwBytesToRead
+ if((dwByteOffset + dwBytesToRead) > hf->dwDataSize)
+ dwBytesToRead = hf->dwDataSize - dwByteOffset;
+ dwRawBytesToRead = dwBytesToRead;
+
+ // Perform all necessary work to do with compressed files
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ // If the sector positions are not loaded yet, do it
+ if(hf->SectorOffsets == NULL)
+ {
+ nError = AllocateSectorOffsets(hf, true);
+ if(nError != ERROR_SUCCESS || hf->SectorOffsets == NULL)
+ return nError;
+ }
+
+ // If the sector checksums are not loaded yet, load them now.
+ if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) && hf->bLoadedSectorCRCs == false)
+ {
+ //
+ // Sector CRCs is plain crap feature. It is almost never present,
+ // often it's empty, or the end offset of sector CRCs is zero.
+ // We only try to load sector CRCs once, and regardless if it fails
+ // or not, we won't try that again for the given file.
+ //
+
+ AllocateSectorChecksums(hf, true);
+ hf->bLoadedSectorCRCs = true;
+ }
+
+ // TODO: If the raw data MD5s are not loaded yet, load them now
+ // Only do it if the MPQ is of format 4.0
+// if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4 && ha->pHeader->dwRawChunkSize != 0)
+// {
+// nError = AllocateRawMD5s(hf, true);
+// if(nError != ERROR_SUCCESS)
+// return nError;
+// }
+
+ // Assign the temporary buffer as target for read operation
+ dwRawSectorOffset = hf->SectorOffsets[dwSectorIndex];
+ dwRawBytesToRead = hf->SectorOffsets[dwSectorIndex + dwSectorsToRead] - dwRawSectorOffset;
+
+ // If the file is compressed, also allocate secondary buffer
+ pbInSector = pbRawSector = STORM_ALLOC(BYTE, dwRawBytesToRead);
+ if(pbRawSector == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Calculate raw file offset where the sector(s) are stored.
+ RawFilePos = CalculateRawSectorOffset(hf, dwRawSectorOffset);
+
+ // Set file pointer and read all required sectors
+ if(FileStream_Read(ha->pStream, &RawFilePos, pbInSector, dwRawBytesToRead))
+ {
+ // Now we have to decrypt and decompress all file sectors that have been loaded
+ for(DWORD i = 0; i < dwSectorsToRead; i++)
+ {
+ DWORD dwRawBytesInThisSector = ha->dwSectorSize;
+ DWORD dwBytesInThisSector = ha->dwSectorSize;
+ DWORD dwIndex = dwSectorIndex + i;
+
+ // If there is not enough bytes in the last sector,
+ // cut the number of bytes in this sector
+ if(dwRawBytesInThisSector > dwBytesToRead)
+ dwRawBytesInThisSector = dwBytesToRead;
+ if(dwBytesInThisSector > dwBytesToRead)
+ dwBytesInThisSector = dwBytesToRead;
+
+ // If the file is compressed, we have to adjust the raw sector size
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ dwRawBytesInThisSector = hf->SectorOffsets[dwIndex + 1] - hf->SectorOffsets[dwIndex];
+
+ // If the file is encrypted, we have to decrypt the sector
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector);
+
+ // If we don't know the key, try to detect it by file content
+ if(hf->dwFileKey == 0)
+ {
+ hf->dwFileKey = DetectFileKeyByContent(pbInSector, dwBytesInThisSector, hf->dwDataSize);
+ if(hf->dwFileKey == 0)
+ {
+ nError = ERROR_UNKNOWN_FILE_KEY;
+ break;
+ }
+ }
+
+ DecryptMpqBlock(pbInSector, dwRawBytesInThisSector, hf->dwFileKey + dwIndex);
+ BSWAP_ARRAY32_UNSIGNED(pbInSector, dwRawBytesInThisSector);
+ }
+
+ // If the file has sector CRC check turned on, perform it
+ if(hf->bCheckSectorCRCs && hf->SectorChksums != NULL)
+ {
+ DWORD dwAdlerExpected = hf->SectorChksums[dwIndex];
+ DWORD dwAdlerValue = 0;
+
+ // We can only check sector CRC when it's not zero
+ // Neither can we check it if it's 0xFFFFFFFF.
+ if(dwAdlerExpected != 0 && dwAdlerExpected != 0xFFFFFFFF)
+ {
+ dwAdlerValue = adler32(0, pbInSector, dwRawBytesInThisSector);
+ if(dwAdlerValue != dwAdlerExpected)
+ {
+ nError = ERROR_CHECKSUM_ERROR;
+ break;
+ }
+ }
+ }
+
+ // If the sector is really compressed, decompress it.
+ // WARNING : Some sectors may not be compressed, it can be determined only
+ // by comparing uncompressed and compressed size !!!
+ if(dwRawBytesInThisSector < dwBytesInThisSector)
+ {
+ int cbOutSector = dwBytesInThisSector;
+ int cbInSector = dwRawBytesInThisSector;
+ int nResult = 0;
+
+ // Is the file compressed by Blizzard's multiple compression ?
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
+ {
+ // Remember the last used compression
+ hf->dwCompression0 = pbInSector[0];
+
+ // Decompress the data
+ if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2)
+ nResult = SCompDecompress2(pbOutSector, &cbOutSector, pbInSector, cbInSector);
+ else
+ nResult = SCompDecompress(pbOutSector, &cbOutSector, pbInSector, cbInSector);
+ }
+
+ // Is the file compressed by PKWARE Data Compression Library ?
+ else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
+ {
+ nResult = SCompExplode(pbOutSector, &cbOutSector, pbInSector, cbInSector);
+ }
+
+ // Did the decompression fail ?
+ if(nResult == 0)
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+ }
+ else
+ {
+ if(pbOutSector != pbInSector)
+ memcpy(pbOutSector, pbInSector, dwBytesInThisSector);
+ }
+
+ // Move pointers
+ dwBytesToRead -= dwBytesInThisSector;
+ dwByteOffset += dwBytesInThisSector;
+ dwBytesRead += dwBytesInThisSector;
+ pbOutSector += dwBytesInThisSector;
+ pbInSector += dwRawBytesInThisSector;
+ dwSectorsDone++;
+ }
+ }
+ else
+ {
+ nError = GetLastError();
+ }
+
+ // Free all used buffers
+ if(pbRawSector != NULL)
+ STORM_FREE(pbRawSector);
+
+ // Give the caller thenumber of bytes read
+ *pdwBytesRead = dwBytesRead;
+ return nError;
+}
+
+static int ReadMpqFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
+{
+ ULONGLONG RawFilePos = hf->RawFilePos;
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ LPBYTE pbCompressed = NULL;
+ LPBYTE pbRawData;
+ int nError = ERROR_SUCCESS;
+
+ // If the file buffer is not allocated yet, do it.
+ if(hf->pbFileSector == NULL)
+ {
+ nError = AllocateSectorBuffer(hf);
+ if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL)
+ return nError;
+ }
+
+ // If the file is a patch file, adjust raw data offset
+ if(hf->pPatchInfo != NULL)
+ RawFilePos += hf->pPatchInfo->dwLength;
+ pbRawData = hf->pbFileSector;
+
+ // If the file sector is not loaded yet, do it
+ if(hf->dwSectorOffs != 0)
+ {
+ // Is the file compressed?
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ // Allocate space for compressed data
+ pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize);
+ if(pbCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ pbRawData = pbCompressed;
+ }
+
+ // Load the raw (compressed, encrypted) data
+ if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize))
+ {
+ STORM_FREE(pbCompressed);
+ return GetLastError();
+ }
+
+ // If the file is encrypted, we have to decrypt the data first
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize);
+ DecryptMpqBlock(pbRawData, pFileEntry->dwCmpSize, hf->dwFileKey);
+ BSWAP_ARRAY32_UNSIGNED(pbRawData, pFileEntry->dwCmpSize);
+ }
+
+ // If the file is compressed, we have to decompress it now
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ int cbOutBuffer = (int)hf->dwDataSize;
+ int cbInBuffer = (int)pFileEntry->dwCmpSize;
+ int nResult = 0;
+
+ //
+ // If the file is an incremental patch, the size of compressed data
+ // is determined as pFileEntry->dwCmpSize - sizeof(TPatchInfo)
+ //
+ // In "wow-update-12694.MPQ" from Wow-Cataclysm BETA:
+ //
+ // File CmprSize DcmpSize DataSize Compressed?
+ // -------------------------------------- ---------- -------- -------- ---------------
+ // esES\DBFilesClient\LightSkyBox.dbc 0xBE->0xA2 0xBC 0xBC Yes
+ // deDE\DBFilesClient\MountCapability.dbc 0x93->0x77 0x77 0x77 No
+ //
+
+ if(pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE)
+ cbInBuffer = cbInBuffer - sizeof(TPatchInfo);
+
+ // Is the file compressed by Blizzard's multiple compression ?
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
+ {
+ // Remember the last used compression
+ hf->dwCompression0 = pbRawData[0];
+
+ // Decompress the file
+ if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_2)
+ nResult = SCompDecompress2(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer);
+ else
+ nResult = SCompDecompress(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer);
+ }
+
+ // Is the file compressed by PKWARE Data Compression Library ?
+ // Note: Single unit files compressed with IMPLODE are not supported by Blizzard
+ else if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
+ nResult = SCompExplode(hf->pbFileSector, &cbOutBuffer, pbRawData, cbInBuffer);
+
+ nError = (nResult != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
+ }
+ else
+ {
+ if(hf->pbFileSector != NULL && pbRawData != hf->pbFileSector)
+ memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize);
+ }
+
+ // Free the decompression buffer.
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+
+ // The file sector is now properly loaded
+ hf->dwSectorOffs = 0;
+ }
+
+ // At this moment, we have the file loaded into the file buffer.
+ // Copy as much as the caller wants
+ if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0)
+ {
+ // File position is greater or equal to file size ?
+ if(dwFilePos >= hf->dwDataSize)
+ {
+ *pdwBytesRead = 0;
+ return ERROR_SUCCESS;
+ }
+
+ // If not enough bytes remaining in the file, cut them
+ if((hf->dwDataSize - dwFilePos) < dwToRead)
+ dwToRead = (hf->dwDataSize - dwFilePos);
+
+ // Copy the bytes
+ memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead);
+
+ // Give the number of bytes read
+ *pdwBytesRead = dwToRead;
+ return ERROR_SUCCESS;
+ }
+
+ // An error, sorry
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+static int ReadMpkFileSingleUnit(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
+{
+ ULONGLONG RawFilePos = hf->RawFilePos + 0x0C; // For some reason, MPK files start at position (hf->RawFilePos + 0x0C)
+ TMPQArchive * ha = hf->ha;
+ TFileEntry * pFileEntry = hf->pFileEntry;
+ LPBYTE pbCompressed = NULL;
+ LPBYTE pbRawData = hf->pbFileSector;
+ int nError = ERROR_SUCCESS;
+
+ // We do not support patch files in MPK archives
+ assert(hf->pPatchInfo == NULL);
+
+ // If the file buffer is not allocated yet, do it.
+ if(hf->pbFileSector == NULL)
+ {
+ nError = AllocateSectorBuffer(hf);
+ if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL)
+ return nError;
+ pbRawData = hf->pbFileSector;
+ }
+
+ // If the file sector is not loaded yet, do it
+ if(hf->dwSectorOffs != 0)
+ {
+ // Is the file compressed?
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ // Allocate space for compressed data
+ pbCompressed = STORM_ALLOC(BYTE, pFileEntry->dwCmpSize);
+ if(pbCompressed == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ pbRawData = pbCompressed;
+ }
+
+ // Load the raw (compressed, encrypted) data
+ if(!FileStream_Read(ha->pStream, &RawFilePos, pbRawData, pFileEntry->dwCmpSize))
+ {
+ STORM_FREE(pbCompressed);
+ return GetLastError();
+ }
+
+ // If the file is encrypted, we have to decrypt the data first
+ if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
+ {
+ DecryptMpkTable(pbRawData, pFileEntry->dwCmpSize);
+ }
+
+ // If the file is compressed, we have to decompress it now
+ if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
+ {
+ int cbOutBuffer = (int)hf->dwDataSize;
+
+ hf->dwCompression0 = pbRawData[0];
+ if(!SCompDecompressMpk(hf->pbFileSector, &cbOutBuffer, pbRawData, (int)pFileEntry->dwCmpSize))
+ nError = ERROR_FILE_CORRUPT;
+ }
+ else
+ {
+ if(pbRawData != hf->pbFileSector)
+ memcpy(hf->pbFileSector, pbRawData, hf->dwDataSize);
+ }
+
+ // Free the decompression buffer.
+ if(pbCompressed != NULL)
+ STORM_FREE(pbCompressed);
+
+ // The file sector is now properly loaded
+ hf->dwSectorOffs = 0;
+ }
+
+ // At this moment, we have the file loaded into the file buffer.
+ // Copy as much as the caller wants
+ if(nError == ERROR_SUCCESS && hf->dwSectorOffs == 0)
+ {
+ // File position is greater or equal to file size ?
+ if(dwFilePos >= hf->dwDataSize)
+ {
+ *pdwBytesRead = 0;
+ return ERROR_SUCCESS;
+ }
+
+ // If not enough bytes remaining in the file, cut them
+ if((hf->dwDataSize - dwFilePos) < dwToRead)
+ dwToRead = (hf->dwDataSize - dwFilePos);
+
+ // Copy the bytes
+ memcpy(pvBuffer, hf->pbFileSector + dwFilePos, dwToRead);
+
+ // Give the number of bytes read
+ *pdwBytesRead = dwToRead;
+ return ERROR_SUCCESS;
+ }
+
+ // An error, sorry
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+
+static int ReadMpqFileSectorFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwBytesToRead, LPDWORD pdwBytesRead)
+{
+ TMPQArchive * ha = hf->ha;
+ LPBYTE pbBuffer = (BYTE *)pvBuffer;
+ DWORD dwTotalBytesRead = 0; // Total bytes read in all three parts
+ DWORD dwSectorSizeMask = ha->dwSectorSize - 1; // Mask for block size, usually 0x0FFF
+ DWORD dwFileSectorPos; // File offset of the loaded sector
+ DWORD dwBytesRead; // Number of bytes read (temporary variable)
+ int nError;
+
+ // If the file position is at or beyond end of file, do nothing
+ if(dwFilePos >= hf->dwDataSize)
+ {
+ *pdwBytesRead = 0;
+ return ERROR_SUCCESS;
+ }
+
+ // If not enough bytes in the file remaining, cut them
+ if(dwBytesToRead > (hf->dwDataSize - dwFilePos))
+ dwBytesToRead = (hf->dwDataSize - dwFilePos);
+
+ // Compute sector position in the file
+ dwFileSectorPos = dwFilePos & ~dwSectorSizeMask; // Position in the block
+
+ // If the file sector buffer is not allocated yet, do it now
+ if(hf->pbFileSector == NULL)
+ {
+ nError = AllocateSectorBuffer(hf);
+ if(nError != ERROR_SUCCESS || hf->pbFileSector == NULL)
+ return nError;
+ }
+
+ // Load the first (incomplete) file sector
+ if(dwFilePos & dwSectorSizeMask)
+ {
+ DWORD dwBytesInSector = ha->dwSectorSize;
+ DWORD dwBufferOffs = dwFilePos & dwSectorSizeMask;
+ DWORD dwToCopy;
+
+ // Is the file sector already loaded ?
+ if(hf->dwSectorOffs != dwFileSectorPos)
+ {
+ // Load one MPQ sector into archive buffer
+ nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesInSector);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Remember that the data loaded to the sector have new file offset
+ hf->dwSectorOffs = dwFileSectorPos;
+ }
+ else
+ {
+ if((dwFileSectorPos + dwBytesInSector) > hf->dwDataSize)
+ dwBytesInSector = hf->dwDataSize - dwFileSectorPos;
+ }
+
+ // Copy the data from the offset in the loaded sector to the end of the sector
+ dwToCopy = dwBytesInSector - dwBufferOffs;
+ if(dwToCopy > dwBytesToRead)
+ dwToCopy = dwBytesToRead;
+
+ // Copy data from sector buffer into target buffer
+ memcpy(pbBuffer, hf->pbFileSector + dwBufferOffs, dwToCopy);
+
+ // Update pointers and byte counts
+ dwTotalBytesRead += dwToCopy;
+ dwFileSectorPos += dwBytesInSector;
+ pbBuffer += dwToCopy;
+ dwBytesToRead -= dwToCopy;
+ }
+
+ // Load the whole ("middle") sectors only if there is at least one full sector to be read
+ if(dwBytesToRead >= ha->dwSectorSize)
+ {
+ DWORD dwBlockBytes = dwBytesToRead & ~dwSectorSizeMask;
+
+ // Load all sectors to the output buffer
+ nError = ReadMpqSectors(hf, pbBuffer, dwFileSectorPos, dwBlockBytes, &dwBytesRead);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Update pointers
+ dwTotalBytesRead += dwBytesRead;
+ dwFileSectorPos += dwBytesRead;
+ pbBuffer += dwBytesRead;
+ dwBytesToRead -= dwBytesRead;
+ }
+
+ // Read the terminating sector
+ if(dwBytesToRead > 0)
+ {
+ DWORD dwToCopy = ha->dwSectorSize;
+
+ // Is the file sector already loaded ?
+ if(hf->dwSectorOffs != dwFileSectorPos)
+ {
+ // Load one MPQ sector into archive buffer
+ nError = ReadMpqSectors(hf, hf->pbFileSector, dwFileSectorPos, ha->dwSectorSize, &dwBytesRead);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Remember that the data loaded to the sector have new file offset
+ hf->dwSectorOffs = dwFileSectorPos;
+ }
+
+ // Check number of bytes read
+ if(dwToCopy > dwBytesToRead)
+ dwToCopy = dwBytesToRead;
+
+ // Copy the data from the cached last sector to the caller's buffer
+ memcpy(pbBuffer, hf->pbFileSector, dwToCopy);
+
+ // Update pointers
+ dwTotalBytesRead += dwToCopy;
+ }
+
+ // Store total number of bytes read to the caller
+ *pdwBytesRead = dwTotalBytesRead;
+ return ERROR_SUCCESS;
+}
+
+static int ReadMpqFilePatchFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
+{
+ TMPQPatcher Patcher;
+ DWORD dwBytesToRead = dwToRead;
+ DWORD dwBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Make sure that the patch file is loaded completely
+ if(nError == ERROR_SUCCESS && hf->pbFileData == NULL)
+ {
+ // Initialize patching process and allocate data
+ nError = Patch_InitPatcher(&Patcher, hf);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Set the current data size
+ Patcher.cbFileData = hf->pFileEntry->dwFileSize;
+
+ // Initialize the patcher object with initial file data
+ if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
+ nError = ReadMpqFileSingleUnit(hf, Patcher.pbFileData1, 0, Patcher.cbFileData, &dwBytesRead);
+ else
+ nError = ReadMpqFileSectorFile(hf, Patcher.pbFileData1, 0, Patcher.cbFileData, &dwBytesRead);
+
+ // Perform the patching process
+ if(nError == ERROR_SUCCESS)
+ nError = Patch_Process(&Patcher, hf);
+
+ // Finalize the patcher structure
+ Patch_Finalize(&Patcher);
+ dwBytesRead = 0;
+ }
+
+ // If there is something to read, do it
+ if(nError == ERROR_SUCCESS)
+ {
+ if(dwFilePos < hf->cbFileData)
+ {
+ // Make sure we don't copy more than file size
+ if((dwFilePos + dwToRead) > hf->cbFileData)
+ dwToRead = hf->cbFileData - dwFilePos;
+
+ // Copy the appropriate amount of the file data to the caller's buffer
+ memcpy(pvBuffer, hf->pbFileData + dwFilePos, dwToRead);
+ dwBytesRead = dwToRead;
+ }
+
+ // Set the proper error code
+ nError = (dwBytesRead == dwBytesToRead) ? ERROR_SUCCESS : ERROR_HANDLE_EOF;
+ }
+
+ // Give the result to the caller
+ if(pdwBytesRead != NULL)
+ *pdwBytesRead = dwBytesRead;
+ return nError;
+}
+
+static int ReadMpqFileLocalFile(TMPQFile * hf, void * pvBuffer, DWORD dwFilePos, DWORD dwToRead, LPDWORD pdwBytesRead)
+{
+ ULONGLONG FilePosition1 = dwFilePos;
+ ULONGLONG FilePosition2;
+ DWORD dwBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ assert(hf->pStream != NULL);
+
+ // Because stream I/O functions are designed to read
+ // "all or nothing", we compare file position before and after,
+ // and if they differ, we assume that number of bytes read
+ // is the difference between them
+
+ if(!FileStream_Read(hf->pStream, &FilePosition1, pvBuffer, dwToRead))
+ {
+ // If not all bytes have been read, then return the number of bytes read
+ if((nError = GetLastError()) == ERROR_HANDLE_EOF)
+ {
+ FileStream_GetPos(hf->pStream, &FilePosition2);
+ dwBytesRead = (DWORD)(FilePosition2 - FilePosition1);
+ }
+ }
+ else
+ {
+ dwBytesRead = dwToRead;
+ }
+
+ *pdwBytesRead = dwBytesRead;
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// SFileReadFile
+
+bool WINAPI SFileReadFile(HANDLE hFile, void * pvBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+ DWORD dwBytesRead = 0; // Number of bytes read
+ int nError = ERROR_SUCCESS;
+
+ // Always zero the result
+ if(pdwRead != NULL)
+ *pdwRead = 0;
+
+ // Check valid parameters
+ if(!IsValidFileHandle(hFile))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ if(pvBuffer == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ // If we didn't load the patch info yet, do it now
+ 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)
+ {
+ SetLastError(nError);
+ return false;
+ }
+ }
+
+ // Clear the last used compression
+ hf->dwCompression0 = 0;
+
+ // If the file is local file, read the data directly from the stream
+ if(hf->pStream != NULL)
+ {
+ nError = ReadMpqFileLocalFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
+ }
+
+ // If the file is a patch file, we have to read it special way
+ else if(hf->hfPatch != NULL && (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
+ {
+ nError = ReadMpqFilePatchFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
+ }
+
+ // If the archive is a MPK archive, we need special way to read the file
+ else if(hf->ha->dwSubType == MPQ_SUBTYPE_MPK)
+ {
+ nError = ReadMpkFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
+ }
+
+ // If the file is single unit file, redirect it to read file
+ else if(hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
+ {
+ nError = ReadMpqFileSingleUnit(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
+ }
+
+ // Otherwise read it as sector based MPQ file
+ else
+ {
+ nError = ReadMpqFileSectorFile(hf, pvBuffer, hf->dwFilePos, dwToRead, &dwBytesRead);
+ }
+
+ // Increment the file position
+ hf->dwFilePos += dwBytesRead;
+
+ // Give the caller the number of bytes read
+ if(pdwRead != NULL)
+ *pdwRead = dwBytesRead;
+
+ // If the read operation succeeded, but not full number of bytes was read,
+ // set the last error to ERROR_HANDLE_EOF
+ if(nError == ERROR_SUCCESS && (dwBytesRead < dwToRead))
+ nError = ERROR_HANDLE_EOF;
+
+ // If something failed, set the last error value
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// SFileGetFileSize
+
+DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh)
+{
+ ULONGLONG FileSize;
+ TMPQFile * hf = (TMPQFile *)hFile;
+
+ // Validate the file handle before we go on
+ if(IsValidFileHandle(hFile))
+ {
+ // Make sure that the variable is initialized
+ FileSize = 0;
+
+ // If the file is patched file, we have to get the size of the last version
+ if(hf->hfPatch != NULL)
+ {
+ // Walk through the entire patch chain, take the last version
+ while(hf != NULL)
+ {
+ // Get the size of the currently pointed version
+ FileSize = hf->pFileEntry->dwFileSize;
+
+ // Move to the next patch file in the hierarchy
+ hf = hf->hfPatch;
+ }
+ }
+ else
+ {
+ // Is it a local file ?
+ if(hf->pStream != NULL)
+ {
+ FileStream_GetSize(hf->pStream, &FileSize);
+ }
+ else
+ {
+ FileSize = hf->dwDataSize;
+ }
+ }
+
+ // If opened from archive, return file size
+ if(pdwFileSizeHigh != NULL)
+ *pdwFileSizeHigh = (DWORD)(FileSize >> 32);
+ return (DWORD)FileSize;
+ }
+
+ SetLastError(ERROR_INVALID_HANDLE);
+ return SFILE_INVALID_SIZE;
+}
+
+DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod)
+{
+ TMPQFile * hf = (TMPQFile *)hFile;
+ ULONGLONG OldPosition;
+ ULONGLONG NewPosition;
+ ULONGLONG FileSize;
+ ULONGLONG DeltaPos;
+
+ // If the hFile is not a valid file handle, return an error.
+ if(!IsValidFileHandle(hFile))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return SFILE_INVALID_POS;
+ }
+
+ // Retrieve the file size for handling the limits
+ if(hf->pStream != NULL)
+ {
+ FileStream_GetSize(hf->pStream, &FileSize);
+ }
+ else
+ {
+ FileSize = SFileGetFileSize(hFile, NULL);
+ }
+
+ // Handle the NULL and non-NULL values of plFilePosHigh
+ // Non-NULL: The DeltaPos is combined from lFilePos and *lpFilePosHigh
+ // NULL: The DeltaPos is sign-extended value of lFilePos
+ DeltaPos = (plFilePosHigh != NULL) ? MAKE_OFFSET64(plFilePosHigh[0], lFilePos) : (ULONGLONG)(LONGLONG)lFilePos;
+
+ // Get the relative point where to move from
+ switch(dwMoveMethod)
+ {
+ case FILE_BEGIN:
+
+ // Move relative to the file begin.
+ OldPosition = 0;
+ break;
+
+ case FILE_CURRENT:
+
+ // Retrieve the current file position
+ if(hf->pStream != NULL)
+ {
+ FileStream_GetPos(hf->pStream, &OldPosition);
+ }
+ else
+ {
+ OldPosition = hf->dwFilePos;
+ }
+ break;
+
+ case FILE_END:
+
+ // Move relative to the end of the file
+ OldPosition = FileSize;
+ break;
+
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SFILE_INVALID_POS;
+ }
+
+ // Calculate the new position
+ NewPosition = OldPosition + DeltaPos;
+
+ // If moving backward, don't allow the new position go negative
+ if((LONGLONG)DeltaPos < 0)
+ {
+ if(NewPosition > FileSize) // Position is negative
+ {
+ SetLastError(ERROR_NEGATIVE_SEEK);
+ return SFILE_INVALID_POS;
+ }
+ }
+
+ // If moving forward, don't allow the new position go past the end of the file
+ else
+ {
+ if(NewPosition > FileSize)
+ NewPosition = FileSize;
+ }
+
+ // Now apply the file pointer to the file
+ if(hf->pStream != NULL)
+ {
+ if(!FileStream_Read(hf->pStream, &NewPosition, NULL, 0))
+ return SFILE_INVALID_POS;
+ }
+ else
+ {
+ hf->dwFilePos = (DWORD)NewPosition;
+ }
+
+ // Return the new file position
+ if(plFilePosHigh != NULL)
+ *plFilePosHigh = (LONG)(NewPosition >> 32);
+ return (DWORD)NewPosition;
+}
diff --git a/dep/StormLib/src/SFileVerify.cpp b/dep/StormLib/src/SFileVerify.cpp
new file mode 100644
index 000000000..0248b9136
--- /dev/null
+++ b/dep/StormLib/src/SFileVerify.cpp
@@ -0,0 +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 */
+/* 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(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_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_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/dep/StormLib/src/StormCommon.h b/dep/StormLib/src/StormCommon.h
new file mode 100644
index 000000000..4a016c6da
--- /dev/null
+++ b/dep/StormLib/src/StormCommon.h
@@ -0,0 +1,387 @@
+/*****************************************************************************/
+/* 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
+#endif
+
+// Include functions from bzlib
+#ifndef __SYS_BZLIB
+ #include "bzip2/bzlib.h"
+#else
+ #include
+#endif
+
+//-----------------------------------------------------------------------------
+// Cryptography support
+
+// Headers from LibTomCrypt
+#include "tomcrypt.h"
+
+#include "rsa/rsa_verify_simple.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 StringCopy(char * szTarget, size_t cchTarget, const char * szSource);
+void StringCat(char * szTarget, size_t cchTargetMax, const char * szSource);
+
+#ifdef _UNICODE
+void StringCopy(TCHAR * szTarget, size_t cchTarget, const char * szSource);
+void StringCopy(char * szTarget, size_t cchTarget, const TCHAR * szSource);
+void StringCopy(TCHAR * szTarget, size_t cchTarget, const TCHAR * szSource);
+void StringCat(TCHAR * szTarget, size_t cchTargetMax, const TCHAR * szSource);
+#endif
+
+//-----------------------------------------------------------------------------
+// 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 GetNearestPowerOfTwo(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 bIsWarcraft3Map);
+
+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);
+
+template
+const XCHAR * GetPlainFileName(const XCHAR * szFileName)
+{
+ const XCHAR * szPlainName = szFileName;
+
+ while(*szFileName != 0)
+ {
+ if(*szFileName == '\\' || *szFileName == '/')
+ szPlainName = szFileName + 1;
+ szFileName++;
+ }
+
+ return szPlainName;
+}
+
+//-----------------------------------------------------------------------------
+// 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/dep/StormLib/src/StormLib.h b/dep/StormLib/src/StormLib.h
new file mode 100644
index 000000000..31f6edae9
--- /dev/null
+++ b/dep/StormLib/src/StormLib.h
@@ -0,0 +1,1124 @@
+/*****************************************************************************/
+/* StormLib.h Copyright (c) Ladislav Zezula 1999-2017 */
+/*---------------------------------------------------------------------------*/
+/* StormLib library v 9.22 */
+/* */
+/* Author : Ladislav Zezula */
+/* E-mail : ladik@zezula.net */
+/* WWW : http://www.zezula.net */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.99 1.00 Lad Created */
+/* 24.03.03 2.50 Lad Version 2.50 */
+/* 02.04.03 3.00 Lad Version 3.00 with compression */
+/* 11.04.03 3.01 Lad Renamed to StormLib.h for compatibility with */
+/* original headers for Storm.dll */
+/* 10.05.03 3.02 Lad Added Pkware DCL compression */
+/* 26.05.03 4.00 Lad Completed all compressions */
+/* 18.06.03 4.01 Lad Added SFileSetFileLocale */
+/* Added SFileExtractFile */
+/* 26.07.03 4.02 Lad Implemented nameless rename and delete */
+/* 26.07.03 4.03 Lad Added support for protected MPQs */
+/* 28.08.03 4.10 Lad Fixed bugs that caused StormLib incorrectly work */
+/* with Diablo I savegames and with files having full */
+/* hash table */
+/* 08.12.03 4.11 DCH Fixed bug in reading file sector larger than 0x1000 */
+/* on certain files. */
+/* Fixed bug in AddFile with MPQ_FILE_REPLACE_EXISTING */
+/* (Thanx Daniel Chiamarello, dchiamarello@madvawes.com)*/
+/* 21.12.03 4.50 Lad Completed port for Mac */
+/* Fixed bug in compacting (if fsize is mul of 0x1000) */
+/* Fixed bug in SCompCompress */
+/* 27.05.04 4.51 Lad Changed memory management from new/delete to our */
+/* own macros */
+/* 22.06.04 4.60 Lad Optimized search. Support for multiple listfiles. */
+/* 30.09.04 4.61 Lad Fixed some bugs (Aaargh !!!) */
+/* Correctly works if HashTableSize > BlockTableSize */
+/* 29.12.04 4.70 Lad Fixed compatibility problem with MPQs from WoW */
+/* 14.07.05 5.00 Lad Added the BZLIB compression support */
+/* Added suport of files stored as single unit */
+/* 17.04.06 5.01 Lad Converted to MS Visual Studio 8.0 */
+/* Fixed issue with protected Warcraft 3 protected maps */
+/* 15.05.06 5.02 Lad Fixed issue with WoW 1.10+ */
+/* 07.09.06 5.10 Lad Fixed processing files longer than 2GB */
+/* 22.11.06 6.00 Lad Support for MPQ archives V2 */
+/* 12.06.07 6.10 Lad Support for (attributes) file */
+/* 10.09.07 6.12 Lad Support for MPQs protected by corrupting hash table */
+/* 03.12.07 6.13 Lad Support for MPQs with hash tbl size > block tbl size */
+/* 07.04.08 6.20 Lad Added SFileFlushArchive */
+/* 09.04.08 Lad Removed FilePointer variable from MPQ handle */
+/* structure, as it caused more problems than benefits */
+/* 12.05.08 6.22 Lad Support for w3xMaster map protector */
+/* 05.10.08 6.23 Lad Support for protectors who set negative values in */
+/* the table of file blocks */
+/* 26.05.09 6.24 Lad Fixed search for multiple lang files with deleted */
+/* entries */
+/* 03.09.09 6.25 Lad Fixed decompression bug in huffmann decompression */
+/* 22.03.10 6.50 Lad New compressions in Starcraft II (LZMA, sparse) */
+/* Fixed compacting MPQs that contain single unit files */
+/* 26.04.10 7.00 Lad Major rewrite */
+/* 08.06.10 7.10 Lad Support for partial MPQs */
+/* 08.07.10 7.11 Lad Support for MPQs v 3.0 */
+/* 20.08.10 7.20 Lad Support for opening multiple MPQs in patch mode */
+/* 20.09.10 8.00 Lad MPQs v 4, HET and BET tables */
+/* 07.01.11 8.01 Lad Write support for MPQs v 3 and 4 */
+/* 15.09.11 8.04 Lad Bug fixes, testing for Diablo III MPQs */
+/* 26.04.12 8.10 Lad Support for data map, added SFileGetArchiveBitmap */
+/* 29.05.12 8.20 Lad C-only interface */
+/* 14.01.13 8.21 Lad ADPCM and Huffmann (de)compression refactored */
+/* 04.12.13 9.00 Lad Unit tests, bug fixes */
+/* 27.08.14 9.10 Lad Signing archives with weak digital signature */
+/* 25.11.14 9.11 Lad Fixed bug reading & creating HET table */
+/* 18.09.15 9.20 Lad Release 9.20 */
+/* 12.12.16 9.21 Lad Release 9.21 */
+/* 10.11.17 9.22 Lad Release 9.22 */
+/*****************************************************************************/
+
+#ifndef __STORMLIB_H__
+#define __STORMLIB_H__
+
+#ifdef _MSC_VER
+#pragma warning(disable:4668) // 'XXX' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
+#pragma warning(disable:4820) // 'XXX' : '2' bytes padding added after data member 'XXX::yyy'
+#endif
+
+#include "StormPort.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//-----------------------------------------------------------------------------
+// Use the apropriate library
+//
+// The library type is encoded in the library name as the following
+// StormLibXYZ.lib
+//
+// X - D for Debug version, R for Release version
+// Y - A for ANSI version, U for Unicode version
+// Z - S for static-linked CRT library, D for multithreaded DLL CRT library
+//
+
+#if defined(__STORMLIB_SELF__) && !defined(STORMLIB_NO_AUTO_LINK)
+#define STORMLIB_NO_AUTO_LINK // Define this if you don't want to link using pragmas when using msvc
+#endif
+
+#if defined(_MSC_VER) && !defined(STORMLIB_NO_AUTO_LINK)
+ #ifdef _DEBUG // DEBUG VERSIONS
+ #ifndef _UNICODE
+ #ifdef _DLL
+ #pragma comment(lib, "StormLibDAD.lib") // Debug Ansi CRT-DLL version
+ #else
+ #pragma comment(lib, "StormLibDAS.lib") // Debug Ansi CRT-LIB version
+ #endif
+ #else
+ #ifdef _DLL
+ #pragma comment(lib, "StormLibDUD.lib") // Debug Unicode CRT-DLL version
+ #else
+ #pragma comment(lib, "StormLibDUS.lib") // Debug Unicode CRT-LIB version
+ #endif
+ #endif
+ #else // RELEASE VERSIONS
+ #ifndef _UNICODE
+ #ifdef _DLL
+ #pragma comment(lib, "StormLibRAD.lib") // Release Ansi CRT-DLL version
+ #else
+ #pragma comment(lib, "StormLibRAS.lib") // Release Ansi CRT-LIB version
+ #endif
+ #else
+ #ifdef _DLL
+ #pragma comment(lib, "StormLibRUD.lib") // Release Unicode CRT-DLL version
+ #else
+ #pragma comment(lib, "StormLibRUS.lib") // Release Unicode CRT-LIB version
+ #endif
+ #endif
+ #endif
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Defines
+
+#define STORMLIB_VERSION 0x0916 // Current version of StormLib (9.21)
+#define STORMLIB_VERSION_STRING "9.22" // String version of StormLib version
+
+#define ID_MPQ 0x1A51504D // MPQ archive header ID ('MPQ\x1A')
+#define ID_MPQ_USERDATA 0x1B51504D // MPQ userdata entry ('MPQ\x1B')
+#define ID_MPK 0x1A4B504D // MPK archive header ID ('MPK\x1A')
+
+#define ERROR_AVI_FILE 10000 // Not a MPQ file, but an AVI file.
+#define ERROR_UNKNOWN_FILE_KEY 10001 // Returned by SFileReadFile when can't find file key
+#define ERROR_CHECKSUM_ERROR 10002 // Returned by SFileReadFile when sector CRC doesn't match
+#define ERROR_INTERNAL_FILE 10003 // The given operation is not allowed on internal file
+#define ERROR_BASE_FILE_MISSING 10004 // The file is present as incremental patch file, but base file is missing
+#define ERROR_MARKED_FOR_DELETE 10005 // The file was marked as "deleted" in the MPQ
+#define ERROR_FILE_INCOMPLETE 10006 // The required file part is missing
+#define ERROR_UNKNOWN_FILE_NAMES 10007 // A name of at least one file is unknown
+#define ERROR_CANT_FIND_PATCH_PREFIX 10008 // StormLib was unable to find patch prefix for the patches
+
+// Values for SFileCreateArchive
+#define HASH_TABLE_SIZE_MIN 0x00000004 // Verified: If there is 1 file, hash table size is 4
+#define HASH_TABLE_SIZE_DEFAULT 0x00001000 // Default hash table size for empty MPQs
+#define HASH_TABLE_SIZE_MAX 0x00080000 // Maximum acceptable hash table size
+
+#define HASH_ENTRY_DELETED 0xFFFFFFFE // Block index for deleted entry in the hash table
+#define HASH_ENTRY_FREE 0xFFFFFFFF // Block index for free entry in the hash table
+
+#define HET_ENTRY_DELETED 0x80 // NameHash1 value for a deleted entry
+#define HET_ENTRY_FREE 0x00 // NameHash1 value for free entry
+
+#define HASH_STATE_SIZE 0x60 // Size of LibTomCrypt's hash_state structure
+
+// Values for SFileOpenArchive
+#define SFILE_OPEN_HARD_DISK_FILE 2 // Open the archive on HDD
+#define SFILE_OPEN_CDROM_FILE 3 // Open the archive only if it is on CDROM
+
+// Values for SFileOpenFile
+#define SFILE_OPEN_FROM_MPQ 0x00000000 // Open the file from the MPQ archive
+#define SFILE_OPEN_CHECK_EXISTS 0xFFFFFFFC // Only check whether the file exists
+#define SFILE_OPEN_BASE_FILE 0xFFFFFFFD // Reserved for StormLib internal use
+#define SFILE_OPEN_ANY_LOCALE 0xFFFFFFFE // Reserved for StormLib internal use
+#define SFILE_OPEN_LOCAL_FILE 0xFFFFFFFF // Open a local file
+
+// Flags for TMPQArchive::dwFlags. Used internally
+#define MPQ_FLAG_READ_ONLY 0x00000001 // If set, the MPQ has been open for read-only access
+#define MPQ_FLAG_CHANGED 0x00000002 // If set, the MPQ tables have been changed
+#define MPQ_FLAG_MALFORMED 0x00000004 // Malformed data structure detected (W3M map protectors)
+#define MPQ_FLAG_HASH_TABLE_CUT 0x00000008 // The hash table goes beyond EOF
+#define MPQ_FLAG_BLOCK_TABLE_CUT 0x00000010 // The hash table goes beyond EOF
+#define MPQ_FLAG_CHECK_SECTOR_CRC 0x00000020 // Checking sector CRC when reading files
+#define MPQ_FLAG_SAVING_TABLES 0x00000040 // If set, we are saving MPQ internal files and MPQ tables
+#define MPQ_FLAG_PATCH 0x00000080 // If set, this MPQ is a patch archive
+#define MPQ_FLAG_WAR3_MAP 0x00000100 // If set, this MPQ is a map for Warcraft III
+#define MPQ_FLAG_LISTFILE_NONE 0x00000200 // Set when no (listfile) was found in InvalidateInternalFiles
+#define MPQ_FLAG_LISTFILE_NEW 0x00000400 // Set when (listfile) invalidated by InvalidateInternalFiles
+#define MPQ_FLAG_LISTFILE_FORCE 0x00000800 // Save updated listfile on exit
+#define MPQ_FLAG_ATTRIBUTES_NONE 0x00001000 // Set when no (attributes) was found in InvalidateInternalFiles
+#define MPQ_FLAG_ATTRIBUTES_NEW 0x00002000 // Set when (attributes) invalidated by InvalidateInternalFiles
+#define MPQ_FLAG_SIGNATURE_NONE 0x00004000 // Set when no (signature) was found in InvalidateInternalFiles
+#define MPQ_FLAG_SIGNATURE_NEW 0x00008000 // Set when (signature) invalidated by InvalidateInternalFiles
+
+// Values for TMPQArchive::dwSubType
+#define MPQ_SUBTYPE_MPQ 0x00000000 // The file is a MPQ file (Blizzard games)
+#define MPQ_SUBTYPE_SQP 0x00000001 // The file is a SQP file (War of the Immortals)
+#define MPQ_SUBTYPE_MPK 0x00000002 // The file is a MPK file (Longwu Online)
+
+// Return value for SFileGetFileSize and SFileSetFilePointer
+#define SFILE_INVALID_SIZE 0xFFFFFFFF
+#define SFILE_INVALID_POS 0xFFFFFFFF
+#define SFILE_INVALID_ATTRIBUTES 0xFFFFFFFF
+
+// Flags for SFileAddFile
+#define MPQ_FILE_IMPLODE 0x00000100 // Implode method (By PKWARE Data Compression Library)
+#define MPQ_FILE_COMPRESS 0x00000200 // Compress methods (By multiple methods)
+#define MPQ_FILE_ENCRYPTED 0x00010000 // Indicates whether file is encrypted
+#define MPQ_FILE_FIX_KEY 0x00020000 // File decryption key has to be fixed
+#define MPQ_FILE_PATCH_FILE 0x00100000 // The file is a patch file. Raw file data begin with TPatchInfo structure
+#define MPQ_FILE_SINGLE_UNIT 0x01000000 // File is stored as a single unit, rather than split into sectors (Thx, Quantam)
+#define MPQ_FILE_DELETE_MARKER 0x02000000 // File is a deletion marker. Used in MPQ patches, indicating that the file no longer exists.
+#define MPQ_FILE_SECTOR_CRC 0x04000000 // File has checksums for each sector.
+ // Ignored if file is not compressed or imploded.
+#define MPQ_FILE_SIGNATURE 0x10000000 // Present on STANDARD.SNP\(signature). The only occurence ever observed
+#define MPQ_FILE_EXISTS 0x80000000 // Set if file exists, reset when the file was deleted
+#define MPQ_FILE_REPLACEEXISTING 0x80000000 // Replace when the file exist (SFileAddFile)
+
+#define MPQ_FILE_COMPRESS_MASK 0x0000FF00 // Mask for a file being compressed
+
+#define MPQ_FILE_DEFAULT_INTERNAL 0xFFFFFFFF // Use default flags for internal files
+
+#define MPQ_FILE_VALID_FLAGS (MPQ_FILE_IMPLODE | \
+ MPQ_FILE_COMPRESS | \
+ MPQ_FILE_ENCRYPTED | \
+ MPQ_FILE_FIX_KEY | \
+ MPQ_FILE_PATCH_FILE | \
+ MPQ_FILE_SINGLE_UNIT | \
+ MPQ_FILE_DELETE_MARKER | \
+ MPQ_FILE_SECTOR_CRC | \
+ MPQ_FILE_SIGNATURE | \
+ MPQ_FILE_EXISTS)
+
+#define MPQ_FILE_VALID_FLAGS_W3X (MPQ_FILE_IMPLODE | \
+ MPQ_FILE_COMPRESS | \
+ MPQ_FILE_ENCRYPTED | \
+ MPQ_FILE_FIX_KEY | \
+ MPQ_FILE_DELETE_MARKER | \
+ MPQ_FILE_SECTOR_CRC | \
+ MPQ_FILE_SIGNATURE | \
+ MPQ_FILE_EXISTS)
+
+// We need to mask out the upper 4 bits of the block table index.
+// This is because it gets shifted out when calculating block table offset
+// BlockTableOffset = pHash->dwBlockIndex << 0x04
+// Malformed MPQ maps may contain block indexes like 0x40000001 or 0xF0000023
+#define BLOCK_INDEX_MASK 0x0FFFFFFF
+#define MPQ_BLOCK_INDEX(pHash) (pHash->dwBlockIndex & BLOCK_INDEX_MASK)
+
+// Compression types for multiple compressions
+#define MPQ_COMPRESSION_HUFFMANN 0x01 // Huffmann compression (used on WAVE files only)
+#define MPQ_COMPRESSION_ZLIB 0x02 // ZLIB compression
+#define MPQ_COMPRESSION_PKWARE 0x08 // PKWARE DCL compression
+#define MPQ_COMPRESSION_BZIP2 0x10 // BZIP2 compression (added in Warcraft III)
+#define MPQ_COMPRESSION_SPARSE 0x20 // Sparse compression (added in Starcraft 2)
+#define MPQ_COMPRESSION_ADPCM_MONO 0x40 // IMA ADPCM compression (mono)
+#define MPQ_COMPRESSION_ADPCM_STEREO 0x80 // IMA ADPCM compression (stereo)
+#define MPQ_COMPRESSION_LZMA 0x12 // LZMA compression. Added in Starcraft 2. This value is NOT a combination of flags.
+#define MPQ_COMPRESSION_NEXT_SAME 0xFFFFFFFF // Same compression
+
+// Constants for SFileAddWave
+#define MPQ_WAVE_QUALITY_HIGH 0 // Best quality, the worst compression
+#define MPQ_WAVE_QUALITY_MEDIUM 1 // Medium quality, medium compression
+#define MPQ_WAVE_QUALITY_LOW 2 // Low quality, the best compression
+
+// Signatures for HET and BET table
+#define HET_TABLE_SIGNATURE 0x1A544548 // 'HET\x1a'
+#define BET_TABLE_SIGNATURE 0x1A544542 // 'BET\x1a'
+
+// Decryption keys for MPQ tables
+#define MPQ_KEY_HASH_TABLE 0xC3AF3770 // Obtained by HashString("(hash table)", MPQ_HASH_FILE_KEY)
+#define MPQ_KEY_BLOCK_TABLE 0xEC83B3A3 // Obtained by HashString("(block table)", MPQ_HASH_FILE_KEY)
+
+#define LISTFILE_NAME "(listfile)" // Name of internal listfile
+#define SIGNATURE_NAME "(signature)" // Name of internal signature
+#define ATTRIBUTES_NAME "(attributes)" // Name of internal attributes file
+#define PATCH_METADATA_NAME "(patch_metadata)"
+
+#define MPQ_FORMAT_VERSION_1 0 // Up to The Burning Crusade
+#define MPQ_FORMAT_VERSION_2 1 // The Burning Crusade and newer
+#define MPQ_FORMAT_VERSION_3 2 // WoW Cataclysm Beta
+#define MPQ_FORMAT_VERSION_4 3 // WoW Cataclysm and newer
+
+// Flags for MPQ attributes
+#define MPQ_ATTRIBUTE_CRC32 0x00000001 // The "(attributes)" contains CRC32 for each file
+#define MPQ_ATTRIBUTE_FILETIME 0x00000002 // The "(attributes)" contains file time for each file
+#define MPQ_ATTRIBUTE_MD5 0x00000004 // The "(attributes)" contains MD5 for each file
+#define MPQ_ATTRIBUTE_PATCH_BIT 0x00000008 // The "(attributes)" contains a patch bit for each file
+#define MPQ_ATTRIBUTE_ALL 0x0000000F // Summary mask
+
+#define MPQ_ATTRIBUTES_V1 100 // (attributes) format version 1.00
+
+// Flags for SFileOpenArchive
+#define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file
+#define BASE_PROVIDER_MAP 0x00000001 // Base data source is memory-mapped file
+#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server
+#define BASE_PROVIDER_MASK 0x0000000F // Mask for base provider value
+
+#define STREAM_PROVIDER_FLAT 0x00000000 // Stream is linear with no offset mapping
+#define STREAM_PROVIDER_PARTIAL 0x00000010 // Stream is partial file (.part)
+#define STREAM_PROVIDER_MPQE 0x00000020 // Stream is an encrypted MPQ
+#define STREAM_PROVIDER_BLOCK4 0x00000030 // 0x4000 per block, text MD5 after each block, max 0x2000 blocks per file
+#define STREAM_PROVIDER_MASK 0x000000F0 // Mask for stream provider value
+
+#define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only
+#define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write
+#define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it
+#define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options
+
+#define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers
+#define STREAM_FLAGS_MASK 0x0000FFFF // Mask for all stream flags (providers+options)
+
+#define MPQ_OPEN_NO_LISTFILE 0x00010000 // Don't load the internal listfile
+#define MPQ_OPEN_NO_ATTRIBUTES 0x00020000 // Don't open the attributes
+#define MPQ_OPEN_NO_HEADER_SEARCH 0x00040000 // Don't search for the MPQ header past the begin of the file
+#define MPQ_OPEN_FORCE_MPQ_V1 0x00080000 // Always open the archive as MPQ v 1.00, ignore the "wFormatVersion" variable in the header
+#define MPQ_OPEN_CHECK_SECTOR_CRC 0x00100000 // On files with MPQ_FILE_SECTOR_CRC, the CRC will be checked when reading file
+#define MPQ_OPEN_PATCH 0x00200000 // This archive is a patch MPQ. Used internally.
+#define MPQ_OPEN_FORCE_LISTFILE 0x00400000 // Force add listfile even if there is none at the moment of opening
+#define MPQ_OPEN_READ_ONLY STREAM_FLAG_READ_ONLY
+
+// Flags for SFileCreateArchive
+#define MPQ_CREATE_LISTFILE 0x00100000 // Also add the (listfile) file
+#define MPQ_CREATE_ATTRIBUTES 0x00200000 // Also add the (attributes) file
+#define MPQ_CREATE_SIGNATURE 0x00400000 // Also add the (signature) file
+#define MPQ_CREATE_ARCHIVE_V1 0x00000000 // Creates archive of version 1 (size up to 4GB)
+#define MPQ_CREATE_ARCHIVE_V2 0x01000000 // Creates archive of version 2 (larger than 4 GB)
+#define MPQ_CREATE_ARCHIVE_V3 0x02000000 // Creates archive of version 3
+#define MPQ_CREATE_ARCHIVE_V4 0x03000000 // Creates archive of version 4
+#define MPQ_CREATE_ARCHIVE_VMASK 0x0F000000 // Mask for archive version
+
+#define FLAGS_TO_FORMAT_SHIFT 24 // (MPQ_CREATE_ARCHIVE_V4 >> FLAGS_TO_FORMAT_SHIFT) => MPQ_FORMAT_VERSION_4
+
+// Flags for SFileVerifyFile
+#define SFILE_VERIFY_SECTOR_CRC 0x00000001 // Verify sector checksum for the file, if available
+#define SFILE_VERIFY_FILE_CRC 0x00000002 // Verify file CRC, if available
+#define SFILE_VERIFY_FILE_MD5 0x00000004 // Verify file MD5, if available
+#define SFILE_VERIFY_RAW_MD5 0x00000008 // Verify raw file MD5, if available
+#define SFILE_VERIFY_ALL 0x0000000F // Verify every checksum possible
+
+// Return values for SFileVerifyFile
+#define VERIFY_OPEN_ERROR 0x0001 // Failed to open the file
+#define VERIFY_READ_ERROR 0x0002 // Failed to read all data from the file
+#define VERIFY_FILE_HAS_SECTOR_CRC 0x0004 // File has sector CRC
+#define VERIFY_FILE_SECTOR_CRC_ERROR 0x0008 // Sector CRC check failed
+#define VERIFY_FILE_HAS_CHECKSUM 0x0010 // File has CRC32
+#define VERIFY_FILE_CHECKSUM_ERROR 0x0020 // CRC32 check failed
+#define VERIFY_FILE_HAS_MD5 0x0040 // File has data MD5
+#define VERIFY_FILE_MD5_ERROR 0x0080 // MD5 check failed
+#define VERIFY_FILE_HAS_RAW_MD5 0x0100 // File has raw data MD5
+#define VERIFY_FILE_RAW_MD5_ERROR 0x0200 // Raw MD5 check failed
+#define VERIFY_FILE_ERROR_MASK (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR | VERIFY_FILE_RAW_MD5_ERROR)
+
+// Flags for SFileVerifyRawData (for MPQs version 4.0 or higher)
+#define SFILE_VERIFY_MPQ_HEADER 0x0001 // Verify raw MPQ header
+#define SFILE_VERIFY_HET_TABLE 0x0002 // Verify raw data of the HET table
+#define SFILE_VERIFY_BET_TABLE 0x0003 // Verify raw data of the BET table
+#define SFILE_VERIFY_HASH_TABLE 0x0004 // Verify raw data of the hash table
+#define SFILE_VERIFY_BLOCK_TABLE 0x0005 // Verify raw data of the block table
+#define SFILE_VERIFY_HIBLOCK_TABLE 0x0006 // Verify raw data of the hi-block table
+#define SFILE_VERIFY_FILE 0x0007 // Verify raw data of a file
+
+// Signature types
+#define SIGNATURE_TYPE_NONE 0x0000 // The archive has no signature in it
+#define SIGNATURE_TYPE_WEAK 0x0001 // The archive has weak signature
+#define SIGNATURE_TYPE_STRONG 0x0002 // The archive has strong signature
+
+// Return values for SFileVerifyArchive
+#define ERROR_NO_SIGNATURE 0 // There is no signature in the MPQ
+#define ERROR_VERIFY_FAILED 1 // There was an error during verifying signature (like no memory)
+#define ERROR_WEAK_SIGNATURE_OK 2 // There is a weak signature and sign check passed
+#define ERROR_WEAK_SIGNATURE_ERROR 3 // There is a weak signature but sign check failed
+#define ERROR_STRONG_SIGNATURE_OK 4 // There is a strong signature and sign check passed
+#define ERROR_STRONG_SIGNATURE_ERROR 5 // There is a strong signature but sign check failed
+
+#ifndef MD5_DIGEST_SIZE
+#define MD5_DIGEST_SIZE 0x10
+#endif
+
+#ifndef SHA1_DIGEST_SIZE
+#define SHA1_DIGEST_SIZE 0x14 // 160 bits
+#endif
+
+#ifndef LANG_NEUTRAL
+#define LANG_NEUTRAL 0x00 // Neutral locale
+#endif
+
+// Pointer to hashing function
+typedef DWORD (*HASH_STRING)(const char * szFileName, DWORD dwHashType);
+
+//-----------------------------------------------------------------------------
+// File information classes for SFileGetFileInfo and SFileFreeFileInfo
+
+typedef enum _SFileInfoClass
+{
+ // Info classes for archives
+ SFileMpqFileName, // Name of the archive file (TCHAR [])
+ SFileMpqStreamBitmap, // Array of bits, each bit means availability of one block (BYTE [])
+ SFileMpqUserDataOffset, // Offset of the user data header (ULONGLONG)
+ SFileMpqUserDataHeader, // Raw (unfixed) user data header (TMPQUserData)
+ SFileMpqUserData, // MPQ USer data, without the header (BYTE [])
+ SFileMpqHeaderOffset, // Offset of the MPQ header (ULONGLONG)
+ SFileMpqHeaderSize, // Fixed size of the MPQ header
+ SFileMpqHeader, // Raw (unfixed) archive header (TMPQHeader)
+ SFileMpqHetTableOffset, // Offset of the HET table, relative to MPQ header (ULONGLONG)
+ SFileMpqHetTableSize, // Compressed size of the HET table (ULONGLONG)
+ SFileMpqHetHeader, // HET table header (TMPQHetHeader)
+ SFileMpqHetTable, // HET table as pointer. Must be freed using SFileFreeFileInfo
+ SFileMpqBetTableOffset, // Offset of the BET table, relative to MPQ header (ULONGLONG)
+ SFileMpqBetTableSize, // Compressed size of the BET table (ULONGLONG)
+ SFileMpqBetHeader, // BET table header, followed by the flags (TMPQBetHeader + DWORD[])
+ SFileMpqBetTable, // BET table as pointer. Must be freed using SFileFreeFileInfo
+ SFileMpqHashTableOffset, // Hash table offset, relative to MPQ header (ULONGLONG)
+ SFileMpqHashTableSize64, // Compressed size of the hash table (ULONGLONG)
+ SFileMpqHashTableSize, // Size of the hash table, in entries (DWORD)
+ SFileMpqHashTable, // Raw (unfixed) hash table (TMPQBlock [])
+ SFileMpqBlockTableOffset, // Block table offset, relative to MPQ header (ULONGLONG)
+ SFileMpqBlockTableSize64, // Compressed size of the block table (ULONGLONG)
+ SFileMpqBlockTableSize, // Size of the block table, in entries (DWORD)
+ SFileMpqBlockTable, // Raw (unfixed) block table (TMPQBlock [])
+ SFileMpqHiBlockTableOffset, // Hi-block table offset, relative to MPQ header (ULONGLONG)
+ SFileMpqHiBlockTableSize64, // Compressed size of the hi-block table (ULONGLONG)
+ SFileMpqHiBlockTable, // The hi-block table (USHORT [])
+ SFileMpqSignatures, // Signatures present in the MPQ (DWORD)
+ SFileMpqStrongSignatureOffset, // Byte offset of the strong signature, relative to begin of the file (ULONGLONG)
+ SFileMpqStrongSignatureSize, // Size of the strong signature (DWORD)
+ SFileMpqStrongSignature, // The strong signature (BYTE [])
+ SFileMpqArchiveSize64, // Archive size from the header (ULONGLONG)
+ SFileMpqArchiveSize, // Archive size from the header (DWORD)
+ SFileMpqMaxFileCount, // Max number of files in the archive (DWORD)
+ SFileMpqFileTableSize, // Number of entries in the file table (DWORD)
+ SFileMpqSectorSize, // Sector size (DWORD)
+ SFileMpqNumberOfFiles, // Number of files (DWORD)
+ SFileMpqRawChunkSize, // Size of the raw data chunk for MD5
+ SFileMpqStreamFlags, // Stream flags (DWORD)
+ SFileMpqFlags, // Nonzero if the MPQ is read only (DWORD)
+
+ // Info classes for files
+ SFileInfoPatchChain, // Chain of patches where the file is (TCHAR [])
+ SFileInfoFileEntry, // The file entry for the file (TFileEntry)
+ SFileInfoHashEntry, // Hash table entry for the file (TMPQHash)
+ SFileInfoHashIndex, // Index of the hash table entry (DWORD)
+ SFileInfoNameHash1, // The first name hash in the hash table (DWORD)
+ SFileInfoNameHash2, // The second name hash in the hash table (DWORD)
+ SFileInfoNameHash3, // 64-bit file name hash for the HET/BET tables (ULONGLONG)
+ SFileInfoLocale, // File locale (DWORD)
+ SFileInfoFileIndex, // Block index (DWORD)
+ SFileInfoByteOffset, // File position in the archive (ULONGLONG)
+ SFileInfoFileTime, // File time (ULONGLONG)
+ SFileInfoFileSize, // Size of the file (DWORD)
+ SFileInfoCompressedSize, // Compressed file size (DWORD)
+ SFileInfoFlags, // File flags from (DWORD)
+ SFileInfoEncryptionKey, // File encryption key
+ SFileInfoEncryptionKeyRaw, // Unfixed value of the file key
+ SFileInfoCRC32, // CRC32 of the file
+} SFileInfoClass;
+
+//-----------------------------------------------------------------------------
+// Callback functions
+
+// Values for compact callback
+#define CCB_CHECKING_FILES 1 // Checking archive (dwParam1 = current, dwParam2 = total)
+#define CCB_CHECKING_HASH_TABLE 2 // Checking hash table (dwParam1 = current, dwParam2 = total)
+#define CCB_COPYING_NON_MPQ_DATA 3 // Copying non-MPQ data: No params used
+#define CCB_COMPACTING_FILES 4 // Compacting archive (dwParam1 = current, dwParam2 = total)
+#define CCB_CLOSING_ARCHIVE 5 // Closing archive: No params used
+
+typedef void (WINAPI * SFILE_DOWNLOAD_CALLBACK)(void * pvUserData, ULONGLONG ByteOffset, DWORD dwTotalBytes);
+typedef void (WINAPI * SFILE_ADDFILE_CALLBACK)(void * pvUserData, DWORD dwBytesWritten, DWORD dwTotalBytes, bool bFinalCall);
+typedef void (WINAPI * SFILE_COMPACT_CALLBACK)(void * pvUserData, DWORD dwWorkType, ULONGLONG BytesProcessed, ULONGLONG TotalBytes);
+
+typedef struct TFileStream TFileStream;
+
+//-----------------------------------------------------------------------------
+// Structure for bit arrays used for HET and BET tables
+
+typedef struct _TBitArray
+{
+ DWORD NumberOfBytes; // Total number of bytes in "Elements"
+ DWORD NumberOfBits; // Total number of bits that are available
+ BYTE Elements[1]; // Array of elements (variable length)
+} TBitArray;
+
+void GetBits(TBitArray * array, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize);
+void SetBits(TBitArray * array, unsigned int nBitPosition, unsigned int nBitLength, void * pvBuffer, int nResultSize);
+
+//-----------------------------------------------------------------------------
+// Structures related to MPQ format
+//
+// Note: All structures in this header file are supposed to remain private
+// to StormLib. The structures may (and will) change over time, as the MPQ
+// file format evolves. Programmers directly using these structures need to
+// be aware of this. And the last, but not least, NEVER do any modifications
+// to those structures directly, always use SFile* functions.
+//
+
+#define MPQ_HEADER_SIZE_V1 0x20
+#define MPQ_HEADER_SIZE_V2 0x2C
+#define MPQ_HEADER_SIZE_V3 0x44
+#define MPQ_HEADER_SIZE_V4 0xD0
+#define MPQ_HEADER_DWORDS (MPQ_HEADER_SIZE_V4 / 0x04)
+
+typedef struct _TMPQUserData
+{
+ // The ID_MPQ_USERDATA ('MPQ\x1B') signature
+ DWORD dwID;
+
+ // Maximum size of the user data
+ DWORD cbUserDataSize;
+
+ // Offset of the MPQ header, relative to the begin of this header
+ DWORD dwHeaderOffs;
+
+ // Appears to be size of user data header (Starcraft II maps)
+ DWORD cbUserDataHeader;
+} TMPQUserData;
+
+// MPQ file header
+//
+// We have to make sure that the header is packed OK.
+// Reason: A 64-bit integer at the beginning of 3.0 part,
+// which is offset 0x2C
+#pragma pack(push, 1)
+typedef struct _TMPQHeader
+{
+ // The ID_MPQ ('MPQ\x1A') signature
+ DWORD dwID;
+
+ // Size of the archive header
+ DWORD dwHeaderSize;
+
+ // 32-bit size of MPQ archive
+ // This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive
+ // is calculated as the size from the beginning of the archive to the end of the hash table,
+ // block table, or hi-block table (whichever is largest).
+ DWORD dwArchiveSize;
+
+ // 0 = Format 1 (up to The Burning Crusade)
+ // 1 = Format 2 (The Burning Crusade and newer)
+ // 2 = Format 3 (WoW - Cataclysm beta or newer)
+ // 3 = Format 4 (WoW - Cataclysm beta or newer)
+ USHORT wFormatVersion;
+
+ // Power of two exponent specifying the number of 512-byte disk sectors in each file sector
+ // in the archive. The size of each file sector in the archive is 512 * 2 ^ wSectorSize.
+ USHORT wSectorSize;
+
+ // Offset to the beginning of the hash table, relative to the beginning of the archive.
+ DWORD dwHashTablePos;
+
+ // Offset to the beginning of the block table, relative to the beginning of the archive.
+ DWORD dwBlockTablePos;
+
+ // Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for
+ // the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
+ DWORD dwHashTableSize;
+
+ // Number of entries in the block table
+ DWORD dwBlockTableSize;
+
+ //-- MPQ HEADER v 2 -------------------------------------------
+
+ // Offset to the beginning of array of 16-bit high parts of file offsets.
+ ULONGLONG HiBlockTablePos64;
+
+ // High 16 bits of the hash table offset for large archives.
+ USHORT wHashTablePosHi;
+
+ // High 16 bits of the block table offset for large archives.
+ USHORT wBlockTablePosHi;
+
+ //-- MPQ HEADER v 3 -------------------------------------------
+
+ // 64-bit version of the archive size
+ ULONGLONG ArchiveSize64;
+
+ // 64-bit position of the BET table
+ ULONGLONG BetTablePos64;
+
+ // 64-bit position of the HET table
+ ULONGLONG HetTablePos64;
+
+ //-- MPQ HEADER v 4 -------------------------------------------
+
+ // Compressed size of the hash table
+ ULONGLONG HashTableSize64;
+
+ // Compressed size of the block table
+ ULONGLONG BlockTableSize64;
+
+ // Compressed size of the hi-block table
+ ULONGLONG HiBlockTableSize64;
+
+ // Compressed size of the HET block
+ ULONGLONG HetTableSize64;
+
+ // Compressed size of the BET block
+ ULONGLONG BetTableSize64;
+
+ // Size of raw data chunk to calculate MD5.
+ // MD5 of each data chunk follows the raw file data.
+ DWORD dwRawChunkSize;
+
+ // MD5 of MPQ tables
+ unsigned char MD5_BlockTable[MD5_DIGEST_SIZE]; // MD5 of the block table before decryption
+ unsigned char MD5_HashTable[MD5_DIGEST_SIZE]; // MD5 of the hash table before decryption
+ unsigned char MD5_HiBlockTable[MD5_DIGEST_SIZE]; // MD5 of the hi-block table
+ unsigned char MD5_BetTable[MD5_DIGEST_SIZE]; // MD5 of the BET table before decryption
+ unsigned char MD5_HetTable[MD5_DIGEST_SIZE]; // MD5 of the HET table before decryption
+ unsigned char MD5_MpqHeader[MD5_DIGEST_SIZE]; // MD5 of the MPQ header from signature to (including) MD5_HetTable
+} TMPQHeader;
+#pragma pack(pop)
+
+// Hash table entry. All files in the archive are searched by their hashes.
+typedef struct _TMPQHash
+{
+ // The hash of the file path, using method A.
+ DWORD dwName1;
+
+ // The hash of the file path, using method B.
+ DWORD dwName2;
+
+#ifdef PLATFORM_LITTLE_ENDIAN
+
+ // The language of the file. This is a Windows LANGID data type, and uses the same values.
+ // 0 indicates the default language (American English), or that the file is language-neutral.
+ USHORT lcLocale;
+
+ // The platform the file is used for. 0 indicates the default platform.
+ // No other values have been observed.
+ BYTE Platform;
+ BYTE Reserved;
+
+#else
+
+ BYTE Platform;
+ BYTE Reserved;
+ USHORT lcLocale;
+
+#endif
+
+ // If the hash table entry is valid, this is the index into the block table of the file.
+ // Otherwise, one of the following two values:
+ // - FFFFFFFFh: Hash table entry is empty, and has always been empty.
+ // Terminates searches for a given file.
+ // - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
+ // Does not terminate searches for a given file.
+ DWORD dwBlockIndex;
+} TMPQHash;
+
+// File description block contains informations about the file
+typedef struct _TMPQBlock
+{
+ // Offset of the beginning of the file, relative to the beginning of the archive.
+ DWORD dwFilePos;
+
+ // Compressed file size
+ DWORD dwCSize;
+
+ // Only valid if the block is a file; otherwise meaningless, and should be 0.
+ // If the file is compressed, this is the size of the uncompressed file data.
+ DWORD dwFSize;
+
+ // Flags for the file. See MPQ_FILE_XXXX constants
+ DWORD dwFlags;
+} TMPQBlock;
+
+// Patch file information, preceding the sector offset table
+typedef struct _TPatchInfo
+{
+ DWORD dwLength; // Length of patch info header, in bytes
+ DWORD dwFlags; // Flags. 0x80000000 = MD5 (?)
+ DWORD dwDataSize; // Uncompressed size of the patch file
+ BYTE md5[0x10]; // MD5 of the entire patch file after decompression
+
+ // Followed by the sector table (variable length)
+} TPatchInfo;
+
+// This is the combined file entry for maintaining file list in the MPQ.
+// This structure is combined from block table, hi-block table,
+// (attributes) file and from (listfile).
+typedef struct _TFileEntry
+{
+ ULONGLONG FileNameHash; // Jenkins hash of the file name. Only used when the MPQ has BET table.
+ ULONGLONG ByteOffset; // Position of the file content in the MPQ, relative to the MPQ header
+ ULONGLONG FileTime; // FileTime from the (attributes) file. 0 if not present.
+ DWORD dwFileSize; // Decompressed size of the file
+ DWORD dwCmpSize; // Compressed size of the file (i.e., size of the file data in the MPQ)
+ DWORD dwFlags; // File flags (from block table)
+ DWORD dwCrc32; // CRC32 from (attributes) file. 0 if not present.
+ BYTE md5[MD5_DIGEST_SIZE]; // File MD5 from the (attributes) file. 0 if not present.
+ char * szFileName; // File name. NULL if not known.
+} TFileEntry;
+
+// Common header for HET and BET tables
+typedef struct _TMPQExtHeader
+{
+ DWORD dwSignature; // 'HET\x1A' or 'BET\x1A'
+ DWORD dwVersion; // Version. Seems to be always 1
+ DWORD dwDataSize; // Size of the contained table
+
+ // Followed by the table header
+ // Followed by the table data
+
+} TMPQExtHeader;
+
+// Structure for HET table header
+typedef struct _TMPQHetHeader
+{
+ TMPQExtHeader ExtHdr;
+
+ DWORD dwTableSize; // Size of the entire HET table, including HET_TABLE_HEADER (in bytes)
+ DWORD dwEntryCount; // Number of occupied entries in the HET table
+ DWORD dwTotalCount; // Total number of entries in the HET table
+ DWORD dwNameHashBitSize; // Size of the name hash entry (in bits)
+ DWORD dwIndexSizeTotal; // Total size of file index (in bits)
+ DWORD dwIndexSizeExtra; // Extra bits in the file index
+ DWORD dwIndexSize; // Effective size of the file index (in bits)
+ DWORD dwIndexTableSize; // Size of the block index subtable (in bytes)
+
+} TMPQHetHeader;
+
+// Structure for BET table header
+typedef struct _TMPQBetHeader
+{
+ TMPQExtHeader ExtHdr;
+
+ DWORD dwTableSize; // Size of the entire BET table, including the header (in bytes)
+ DWORD dwEntryCount; // Number of entries in the BET table. Must match HET_TABLE_HEADER::dwEntryCount
+ DWORD dwUnknown08;
+ DWORD dwTableEntrySize; // Size of one table entry (in bits)
+ DWORD dwBitIndex_FilePos; // Bit index of the file position (within the entry record)
+ DWORD dwBitIndex_FileSize; // Bit index of the file size (within the entry record)
+ DWORD dwBitIndex_CmpSize; // Bit index of the compressed size (within the entry record)
+ DWORD dwBitIndex_FlagIndex; // Bit index of the flag index (within the entry record)
+ DWORD dwBitIndex_Unknown; // Bit index of the ??? (within the entry record)
+ DWORD dwBitCount_FilePos; // Bit size of file position (in the entry record)
+ DWORD dwBitCount_FileSize; // Bit size of file size (in the entry record)
+ DWORD dwBitCount_CmpSize; // Bit size of compressed file size (in the entry record)
+ DWORD dwBitCount_FlagIndex; // Bit size of flags index (in the entry record)
+ DWORD dwBitCount_Unknown; // Bit size of ??? (in the entry record)
+ DWORD dwBitTotal_NameHash2; // Total bit size of the NameHash2
+ DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2
+ DWORD dwBitCount_NameHash2; // Effective size of NameHash2 (in bits)
+ DWORD dwNameHashArraySize; // Size of NameHash2 table, in bytes
+ DWORD dwFlagCount; // Number of flags in the following array
+
+} TMPQBetHeader;
+
+// Structure for parsed HET table
+typedef struct _TMPQHetTable
+{
+ TBitArray * pBetIndexes; // Bit array of FileIndex values
+ LPBYTE pNameHashes; // Array of NameHash1 values (NameHash1 = upper 8 bits of FileName hashe)
+ ULONGLONG AndMask64; // AND mask used for calculating file name hash
+ ULONGLONG OrMask64; // OR mask used for setting the highest bit of the file name hash
+
+ DWORD dwEntryCount; // Number of occupied entries in the HET table
+ DWORD dwTotalCount; // Number of entries in both NameHash and FileIndex table
+ DWORD dwNameHashBitSize; // Size of the name hash entry (in bits)
+ DWORD dwIndexSizeTotal; // Total size of one entry in pBetIndexes (in bits)
+ DWORD dwIndexSizeExtra; // Extra bits in the entry in pBetIndexes
+ DWORD dwIndexSize; // Effective size of one entry in pBetIndexes (in bits)
+} TMPQHetTable;
+
+// Structure for parsed BET table
+typedef struct _TMPQBetTable
+{
+ TBitArray * pNameHashes; // Array of NameHash2 entries (lower 24 bits of FileName hash)
+ TBitArray * pFileTable; // Bit-based file table
+ LPDWORD pFileFlags; // Array of file flags
+
+ DWORD dwTableEntrySize; // Size of one table entry, in bits
+ DWORD dwBitIndex_FilePos; // Bit index of the file position in the table entry
+ DWORD dwBitIndex_FileSize; // Bit index of the file size in the table entry
+ DWORD dwBitIndex_CmpSize; // Bit index of the compressed size in the table entry
+ DWORD dwBitIndex_FlagIndex; // Bit index of the flag index in the table entry
+ DWORD dwBitIndex_Unknown; // Bit index of ??? in the table entry
+ DWORD dwBitCount_FilePos; // Size of file offset (in bits) within table entry
+ DWORD dwBitCount_FileSize; // Size of file size (in bits) within table entry
+ DWORD dwBitCount_CmpSize; // Size of compressed file size (in bits) within table entry
+ DWORD dwBitCount_FlagIndex; // Size of flag index (in bits) within table entry
+ DWORD dwBitCount_Unknown; // Size of ??? (in bits) within table entry
+ DWORD dwBitTotal_NameHash2; // Total size of the NameHash2
+ DWORD dwBitExtra_NameHash2; // Extra bits in the NameHash2
+ DWORD dwBitCount_NameHash2; // Effective size of the NameHash2
+ DWORD dwEntryCount; // Number of entries
+ DWORD dwFlagCount; // Number of file flags in pFileFlags
+} TMPQBetTable;
+
+// Structure for patch prefix
+typedef struct _TMPQNamePrefix
+{
+ size_t nLength; // Length of this patch prefix. Can be 0
+ char szPatchPrefix[1]; // Patch name prefix (variable length). If not empty, it always starts with backslash.
+} TMPQNamePrefix;
+
+// Structure for name cache
+typedef struct _TMPQNameCache
+{
+ DWORD FirstNameOffset; // Offset of the first name in the name list (in bytes)
+ DWORD FreeSpaceOffset; // Offset of the first free byte in the name cache (in bytes)
+ DWORD TotalCacheSize; // Size, in bytes, of the cache. Includes wildcard
+ DWORD SearchOffset; // Used by SListFileFindFirstFile
+
+ // Followed by search mask (ASCIIZ, '\0' if none)
+ // Followed by name cache (ANSI multistring)
+
+} TMPQNameCache;
+
+// Archive handle structure
+typedef struct _TMPQArchive
+{
+ TFileStream * pStream; // Open stream for the MPQ
+
+ ULONGLONG UserDataPos; // Position of user data (relative to the begin of the file)
+ ULONGLONG MpqPos; // MPQ header offset (relative to the begin of the file)
+ ULONGLONG FileSize; // Size of the file at the moment of file open
+
+ struct _TMPQArchive * haPatch; // Pointer to patch archive, if any
+ struct _TMPQArchive * haBase; // Pointer to base ("previous version") archive, if any
+ TMPQNamePrefix * pPatchPrefix; // Patch prefix to precede names of patch files
+
+ TMPQUserData * pUserData; // MPQ user data (NULL if not present in the file)
+ TMPQHeader * pHeader; // MPQ file header
+ TMPQHash * pHashTable; // Hash table
+ TMPQHetTable * pHetTable; // HET table
+ TFileEntry * pFileTable; // File table
+ HASH_STRING pfnHashString; // Hashing function that will convert the file name into hash
+
+ TMPQUserData UserData; // MPQ user data. Valid only when ID_MPQ_USERDATA has been found
+ DWORD HeaderData[MPQ_HEADER_DWORDS]; // Storage for MPQ header
+
+ DWORD dwHETBlockSize;
+ DWORD dwBETBlockSize;
+ DWORD dwMaxFileCount; // Maximum number of files in the MPQ. Also total size of the file table.
+ DWORD dwFileTableSize; // Current size of the file table, e.g. index of the entry past the last occupied one
+ DWORD dwReservedFiles; // Number of entries reserved for internal MPQ files (listfile, attributes)
+ DWORD dwSectorSize; // Default size of one file sector
+ DWORD dwFileFlags1; // Flags for (listfile)
+ DWORD dwFileFlags2; // Flags for (attributes)
+ DWORD dwFileFlags3; // Flags for (signature)
+ DWORD dwAttrFlags; // Flags for the (attributes) file, see MPQ_ATTRIBUTE_XXX
+ DWORD dwFlags; // See MPQ_FLAG_XXXXX
+ DWORD dwSubType; // See MPQ_SUBTYPE_XXX
+
+ SFILE_ADDFILE_CALLBACK pfnAddFileCB; // Callback function for adding files
+ void * pvAddFileUserData; // User data thats passed to the callback
+
+ SFILE_COMPACT_CALLBACK pfnCompactCB; // Callback function for compacting the archive
+ ULONGLONG CompactBytesProcessed; // Amount of bytes that have been processed during a particular compact call
+ ULONGLONG CompactTotalBytes; // Total amount of bytes to be compacted
+ void * pvCompactUserData; // User data thats passed to the callback
+} TMPQArchive;
+
+// File handle structure
+typedef struct _TMPQFile
+{
+ TFileStream * pStream; // File stream. Only used on local files
+ TMPQArchive * ha; // Archive handle
+ TMPQHash * pHashEntry; // Pointer to hash table entry, if the file was open using hash table
+ TFileEntry * pFileEntry; // File entry for the file
+ ULONGLONG RawFilePos; // Offset in MPQ archive (relative to file begin)
+ ULONGLONG MpqFilePos; // Offset in MPQ archive (relative to MPQ header)
+ DWORD dwHashIndex; // Hash table index (0xFFFFFFFF if not used)
+ DWORD dwFileKey; // Decryption key
+ DWORD dwFilePos; // Current file position
+ DWORD dwMagic; // 'FILE'
+
+ struct _TMPQFile * hfPatch; // Pointer to opened patch file
+
+ TPatchInfo * pPatchInfo; // Patch info block, preceding the sector table
+ LPDWORD SectorOffsets; // Position of each file sector, relative to the begin of the file. Only for compressed files.
+ LPDWORD SectorChksums; // Array of sector checksums (either ADLER32 or MD5) values for each file sector
+ LPBYTE pbFileData; // Data of the file (single unit files, patched files)
+ DWORD cbFileData; // Size of file data
+ DWORD dwCompression0; // Compression that will be used on the first file sector
+ DWORD dwSectorCount; // Number of sectors in the file
+ DWORD dwPatchedFileSize; // Size of patched file. Used when saving patch file to the MPQ
+ DWORD dwDataSize; // Size of data in the file (on patch files, this differs from file size in block table entry)
+
+ LPBYTE pbFileSector; // Last loaded file sector. For single unit files, entire file content
+ DWORD dwSectorOffs; // File position of currently loaded file sector
+ DWORD dwSectorSize; // Size of the file sector. For single unit files, this is equal to the file size
+
+ unsigned char hctx[HASH_STATE_SIZE]; // Hash state for MD5. Used when saving file to MPQ
+ DWORD dwCrc32; // CRC32 value, used when saving file to MPQ
+
+ int nAddFileError; // Result of the "Add File" operations
+
+ bool bLoadedSectorCRCs; // If true, we already tried to load sector CRCs
+ bool bCheckSectorCRCs; // If true, then SFileReadFile will check sector CRCs when reading the file
+ bool bIsWriteHandle; // If true, this handle has been created by SFileCreateFile
+} TMPQFile;
+
+// Structure for SFileFindFirstFile and SFileFindNextFile
+typedef struct _SFILE_FIND_DATA
+{
+ char cFileName[MAX_PATH]; // Full name of the found file
+ char * szPlainName; // Plain name of the found file
+ DWORD dwHashIndex; // Hash table index for the file (HAH_ENTRY_FREE if no hash table)
+ DWORD dwBlockIndex; // Block table index for the file
+ DWORD dwFileSize; // File size in bytes
+ DWORD dwFileFlags; // MPQ file flags
+ DWORD dwCompSize; // Compressed file size
+ DWORD dwFileTimeLo; // Low 32-bits of the file time (0 if not present)
+ DWORD dwFileTimeHi; // High 32-bits of the file time (0 if not present)
+ LCID lcLocale; // Locale version
+
+} SFILE_FIND_DATA, *PSFILE_FIND_DATA;
+
+typedef struct _SFILE_CREATE_MPQ
+{
+ DWORD cbSize; // Size of this structure, in bytes
+ DWORD dwMpqVersion; // Version of the MPQ to be created
+ void *pvUserData; // Reserved, must be NULL
+ DWORD cbUserData; // Reserved, must be 0
+ DWORD dwStreamFlags; // Stream flags for creating the MPQ
+ DWORD dwFileFlags1; // File flags for (listfile). Use MPQ_FILE_DEFAULT_INTERNAL to set default flags
+ DWORD dwFileFlags2; // File flags for (attributes). Use MPQ_FILE_DEFAULT_INTERNAL to set default flags
+ DWORD dwFileFlags3; // File flags for (signature). Use MPQ_FILE_DEFAULT_INTERNAL to set default flags
+ DWORD dwAttrFlags; // Flags for the (attributes) file. If 0, no attributes will be created
+ DWORD dwSectorSize; // Sector size for compressed files
+ DWORD dwRawChunkSize; // Size of raw data chunk
+ DWORD dwMaxFileCount; // File limit for the MPQ
+
+} SFILE_CREATE_MPQ, *PSFILE_CREATE_MPQ;
+
+//-----------------------------------------------------------------------------
+// Stream support - functions
+
+// Structure used by FileStream_GetBitmap
+typedef struct _TStreamBitmap
+{
+ ULONGLONG StreamSize; // Size of the stream, in bytes
+ DWORD BitmapSize; // Size of the block map, in bytes
+ DWORD BlockCount; // Number of blocks in the stream
+ DWORD BlockSize; // Size of one block
+ DWORD IsComplete; // Nonzero if the file is complete
+
+ // Followed by the BYTE array, each bit means availability of one block
+
+} TStreamBitmap;
+
+// UNICODE versions of the file access functions
+TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags);
+TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags);
+const TCHAR * FileStream_GetFileName(TFileStream * pStream);
+size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider);
+
+bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData);
+
+bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, LPDWORD pcbLengthNeeded);
+bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead);
+bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite);
+bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize);
+bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize);
+bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset);
+bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFT);
+bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags);
+bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream);
+void FileStream_Close(TFileStream * pStream);
+
+//-----------------------------------------------------------------------------
+// Functions prototypes for Storm.dll
+
+// Typedefs for functions exported by Storm.dll
+typedef LCID (WINAPI * SFILESETLOCALE)(LCID);
+typedef bool (WINAPI * SFILEOPENARCHIVE)(const char *, DWORD, DWORD, HANDLE *);
+typedef bool (WINAPI * SFILECLOSEARCHIVE)(HANDLE);
+typedef bool (WINAPI * SFILEOPENFILEEX)(HANDLE, const char *, DWORD, HANDLE *);
+typedef bool (WINAPI * SFILECLOSEFILE)(HANDLE);
+typedef DWORD (WINAPI * SFILEGETFILESIZE)(HANDLE, LPDWORD);
+typedef DWORD (WINAPI * SFILESETFILEPOINTER)(HANDLE, LONG, LONG *, DWORD);
+typedef bool (WINAPI * SFILEREADFILE)(HANDLE, void *, DWORD, LPDWORD, LPOVERLAPPED);
+
+//-----------------------------------------------------------------------------
+// Functions for manipulation with StormLib global flags
+
+LCID WINAPI SFileGetLocale();
+LCID WINAPI SFileSetLocale(LCID lcNewLocale);
+
+//-----------------------------------------------------------------------------
+// Functions for archive manipulation
+
+bool WINAPI SFileOpenArchive(const TCHAR * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMpq);
+bool WINAPI SFileCreateArchive(const TCHAR * szMpqName, DWORD dwCreateFlags, DWORD dwMaxFileCount, HANDLE * phMpq);
+bool WINAPI SFileCreateArchive2(const TCHAR * szMpqName, PSFILE_CREATE_MPQ pCreateInfo, HANDLE * phMpq);
+
+bool WINAPI SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK DownloadCB, void * pvUserData);
+bool WINAPI SFileFlushArchive(HANDLE hMpq);
+bool WINAPI SFileCloseArchive(HANDLE hMpq);
+
+// Adds another listfile into MPQ. The currently added listfile(s) remain,
+// so you can use this API to combining more listfiles.
+// Note that this function is internally called by SFileFindFirstFile
+int WINAPI SFileAddListFile(HANDLE hMpq, const TCHAR * szListFile);
+
+// Archive compacting
+bool WINAPI SFileSetCompactCallback(HANDLE hMpq, SFILE_COMPACT_CALLBACK CompactCB, void * pvUserData);
+bool WINAPI SFileCompactArchive(HANDLE hMpq, const TCHAR * szListFile, bool bReserved);
+
+// Changing the maximum file count
+DWORD WINAPI SFileGetMaxFileCount(HANDLE hMpq);
+bool WINAPI SFileSetMaxFileCount(HANDLE hMpq, DWORD dwMaxFileCount);
+
+// Changing (attributes) file
+DWORD WINAPI SFileGetAttributes(HANDLE hMpq);
+bool WINAPI SFileSetAttributes(HANDLE hMpq, DWORD dwFlags);
+bool WINAPI SFileUpdateFileAttributes(HANDLE hMpq, const char * szFileName);
+
+//-----------------------------------------------------------------------------
+// Functions for manipulation with patch archives
+
+bool WINAPI SFileOpenPatchArchive(HANDLE hMpq, const TCHAR * szPatchMpqName, const char * szPatchPathPrefix, DWORD dwFlags);
+bool WINAPI SFileIsPatchedArchive(HANDLE hMpq);
+
+//-----------------------------------------------------------------------------
+// Functions for file manipulation
+
+// Reading from MPQ file
+bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName);
+bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile);
+DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD pdwFileSizeHigh);
+DWORD WINAPI SFileSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod);
+bool WINAPI SFileReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped);
+bool WINAPI SFileCloseFile(HANDLE hFile);
+
+// Retrieving info about a file in the archive
+bool WINAPI SFileGetFileInfo(HANDLE hMpqOrFile, SFileInfoClass InfoClass, void * pvFileInfo, DWORD cbFileInfo, LPDWORD pcbLengthNeeded);
+bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName);
+bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass);
+
+// High-level extract function
+bool WINAPI SFileExtractFile(HANDLE hMpq, const char * szToExtract, const TCHAR * szExtracted, DWORD dwSearchScope);
+
+//-----------------------------------------------------------------------------
+// Functions for file and archive verification
+
+// Generates file CRC32
+bool WINAPI SFileGetFileChecksums(HANDLE hMpq, const char * szFileName, LPDWORD pdwCrc32, char * pMD5);
+
+// Verifies file against its checksums stored in (attributes) attributes (depending on dwFlags).
+// For dwFlags, use one or more of MPQ_ATTRIBUTE_MD5
+DWORD WINAPI SFileVerifyFile(HANDLE hMpq, const char * szFileName, DWORD 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);
+
+// Verifies the signature, if present
+bool WINAPI SFileSignArchive(HANDLE hMpq, DWORD dwSignatureType);
+DWORD WINAPI SFileVerifyArchive(HANDLE hMpq);
+
+//-----------------------------------------------------------------------------
+// Functions for file searching
+
+HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DATA * lpFindFileData, const TCHAR * szListFile);
+bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData);
+bool WINAPI SFileFindClose(HANDLE hFind);
+
+HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const TCHAR * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData);
+bool WINAPI SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData);
+bool WINAPI SListFileFindClose(HANDLE hFind);
+
+// Locale support
+int WINAPI SFileEnumLocales(HANDLE hMpq, const char * szFileName, LCID * plcLocales, LPDWORD pdwMaxLocales, DWORD dwSearchScope);
+
+//-----------------------------------------------------------------------------
+// Support for adding files to the MPQ
+
+bool WINAPI SFileCreateFile(HANDLE hMpq, const char * szArchivedName, ULONGLONG FileTime, DWORD dwFileSize, LCID lcLocale, DWORD dwFlags, HANDLE * phFile);
+bool WINAPI SFileWriteFile(HANDLE hFile, const void * pvData, DWORD dwSize, DWORD dwCompression);
+bool WINAPI SFileFinishFile(HANDLE hFile);
+
+bool WINAPI SFileAddFileEx(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwCompression, DWORD dwCompressionNext = MPQ_COMPRESSION_NEXT_SAME);
+bool WINAPI SFileAddFile(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags);
+bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwQuality);
+bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope);
+bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szOldFileName, const char * szNewFileName);
+bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale);
+bool WINAPI SFileSetDataCompression(DWORD DataCompression);
+
+bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileCB, void * pvUserData);
+
+//-----------------------------------------------------------------------------
+// Compression and decompression
+
+int WINAPI SCompImplode (void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+int WINAPI SCompExplode (void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+int WINAPI SCompCompress (void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer, unsigned uCompressionMask, int nCmpType, int nCmpLevel);
+int WINAPI SCompDecompress (void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+int WINAPI SCompDecompress2(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+
+//-----------------------------------------------------------------------------
+// Non-Windows support for SetLastError/GetLastError
+
+#ifndef PLATFORM_WINDOWS
+
+void SetLastError(DWORD dwErrCode);
+DWORD GetLastError();
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Functions from Storm.dll. They use slightly different names for keeping
+// possibility to use them together with StormLib (StormXXX instead of SFileXXX)
+
+#ifdef __LINK_STORM_DLL__
+ #define STORM_ALTERNATE_NAMES // Force storm_dll.h to use alternate fnc names
+ #include "..\storm_dll\storm_dll.h"
+#endif // __LINK_STORM_DLL__
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // __STORMLIB_H__
diff --git a/dep/StormLib/src/StormPort.h b/dep/StormLib/src/StormPort.h
new file mode 100644
index 000000000..34151edf4
--- /dev/null
+++ b/dep/StormLib/src/StormPort.h
@@ -0,0 +1,333 @@
+/*****************************************************************************/
+/* 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 */
+/* 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 */
+/* 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
+ #include
+ #include
+ #include
+ #include
+ #include
+ #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
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+
+ // Support for PowerPC on Max OS X
+ #if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1)
+ #include
+ #include
+ #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
+
+#if !defined(PLATFORM_DEFINED) && defined(__HAIKU__)
+
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+
+ #ifndef __BIG_ENDIAN__
+ #define PLATFORM_LITTLE_ENDIAN
+ #endif
+
+ #define PLATFORM_HAIKU
+ #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
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+
+ #define PLATFORM_LITTLE_ENDIAN
+ #define PLATFORM_LINUX
+ # if defined (__FreeBSD__)
+ # define PLATFORM_FREEBSD
+ # endif
+ #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;
+ typedef const char * LPCTSTR;
+ typedef const char * LPCSTR;
+ typedef char * LPTSTR;
+ typedef char * LPSTR;
+
+ #ifdef PLATFORM_32BIT
+ #define _LZMA_UINT32_IS_ULONG
+ #endif
+
+ // Some Windows-specific defines
+ #ifndef MAX_PATH
+ #define MAX_PATH 1024
+ #endif
+
+ #ifndef _countof
+ #define _countof(x) (sizeof(x) / sizeof(x[0]))
+ #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 _tcsnicmp strncasecmp
+ #define _tprintf printf
+ #define _stprintf sprintf
+ #define _tremove remove
+ #define _tmain main
+
+ #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) || defined(PLATFORM_HAIKU) || defined(PLATFORM_FREEBSD)
+ #define stat64 stat
+ #define fstat64 fstat
+ #define lseek64 lseek
+ #define ftruncate64 ftruncate
+ #define off64_t off_t
+ #define O_LARGEFILE 0
+#endif
+
+#if defined(PLATFORM_FREEBSD)
+ #define lstat64 lstat
+#endif
+
+// Platform-specific error codes for UNIX-based platforms
+#if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU)
+ #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_NEGATIVE_SEEK 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/dep/StormLib/src/adpcm/adpcm.cpp b/dep/StormLib/src/adpcm/adpcm.cpp
new file mode 100644
index 000000000..c8086ab6b
--- /dev/null
+++ b/dep/StormLib/src/adpcm/adpcm.cpp
@@ -0,0 +1,401 @@
+/*****************************************************************************/
+/* adpcm.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* This module contains implementation of adpcm decompression method used by */
+/* Storm.dll to decompress WAVE files. Thanks to Tom Amigo for releasing */
+/* his sources. */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 11.03.03 1.00 Lad Splitted from Pkware.cpp */
+/* 20.05.03 2.00 Lad Added compression */
+/* 19.11.03 2.01 Dan Big endian handling */
+/* 10.01.13 3.00 Lad Refactored, beautified, documented :-) */
+/*****************************************************************************/
+
+#include
+
+#include "adpcm.h"
+
+//-----------------------------------------------------------------------------
+// Tables necessary dor decompression
+
+static int NextStepTable[] =
+{
+ -1, 0, -1, 4, -1, 2, -1, 6,
+ -1, 1, -1, 5, -1, 3, -1, 7,
+ -1, 1, -1, 5, -1, 3, -1, 7,
+ -1, 2, -1, 4, -1, 6, -1, 8
+};
+
+static int StepSizeTable[] =
+{
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+ 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
+ 32767
+};
+
+//-----------------------------------------------------------------------------
+// Helper class for writing output ADPCM data
+
+class TADPCMStream
+{
+ public:
+
+ TADPCMStream(void * pvBuffer, size_t cbBuffer)
+ {
+ pbBufferEnd = (unsigned char *)pvBuffer + cbBuffer;
+ pbBuffer = (unsigned char *)pvBuffer;
+ }
+
+ bool ReadByteSample(unsigned char & ByteSample)
+ {
+ // Check if there is enough space in the buffer
+ if(pbBuffer >= pbBufferEnd)
+ return false;
+
+ ByteSample = *pbBuffer++;
+ return true;
+ }
+
+ bool WriteByteSample(unsigned char ByteSample)
+ {
+ // Check if there is enough space in the buffer
+ if(pbBuffer >= pbBufferEnd)
+ return false;
+
+ *pbBuffer++ = ByteSample;
+ return true;
+ }
+
+ bool ReadWordSample(short & OneSample)
+ {
+ // Check if we have enough space in the output buffer
+ if((size_t)(pbBufferEnd - pbBuffer) < sizeof(short))
+ return false;
+
+ // Write the sample
+ OneSample = pbBuffer[0] + (((short)pbBuffer[1]) << 0x08);
+ pbBuffer += sizeof(short);
+ return true;
+ }
+
+ bool WriteWordSample(short OneSample)
+ {
+ // Check if we have enough space in the output buffer
+ if((size_t)(pbBufferEnd - pbBuffer) < sizeof(short))
+ return false;
+
+ // Write the sample
+ *pbBuffer++ = (unsigned char)(OneSample & 0xFF);
+ *pbBuffer++ = (unsigned char)(OneSample >> 0x08);
+ return true;
+ }
+
+ int LengthProcessed(void * pvOutBuffer)
+ {
+ return (int)((unsigned char *)pbBuffer - (unsigned char *)pvOutBuffer);
+ }
+
+ unsigned char * pbBufferEnd;
+ unsigned char * pbBuffer;
+};
+
+//----------------------------------------------------------------------------
+// Local functions
+
+static inline short GetNextStepIndex(int StepIndex, unsigned int EncodedSample)
+{
+ // Get the next step index
+ StepIndex = StepIndex + NextStepTable[EncodedSample & 0x1F];
+
+ // Don't make the step index overflow
+ if(StepIndex < 0)
+ StepIndex = 0;
+ else if(StepIndex > 88)
+ StepIndex = 88;
+
+ return (short)StepIndex;
+}
+
+static inline int UpdatePredictedSample(int PredictedSample, int EncodedSample, int Difference)
+{
+ // Is the sign bit set?
+ if(EncodedSample & 0x40)
+ {
+ PredictedSample -= Difference;
+ if(PredictedSample <= -32768)
+ PredictedSample = -32768;
+ }
+ else
+ {
+ PredictedSample += Difference;
+ if(PredictedSample >= 32767)
+ PredictedSample = 32767;
+ }
+
+ return PredictedSample;
+}
+
+static inline int DecodeSample(int PredictedSample, int EncodedSample, int StepSize, int Difference)
+{
+ if(EncodedSample & 0x01)
+ Difference += (StepSize >> 0);
+
+ if(EncodedSample & 0x02)
+ Difference += (StepSize >> 1);
+
+ if(EncodedSample & 0x04)
+ Difference += (StepSize >> 2);
+
+ if(EncodedSample & 0x08)
+ Difference += (StepSize >> 3);
+
+ if(EncodedSample & 0x10)
+ Difference += (StepSize >> 4);
+
+ if(EncodedSample & 0x20)
+ Difference += (StepSize >> 5);
+
+ return UpdatePredictedSample(PredictedSample, EncodedSample, Difference);
+}
+
+//----------------------------------------------------------------------------
+// Compression routine
+
+int CompressADPCM(void * pvOutBuffer, int cbOutBuffer, void * pvInBuffer, int cbInBuffer, int ChannelCount, int CompressionLevel)
+{
+ TADPCMStream os(pvOutBuffer, cbOutBuffer); // The output stream
+ TADPCMStream is(pvInBuffer, cbInBuffer); // The input stream
+ unsigned char BitShift = (unsigned char)(CompressionLevel - 1);
+ short PredictedSamples[MAX_ADPCM_CHANNEL_COUNT];// Predicted samples for each channel
+ short StepIndexes[MAX_ADPCM_CHANNEL_COUNT]; // Step indexes for each channel
+ short InputSample; // Input sample for the current channel
+ int TotalStepSize;
+ int ChannelIndex;
+ int AbsDifference;
+ int Difference;
+ int MaxBitMask;
+ int StepSize;
+
+// _tprintf(_T("== CMPR Started ==============\n"));
+
+ // First byte in the output stream contains zero. The second one contains the compression level
+ os.WriteByteSample(0);
+ if(!os.WriteByteSample(BitShift))
+ return 2;
+
+ // Set the initial step index for each channel
+ PredictedSamples[0] = PredictedSamples[1] = 0;
+ StepIndexes[0] = StepIndexes[1] = INITIAL_ADPCM_STEP_INDEX;
+
+ // Next, InitialSample value for each channel follows
+ for(int i = 0; i < ChannelCount; i++)
+ {
+ // Get the initial sample from the input stream
+ if(!is.ReadWordSample(InputSample))
+ return os.LengthProcessed(pvOutBuffer);
+
+ // Store the initial sample to our sample array
+ PredictedSamples[i] = InputSample;
+
+ // Also store the loaded sample to the output stream
+ if(!os.WriteWordSample(InputSample))
+ return os.LengthProcessed(pvOutBuffer);
+ }
+
+ // Get the initial index
+ ChannelIndex = ChannelCount - 1;
+
+ // Now keep reading the input data as long as there is something in the input buffer
+ while(is.ReadWordSample(InputSample))
+ {
+ int EncodedSample = 0;
+
+ // If we have two channels, we need to flip the channel index
+ ChannelIndex = (ChannelIndex + 1) % ChannelCount;
+
+ // Get the difference from the previous sample.
+ // If the difference is negative, set the sign bit to the encoded sample
+ AbsDifference = InputSample - PredictedSamples[ChannelIndex];
+ if(AbsDifference < 0)
+ {
+ AbsDifference = -AbsDifference;
+ EncodedSample |= 0x40;
+ }
+
+ // If the difference is too low (higher that difference treshold),
+ // write a step index modifier marker
+ StepSize = StepSizeTable[StepIndexes[ChannelIndex]];
+ if(AbsDifference < (StepSize >> CompressionLevel))
+ {
+ if(StepIndexes[ChannelIndex] != 0)
+ StepIndexes[ChannelIndex]--;
+
+ os.WriteByteSample(0x80);
+ }
+ else
+ {
+ // If the difference is too high, write marker that
+ // indicates increase in step size
+ while(AbsDifference > (StepSize << 1))
+ {
+ if(StepIndexes[ChannelIndex] >= 0x58)
+ break;
+
+ // Modify the step index
+ StepIndexes[ChannelIndex] += 8;
+ if(StepIndexes[ChannelIndex] > 0x58)
+ StepIndexes[ChannelIndex] = 0x58;
+
+ // Write the "modify step index" marker
+ StepSize = StepSizeTable[StepIndexes[ChannelIndex]];
+ os.WriteByteSample(0x81);
+ }
+
+ // Get the limit bit value
+ MaxBitMask = (1 << (BitShift - 1));
+ MaxBitMask = (MaxBitMask > 0x20) ? 0x20 : MaxBitMask;
+ Difference = StepSize >> BitShift;
+ TotalStepSize = 0;
+
+ for(int BitVal = 0x01; BitVal <= MaxBitMask; BitVal <<= 1)
+ {
+ if((TotalStepSize + StepSize) <= AbsDifference)
+ {
+ TotalStepSize += StepSize;
+ EncodedSample |= BitVal;
+ }
+ StepSize >>= 1;
+ }
+
+ PredictedSamples[ChannelIndex] = (short)UpdatePredictedSample(PredictedSamples[ChannelIndex],
+ EncodedSample,
+ Difference + TotalStepSize);
+ // Write the encoded sample to the output stream
+ if(!os.WriteByteSample((unsigned char)EncodedSample))
+ break;
+
+ // Calculates the step index to use for the next encode
+ StepIndexes[ChannelIndex] = GetNextStepIndex(StepIndexes[ChannelIndex], EncodedSample);
+ }
+ }
+
+// _tprintf(_T("== CMPR Ended ================\n"));
+ return os.LengthProcessed(pvOutBuffer);
+}
+
+//----------------------------------------------------------------------------
+// Decompression routine
+
+int DecompressADPCM(void * pvOutBuffer, int cbOutBuffer, void * pvInBuffer, int cbInBuffer, int ChannelCount)
+{
+ TADPCMStream os(pvOutBuffer, cbOutBuffer); // Output stream
+ TADPCMStream is(pvInBuffer, cbInBuffer); // Input stream
+ unsigned char EncodedSample;
+ unsigned char BitShift;
+ short PredictedSamples[MAX_ADPCM_CHANNEL_COUNT]; // Predicted sample for each channel
+ short StepIndexes[MAX_ADPCM_CHANNEL_COUNT]; // Predicted step index for each channel
+ int ChannelIndex; // Current channel index
+
+ // Initialize the StepIndex for each channel
+ PredictedSamples[0] = PredictedSamples[1] = 0;
+ StepIndexes[0] = StepIndexes[1] = INITIAL_ADPCM_STEP_INDEX;
+
+// _tprintf(_T("== DCMP Started ==============\n"));
+
+ // The first byte is always zero, the second one contains bit shift (compression level - 1)
+ is.ReadByteSample(BitShift);
+ is.ReadByteSample(BitShift);
+// _tprintf(_T("DCMP: BitShift = %u\n"), (unsigned int)(unsigned char)BitShift);
+
+ // Next, InitialSample value for each channel follows
+ for(int i = 0; i < ChannelCount; i++)
+ {
+ // Get the initial sample from the input stream
+ short InitialSample;
+
+ // Attempt to read the initial sample
+ if(!is.ReadWordSample(InitialSample))
+ return os.LengthProcessed(pvOutBuffer);
+
+// _tprintf(_T("DCMP: Loaded InitialSample[%u]: %04X\n"), i, (unsigned int)(unsigned short)InitialSample);
+
+ // Store the initial sample to our sample array
+ PredictedSamples[i] = InitialSample;
+
+ // Also store the loaded sample to the output stream
+ if(!os.WriteWordSample(InitialSample))
+ return os.LengthProcessed(pvOutBuffer);
+ }
+
+ // Get the initial index
+ ChannelIndex = ChannelCount - 1;
+
+ // Keep reading as long as there is something in the input buffer
+ while(is.ReadByteSample(EncodedSample))
+ {
+// _tprintf(_T("DCMP: Loaded Encoded Sample: %02X\n"), (unsigned int)(unsigned char)EncodedSample);
+
+ // If we have two channels, we need to flip the channel index
+ ChannelIndex = (ChannelIndex + 1) % ChannelCount;
+
+ if(EncodedSample == 0x80)
+ {
+ if(StepIndexes[ChannelIndex] != 0)
+ StepIndexes[ChannelIndex]--;
+
+// _tprintf(_T("DCMP: Writing Decoded Sample: %04lX\n"), (unsigned int)(unsigned short)PredictedSamples[ChannelIndex]);
+ if(!os.WriteWordSample(PredictedSamples[ChannelIndex]))
+ return os.LengthProcessed(pvOutBuffer);
+ }
+ else if(EncodedSample == 0x81)
+ {
+ // Modify the step index
+ StepIndexes[ChannelIndex] += 8;
+ if(StepIndexes[ChannelIndex] > 0x58)
+ StepIndexes[ChannelIndex] = 0x58;
+
+// _tprintf(_T("DCMP: New value of StepIndex: %04lX\n"), (unsigned int)(unsigned short)StepIndexes[ChannelIndex]);
+
+ // Next pass, keep going on the same channel
+ ChannelIndex = (ChannelIndex + 1) % ChannelCount;
+ }
+ else
+ {
+ int StepIndex = StepIndexes[ChannelIndex];
+ int StepSize = StepSizeTable[StepIndex];
+
+ // Encode one sample
+ PredictedSamples[ChannelIndex] = (short)DecodeSample(PredictedSamples[ChannelIndex],
+ EncodedSample,
+ StepSize,
+ StepSize >> BitShift);
+
+// _tprintf(_T("DCMP: Writing decoded sample: %04X\n"), (unsigned int)(unsigned short)PredictedSamples[ChannelIndex]);
+
+ // Write the decoded sample to the output stream
+ if(!os.WriteWordSample(PredictedSamples[ChannelIndex]))
+ break;
+
+ // Calculates the step index to use for the next encode
+ StepIndexes[ChannelIndex] = GetNextStepIndex(StepIndex, EncodedSample);
+// _tprintf(_T("DCMP: New step index: %04X\n"), (unsigned int)(unsigned short)StepIndexes[ChannelIndex]);
+ }
+ }
+
+// _tprintf(_T("DCMP: Total length written: %u\n"), (unsigned int)os.LengthProcessed(pvOutBuffer));
+// _tprintf(_T("== DCMP Ended ================\n"));
+
+ // Return total bytes written since beginning of the output buffer
+ return os.LengthProcessed(pvOutBuffer);
+}
diff --git a/dep/StormLib/src/adpcm/adpcm.h b/dep/StormLib/src/adpcm/adpcm.h
new file mode 100644
index 000000000..b1bf36143
--- /dev/null
+++ b/dep/StormLib/src/adpcm/adpcm.h
@@ -0,0 +1,26 @@
+/*****************************************************************************/
+/* adpcm.h Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Header file for adpcm decompress functions */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 31.03.03 1.00 Lad The first version of adpcm.h */
+/*****************************************************************************/
+
+#ifndef __ADPCM_H__
+#define __ADPCM_H__
+
+//-----------------------------------------------------------------------------
+// Defines
+
+#define MAX_ADPCM_CHANNEL_COUNT 2
+#define INITIAL_ADPCM_STEP_INDEX 0x2C
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int CompressADPCM (void * pvOutBuffer, int dwOutLength, void * pvInBuffer, int dwInLength, int nCmpType, int ChannelCount);
+int DecompressADPCM(void * pvOutBuffer, int dwOutLength, void * pvInBuffer, int dwInLength, int ChannelCount);
+
+#endif // __ADPCM_H__
diff --git a/dep/StormLib/src/adpcm/adpcm_old.cpp b/dep/StormLib/src/adpcm/adpcm_old.cpp
new file mode 100644
index 000000000..916fa3811
--- /dev/null
+++ b/dep/StormLib/src/adpcm/adpcm_old.cpp
@@ -0,0 +1,358 @@
+/*****************************************************************************/
+/* adpcm.cpp Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* This module contains implementation of adpcm decompression method used by */
+/* Storm.dll to decompress WAVE files. Thanks to Tom Amigo for releasing */
+/* his sources. */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 11.03.03 1.00 Lad Splitted from Pkware.cpp */
+/* 20.05.03 2.00 Lad Added compression */
+/* 19.11.03 2.01 Dan Big endian handling */
+/*****************************************************************************/
+
+#include "adpcm.h"
+
+//------------------------------------------------------------------------------
+// Structures
+
+typedef union _BYTE_AND_WORD_PTR
+{
+ short * pw;
+ unsigned char * pb;
+} BYTE_AND_WORD_PTR;
+
+typedef union _WORD_AND_BYTE_ARRAY
+{
+ short w;
+ unsigned char b[2];
+} WORD_AND_BYTE_ARRAY;
+
+//-----------------------------------------------------------------------------
+// Tables necessary dor decompression
+
+static long Table1503F120[] =
+{
+ 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000004, 0xFFFFFFFF, 0x00000002, 0xFFFFFFFF, 0x00000006,
+ 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0x00000005, 0xFFFFFFFF, 0x00000003, 0xFFFFFFFF, 0x00000007,
+ 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0x00000005, 0xFFFFFFFF, 0x00000003, 0xFFFFFFFF, 0x00000007,
+ 0xFFFFFFFF, 0x00000002, 0xFFFFFFFF, 0x00000004, 0xFFFFFFFF, 0x00000006, 0xFFFFFFFF, 0x00000008
+};
+
+static long step_table[] =
+{
+ 0x00000007, 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E,
+ 0x00000010, 0x00000011, 0x00000013, 0x00000015, 0x00000017, 0x00000019, 0x0000001C, 0x0000001F,
+ 0x00000022, 0x00000025, 0x00000029, 0x0000002D, 0x00000032, 0x00000037, 0x0000003C, 0x00000042,
+ 0x00000049, 0x00000050, 0x00000058, 0x00000061, 0x0000006B, 0x00000076, 0x00000082, 0x0000008F,
+ 0x0000009D, 0x000000AD, 0x000000BE, 0x000000D1, 0x000000E6, 0x000000FD, 0x00000117, 0x00000133,
+ 0x00000151, 0x00000173, 0x00000198, 0x000001C1, 0x000001EE, 0x00000220, 0x00000256, 0x00000292,
+ 0x000002D4, 0x0000031C, 0x0000036C, 0x000003C3, 0x00000424, 0x0000048E, 0x00000502, 0x00000583,
+ 0x00000610, 0x000006AB, 0x00000756, 0x00000812, 0x000008E0, 0x000009C3, 0x00000ABD, 0x00000BD0,
+ 0x00000CFF, 0x00000E4C, 0x00000FBA, 0x0000114C, 0x00001307, 0x000014EE, 0x00001706, 0x00001954,
+ 0x00001BDC, 0x00001EA5, 0x000021B6, 0x00002515, 0x000028CA, 0x00002CDF, 0x0000315B, 0x0000364B,
+ 0x00003BB9, 0x000041B2, 0x00004844, 0x00004F7E, 0x00005771, 0x0000602F, 0x000069CE, 0x00007462,
+ 0x00007FFF
+};
+
+//----------------------------------------------------------------------------
+// CompressWave
+
+// 1500EF70
+int CompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuffer, int dwInLength, int nChannels, int nCmpLevel)
+// ECX EDX
+{
+ WORD_AND_BYTE_ARRAY Wcmp;
+ BYTE_AND_WORD_PTR out; // Pointer to the output buffer
+ long SInt32Array1[2];
+ long SInt32Array2[2];
+ long SInt32Array3[2];
+ long nBytesRemains = dwOutLength; // Number of bytes remaining
+ long nWordsRemains; // Number of words remaining
+// unsigned char * pbSaveOutBuffer; // Copy of output buffer (actually not used)
+ unsigned long dwBitBuff;
+ unsigned long dwStopBit;
+ unsigned long dwBit;
+ unsigned long ebx;
+ unsigned long esi;
+ long nTableValue;
+ long nOneWord;
+ long var_1C;
+ long var_2C;
+ int nLength;
+ int nIndex;
+ int nValue;
+ int i, chnl;
+
+ // If less than 2 bytes remain, don't decompress anything
+// pbSaveOutBuffer = pbOutBuffer;
+ out.pb = pbOutBuffer;
+ if(nBytesRemains < 2)
+ return 2;
+
+ Wcmp.b[1] = (unsigned char)(nCmpLevel - 1);
+ Wcmp.b[0] = (unsigned char)0;
+
+ *out.pw++ = BSWAP_INT16_SIGNED(Wcmp.w);
+ if((out.pb - pbOutBuffer + (nChannels * 2)) > nBytesRemains)
+ return (int)(out.pb - pbOutBuffer + (nChannels * 2));
+
+ SInt32Array1[0] = SInt32Array1[1] = 0x2C;
+
+ for(i = 0; i < nChannels; i++)
+ {
+ nOneWord = BSWAP_INT16_SIGNED(*pwInBuffer++);
+ *out.pw++ = BSWAP_INT16_SIGNED((short)nOneWord);
+ SInt32Array2[i] = nOneWord;
+ }
+
+ // Weird. But it's there
+ nLength = dwInLength;
+ if(nLength < 0) // mov eax, dwInLength; cdq; sub eax, edx;
+ nLength++;
+
+ nLength = (nLength / 2) - (int)(out.pb - pbOutBuffer);
+ nLength = (nLength < 0) ? 0 : nLength;
+
+ nIndex = nChannels - 1; // edi
+ nWordsRemains = dwInLength / 2; // eax
+
+ // ebx - nChannels
+ // ecx - pwOutPos
+ for(chnl = nChannels; chnl < nWordsRemains; chnl++)
+ {
+ // 1500F030
+ if((out.pb - pbOutBuffer + 2) > nBytesRemains)
+ return (int)(out.pb - pbOutBuffer + 2);
+
+ // Switch index
+ if(nChannels == 2)
+ nIndex = (nIndex == 0) ? 1 : 0;
+
+ // Load one word from the input stream
+ nOneWord = BSWAP_INT16_SIGNED(*pwInBuffer++); // ecx - nOneWord
+ SInt32Array3[nIndex] = nOneWord;
+
+ // esi - SInt32Array2[nIndex]
+ // eax - nValue
+ nValue = nOneWord - SInt32Array2[nIndex];
+ nValue = (nValue < 0) ? ((nValue ^ 0xFFFFFFFF) + 1) : nValue;
+
+ ebx = (nOneWord >= SInt32Array2[nIndex]) ? 0 : 0x40;
+
+ // esi - SInt32Array2[nIndex]
+ // edx - step_table[SInt32Array2[nIndex]]
+ // edi - (step_table[SInt32Array1[nIndex]] >> nCmpLevel)
+ nTableValue = step_table[SInt32Array1[nIndex]];
+ dwStopBit = (unsigned long)nCmpLevel;
+
+ // edi - nIndex;
+ if(nValue < (nTableValue >> nCmpLevel))
+ {
+ if(SInt32Array1[nIndex] != 0)
+ SInt32Array1[nIndex]--;
+ *out.pb++ = 0x80;
+ }
+ else
+ {
+ while(nValue > nTableValue * 2)
+ {
+ if(SInt32Array1[nIndex] >= 0x58 || nLength == 0)
+ break;
+
+ SInt32Array1[nIndex] += 8;
+ if(SInt32Array1[nIndex] > 0x58)
+ SInt32Array1[nIndex] = 0x58;
+
+ nTableValue = step_table[SInt32Array1[nIndex]];
+ *out.pb++ = 0x81;
+ nLength--;
+ }
+
+ var_2C = nTableValue >> Wcmp.b[1];
+ dwBitBuff = 0;
+
+ esi = (1 << (dwStopBit - 2));
+ dwStopBit = (esi <= 0x20) ? esi : 0x20;
+
+ for(var_1C = 0, dwBit = 1; ; dwBit <<= 1)
+ {
+// esi = var_1C + nTableValue;
+ if((var_1C + nTableValue) <= nValue)
+ {
+ var_1C += nTableValue;
+ dwBitBuff |= dwBit;
+ }
+ if(dwBit == dwStopBit)
+ break;
+
+ nTableValue >>= 1;
+ }
+
+ nValue = SInt32Array2[nIndex];
+ if(ebx != 0)
+ {
+ nValue -= (var_1C + var_2C);
+ if(nValue < -32768)
+ nValue = -32768;
+ }
+ else
+ {
+ nValue += (var_1C + var_2C);
+ if(nValue > 32767)
+ nValue = 32767;
+ }
+
+ SInt32Array2[nIndex] = nValue;
+ *out.pb++ = (unsigned char)(dwBitBuff | ebx);
+ nTableValue = Table1503F120[dwBitBuff & 0x1F];
+ SInt32Array1[nIndex] = SInt32Array1[nIndex] + nTableValue;
+ if(SInt32Array1[nIndex] < 0)
+ SInt32Array1[nIndex] = 0;
+ else if(SInt32Array1[nIndex] > 0x58)
+ SInt32Array1[nIndex] = 0x58;
+ }
+ }
+
+ return (int)(out.pb - pbOutBuffer);
+}
+
+//----------------------------------------------------------------------------
+// DecompressADPCM
+
+// 1500F230
+int DecompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, unsigned char * pbInBuffer, int dwInLength, int nChannels)
+{
+ BYTE_AND_WORD_PTR out; // Output buffer
+ BYTE_AND_WORD_PTR in;
+ unsigned char * pbInBufferEnd = (pbInBuffer + dwInLength);
+ long SInt32Array1[2];
+ long SInt32Array2[2];
+ long nOneWord;
+ int nIndex;
+ int i;
+
+ SInt32Array1[0] = SInt32Array1[1] = 0x2C;
+ out.pb = pbOutBuffer;
+ in.pb = pbInBuffer;
+ in.pw++;
+
+ // Fill the Uint32Array2 array by channel values.
+ for(i = 0; i < nChannels; i++)
+ {
+ nOneWord = BSWAP_INT16_SIGNED(*in.pw++);
+ SInt32Array2[i] = nOneWord;
+ if(dwOutLength < 2)
+ return (int)(out.pb - pbOutBuffer);
+
+ *out.pw++ = BSWAP_INT16_SIGNED((short)nOneWord);
+ dwOutLength -= sizeof(short);
+ }
+
+ // Get the initial index
+ nIndex = nChannels - 1;
+
+ // Perform the decompression
+ while(in.pb < pbInBufferEnd)
+ {
+ unsigned char nOneByte = *in.pb++;
+
+ // Switch index
+ if(nChannels == 2)
+ nIndex = (nIndex == 0) ? 1 : 0;
+
+ // 1500F2A2: Get one byte from input buffer
+ if(nOneByte & 0x80)
+ {
+ switch(nOneByte & 0x7F)
+ {
+ case 0: // 1500F315
+ if(SInt32Array1[nIndex] != 0)
+ SInt32Array1[nIndex]--;
+
+ if(dwOutLength < 2)
+ return (int)(out.pb - pbOutBuffer);
+
+ *out.pw++ = BSWAP_INT16_SIGNED((unsigned short)SInt32Array2[nIndex]);
+ dwOutLength -= sizeof(unsigned short);
+ break;
+
+ case 1: // 1500F2E8
+ SInt32Array1[nIndex] += 8;
+ if(SInt32Array1[nIndex] > 0x58)
+ SInt32Array1[nIndex] = 0x58;
+
+ if(nChannels == 2)
+ nIndex = (nIndex == 0) ? 1 : 0;
+ break;
+
+ case 2: // 1500F41E
+ break;
+
+ default: // 1500F2C4
+ SInt32Array1[nIndex] -= 8;
+ if(SInt32Array1[nIndex] < 0)
+ SInt32Array1[nIndex] = 0;
+
+ if(nChannels == 2)
+ nIndex = (nIndex == 0) ? 1 : 0;
+ break;
+ }
+ }
+ else
+ {
+ // 1500F349
+ long temp1 = step_table[SInt32Array1[nIndex]]; // EDI
+ long temp2 = temp1 >> pbInBuffer[1]; // ESI
+ long temp3 = SInt32Array2[nIndex]; // ECX
+
+ if(nOneByte & 0x01) // EBX = nOneByte
+ temp2 += (temp1 >> 0);
+
+ if(nOneByte & 0x02)
+ temp2 += (temp1 >> 1);
+
+ if(nOneByte & 0x04)
+ temp2 += (temp1 >> 2);
+
+ if(nOneByte & 0x08)
+ temp2 += (temp1 >> 3);
+
+ if(nOneByte & 0x10)
+ temp2 += (temp1 >> 4);
+
+ if(nOneByte & 0x20)
+ temp2 += (temp1 >> 5);
+
+ if(nOneByte & 0x40)
+ {
+ temp3 = temp3 - temp2;
+ if(temp3 <= -32768)
+ temp3 = -32768;
+ }
+ else
+ {
+ temp3 = temp3 + temp2;
+ if(temp3 >= 32767)
+ temp3 = 32767;
+ }
+
+ SInt32Array2[nIndex] = temp3;
+ if(dwOutLength < 2)
+ break;
+
+ // Store the output 16-bit value
+ *out.pw++ = BSWAP_INT16_SIGNED((short)SInt32Array2[nIndex]);
+ dwOutLength -= 2;
+
+ SInt32Array1[nIndex] += Table1503F120[nOneByte & 0x1F];
+
+ if(SInt32Array1[nIndex] < 0)
+ SInt32Array1[nIndex] = 0;
+ else if(SInt32Array1[nIndex] > 0x58)
+ SInt32Array1[nIndex] = 0x58;
+ }
+ }
+ return (int)(out.pb - pbOutBuffer);
+}
diff --git a/dep/StormLib/src/adpcm/adpcm_old.h b/dep/StormLib/src/adpcm/adpcm_old.h
new file mode 100644
index 000000000..7b76affac
--- /dev/null
+++ b/dep/StormLib/src/adpcm/adpcm_old.h
@@ -0,0 +1,22 @@
+/*****************************************************************************/
+/* adpcm.h Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Header file for adpcm decompress functions */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 31.03.03 1.00 Lad The first version of adpcm.h */
+/*****************************************************************************/
+
+#ifndef __ADPCM_H__
+#define __ADPCM_H__
+
+//-----------------------------------------------------------------------------
+// Functions
+
+#include
+
+int CompressADPCM (unsigned char * pbOutBuffer, int dwOutLength, short * pwInBuffer, int dwInLength, int nCmpType, int nChannels);
+int DecompressADPCM(unsigned char * pbOutBuffer, int dwOutLength, unsigned char * pbInBuffer, int dwInLength, int nChannels);
+
+#endif // __ADPCM_H__
diff --git a/dep/StormLib/src/huffman/huff.cpp b/dep/StormLib/src/huffman/huff.cpp
new file mode 100644
index 000000000..9de5acb51
--- /dev/null
+++ b/dep/StormLib/src/huffman/huff.cpp
@@ -0,0 +1,873 @@
+/*****************************************************************************/
+/* huffman.cpp Copyright (c) Ladislav Zezula 1998-2003 */
+/*---------------------------------------------------------------------------*/
+/* This module contains Huffmann (de)compression methods */
+/* */
+/* Authors : Ladislav Zezula (ladik@zezula.net) */
+/* ShadowFlare (BlakFlare@hotmail.com) */
+/* */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.xx 1.00 Lad The first version of dcmp.cpp */
+/* 03.05.03 1.00 Lad Added compression methods */
+/* 19.11.03 1.01 Dan Big endian handling */
+/* 08.12.03 2.01 Dan High-memory handling (> 0x80000000) */
+/* 09.01.13 3.00 Lad Refactored, beautified, documented :-) */
+/*****************************************************************************/
+
+#include
+#include
+
+#include "huff.h"
+
+//-----------------------------------------------------------------------------
+// Table of byte-to-weight values
+
+// Table for (de)compression. Every compression type has 258 entries
+static unsigned char ByteToWeight_00[] =
+{
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x01
+static unsigned char ByteToWeight_01[] =
+{
+ 0x54, 0x16, 0x16, 0x0D, 0x0C, 0x08, 0x06, 0x05, 0x06, 0x05, 0x06, 0x03, 0x04, 0x04, 0x03, 0x05,
+ 0x0E, 0x0B, 0x14, 0x13, 0x13, 0x09, 0x0B, 0x06, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02,
+ 0x0D, 0x07, 0x09, 0x06, 0x06, 0x04, 0x03, 0x02, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02,
+ 0x09, 0x06, 0x04, 0x04, 0x04, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04,
+ 0x08, 0x03, 0x04, 0x07, 0x09, 0x05, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
+ 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02,
+ 0x06, 0x0A, 0x08, 0x08, 0x06, 0x07, 0x04, 0x03, 0x04, 0x04, 0x02, 0x02, 0x04, 0x02, 0x03, 0x03,
+ 0x04, 0x03, 0x07, 0x07, 0x09, 0x06, 0x04, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x0A, 0x02, 0x02, 0x03, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x03, 0x05, 0x02, 0x03,
+ 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x01, 0x01, 0x01,
+ 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x04, 0x04, 0x04, 0x07, 0x09, 0x08, 0x0C, 0x02,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x03,
+ 0x04, 0x01, 0x02, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
+ 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x02
+static unsigned char ByteToWeight_02[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x27, 0x00, 0x00, 0x23, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x06, 0x0E, 0x10, 0x04,
+ 0x06, 0x08, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x01, 0x01, 0x02, 0x01, 0x01,
+ 0x01, 0x04, 0x02, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x04, 0x01, 0x01, 0x02, 0x03, 0x03, 0x02,
+ 0x03, 0x01, 0x03, 0x06, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01,
+ 0x01, 0x29, 0x07, 0x16, 0x12, 0x40, 0x0A, 0x0A, 0x11, 0x25, 0x01, 0x03, 0x17, 0x10, 0x26, 0x2A,
+ 0x10, 0x01, 0x23, 0x23, 0x2F, 0x10, 0x06, 0x07, 0x02, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x03
+static unsigned char ByteToWeight_03[] =
+{
+ 0xFF, 0x0B, 0x07, 0x05, 0x0B, 0x02, 0x02, 0x02, 0x06, 0x02, 0x02, 0x01, 0x04, 0x02, 0x01, 0x03,
+ 0x09, 0x01, 0x01, 0x01, 0x03, 0x04, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
+ 0x05, 0x01, 0x01, 0x01, 0x0D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
+ 0x0A, 0x04, 0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01,
+ 0x05, 0x02, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03,
+ 0x01, 0x03, 0x01, 0x01, 0x02, 0x05, 0x01, 0x01, 0x04, 0x03, 0x05, 0x01, 0x03, 0x01, 0x03, 0x03,
+ 0x02, 0x01, 0x04, 0x03, 0x0A, 0x06, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x02, 0x01, 0x0A, 0x02, 0x05, 0x01, 0x01, 0x02, 0x07, 0x02, 0x17, 0x01, 0x05, 0x01, 0x01,
+ 0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x06, 0x02, 0x01, 0x04, 0x05, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x04
+static unsigned char ByteToWeight_04[] =
+{
+ 0xFF, 0xFB, 0x98, 0x9A, 0x84, 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x05
+static unsigned char ByteToWeight_05[] =
+{
+ 0xFF, 0xF1, 0x9D, 0x9E, 0x9A, 0x9B, 0x9A, 0x97, 0x93, 0x93, 0x8C, 0x8E, 0x86, 0x88, 0x80, 0x82,
+ 0x7C, 0x7C, 0x72, 0x73, 0x69, 0x6B, 0x5F, 0x60, 0x55, 0x56, 0x4A, 0x4B, 0x40, 0x41, 0x37, 0x37,
+ 0x2F, 0x2F, 0x27, 0x27, 0x21, 0x21, 0x1B, 0x1C, 0x17, 0x17, 0x13, 0x13, 0x10, 0x10, 0x0D, 0x0D,
+ 0x0B, 0x0B, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x19, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+ // Data for compression type 0x06
+static unsigned char ByteToWeight_06[] =
+{
+ 0xC3, 0xCB, 0xF5, 0x41, 0xFF, 0x7B, 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xBF, 0xCC, 0xF2, 0x40, 0xFD, 0x7C, 0xF7, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7A, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x07
+static unsigned char ByteToWeight_07[] =
+{
+ 0xC3, 0xD9, 0xEF, 0x3D, 0xF9, 0x7C, 0xE9, 0x1E, 0xFD, 0xAB, 0xF1, 0x2C, 0xFC, 0x5B, 0xFE, 0x17,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xBD, 0xD9, 0xEC, 0x3D, 0xF5, 0x7D, 0xE8, 0x1D, 0xFB, 0xAE, 0xF0, 0x2C, 0xFB, 0x5C, 0xFF, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+// Data for compression type 0x08
+static unsigned char ByteToWeight_08[] =
+{
+ 0xBA, 0xC5, 0xDA, 0x33, 0xE3, 0x6D, 0xD8, 0x18, 0xE5, 0x94, 0xDA, 0x23, 0xDF, 0x4A, 0xD1, 0x10,
+ 0xEE, 0xAF, 0xE4, 0x2C, 0xEA, 0x5A, 0xDE, 0x15, 0xF4, 0x87, 0xE9, 0x21, 0xF6, 0x43, 0xFC, 0x12,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xB0, 0xC7, 0xD8, 0x33, 0xE3, 0x6B, 0xD6, 0x18, 0xE7, 0x95, 0xD8, 0x23, 0xDB, 0x49, 0xD0, 0x11,
+ 0xE9, 0xB2, 0xE2, 0x2B, 0xE8, 0x5C, 0xDD, 0x15, 0xF1, 0x87, 0xE7, 0x20, 0xF7, 0x44, 0xFF, 0x13,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x5F, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+static unsigned char * WeightTables[0x09] =
+{
+ ByteToWeight_00,
+ ByteToWeight_01,
+ ByteToWeight_02,
+ ByteToWeight_03,
+ ByteToWeight_04,
+ ByteToWeight_05,
+ ByteToWeight_06,
+ ByteToWeight_07,
+ ByteToWeight_08
+};
+
+//-----------------------------------------------------------------------------
+// Debug/diagnostics
+
+#ifdef _DEBUG
+void DumpHuffmannTree(THTreeItem * pItem)
+{
+ THTreeItem * pChildLo; // Item with the lower weight
+ THTreeItem * pChildHi; // Item with the higher weight
+
+ // Get the lower-weight branch
+ pChildLo = pItem->pChildLo;
+ if(pChildLo != NULL)
+ {
+ // Get the higher-weight branch
+ pChildHi = pChildLo->pPrev;
+
+ // Parse the lower-weight branch
+ DumpHuffmannTree(pChildHi);
+ DumpHuffmannTree(pChildLo);
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// TInputStream functions
+
+TInputStream::TInputStream(void * pvInBuffer, size_t cbInBuffer)
+{
+ pbInBufferEnd = (unsigned char *)pvInBuffer + cbInBuffer;
+ pbInBuffer = (unsigned char *)pvInBuffer;
+ BitBuffer = 0;
+ BitCount = 0;
+}
+
+// Gets 7 bits from the stream. DOES NOT remove the bits from input stream
+unsigned int TInputStream::Peek7Bits()
+{
+ unsigned int dwReloadByte = 0;
+
+ // If there is not enough bits to get the value,
+ // we have to add 8 more bits from the input buffer
+ if(BitCount < 7)
+ {
+ dwReloadByte = *pbInBuffer++;
+ BitBuffer |= dwReloadByte << BitCount;
+ BitCount += 8;
+ }
+
+ // Return the first available 7 bits. DO NOT remove them from the input stream
+ return (BitBuffer & 0x7F);
+}
+
+// Gets one bit from input stream
+unsigned int TInputStream::Get1Bit()
+{
+ unsigned int OneBit = 0;
+
+ // Ensure that the input stream is reloaded, if there are no bits left
+ if(BitCount == 0)
+ {
+ // Refill the bit buffer
+ BitBuffer = *pbInBuffer++;
+ BitCount = 8;
+ }
+
+ // Copy the bit from bit buffer to the variable
+ OneBit = (BitBuffer & 0x01);
+ BitBuffer >>= 1;
+ BitCount--;
+
+ return OneBit;
+}
+
+// Gets the whole byte from the input stream.
+unsigned int TInputStream::Get8Bits()
+{
+ unsigned int dwReloadByte = 0;
+ unsigned int dwOneByte = 0;
+
+ // If there is not enough bits to get the value,
+ // we have to add 8 more bits from the input buffer
+ if(BitCount < 8)
+ {
+ dwReloadByte = *pbInBuffer++;
+ BitBuffer |= dwReloadByte << BitCount;
+ BitCount += 8;
+ }
+
+ // Return the lowest 8 its
+ dwOneByte = (BitBuffer & 0xFF);
+ BitBuffer >>= 8;
+ BitCount -= 8;
+ return dwOneByte;
+}
+
+void TInputStream::SkipBits(unsigned int dwBitsToSkip)
+{
+ unsigned int dwReloadByte = 0;
+
+ // If there is not enough bits in the buffer,
+ // we have to add 8 more bits from the input buffer
+ if(BitCount < dwBitsToSkip)
+ {
+ dwReloadByte = *pbInBuffer++;
+ BitBuffer |= dwReloadByte << BitCount;
+ BitCount += 8;
+ }
+
+ // Skip the remaining bits
+ BitBuffer >>= dwBitsToSkip;
+ BitCount -= dwBitsToSkip;
+}
+
+//-----------------------------------------------------------------------------
+// TOutputStream functions
+
+TOutputStream::TOutputStream(void * pvOutBuffer, size_t cbOutLength)
+{
+ pbOutBufferEnd = (unsigned char *)pvOutBuffer + cbOutLength;
+ pbOutBuffer = (unsigned char *)pvOutBuffer;
+ BitBuffer = 0;
+ BitCount = 0;
+}
+
+void TOutputStream::PutBits(unsigned int dwValue, unsigned int nBitCount)
+{
+ BitBuffer |= (dwValue << BitCount);
+ BitCount += nBitCount;
+
+ // Flush completed bytes
+ while(BitCount >= 8)
+ {
+ if(pbOutBuffer < pbOutBufferEnd)
+ *pbOutBuffer++ = (unsigned char)BitBuffer;
+
+ BitBuffer >>= 8;
+ BitCount -= 8;
+ }
+}
+
+void TOutputStream::Flush()
+{
+ while(BitCount != 0)
+ {
+ if(pbOutBuffer < pbOutBufferEnd)
+ *pbOutBuffer++ = (unsigned char)BitBuffer;
+
+ BitBuffer >>= 8;
+ BitCount -= ((BitCount > 8) ? 8 : BitCount);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Methods of the THTreeItem struct
+
+void THTreeItem::RemoveItem()
+{
+ if(pNext != NULL)
+ {
+ pPrev->pNext = pNext;
+ pNext->pPrev = pPrev;
+ pNext = pPrev = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// THuffmannTree class functions
+
+THuffmannTree::THuffmannTree(bool bCompression)
+{
+ pFirst = pLast = LIST_HEAD();
+ MinValidValue = 1;
+ ItemsUsed = 0;
+ bIsCmp0 = 0;
+
+ memset(ItemsByByte, 0, sizeof(ItemsByByte));
+
+ // If we are going to decompress data, we need to invalidate all item links
+ // We do so by zeroing their ValidValue, so it becomes lower MinValidValue
+ if(bCompression == false)
+ {
+ memset(QuickLinks, 0, sizeof(QuickLinks));
+ }
+}
+
+THuffmannTree::~THuffmannTree()
+{
+ // Our Huffmann tree does not use any memory allocations,
+ // so we don't need to do eny code in the destructor
+}
+
+void THuffmannTree::LinkTwoItems(THTreeItem * pItem1, THTreeItem * pItem2)
+{
+ pItem2->pNext = pItem1->pNext;
+ pItem2->pPrev = pItem1->pNext->pPrev;
+ pItem1->pNext->pPrev = pItem2;
+ pItem1->pNext = pItem2;
+}
+
+// Inserts item into the tree (?)
+void THuffmannTree::InsertItem(THTreeItem * pNewItem, TInsertPoint InsertPoint, THTreeItem * pInsertPoint)
+{
+ // Remove the item from the tree
+ pNewItem->RemoveItem();
+
+ if(pInsertPoint == NULL)
+ pInsertPoint = LIST_HEAD();
+
+ switch(InsertPoint)
+ {
+ case InsertAfter:
+ LinkTwoItems(pInsertPoint, pNewItem);
+ return;
+
+ case InsertBefore:
+ pNewItem->pNext = pInsertPoint; // Set next item (or pointer to pointer to first item)
+ pNewItem->pPrev = pInsertPoint->pPrev; // Set prev item (or last item in the tree)
+ pInsertPoint->pPrev->pNext = pNewItem;
+ pInsertPoint->pPrev = pNewItem; // Set the next/last item
+ return;
+ }
+}
+
+THTreeItem * THuffmannTree::FindHigherOrEqualItem(THTreeItem * pItem, unsigned int Weight)
+{
+ // Parse all existing items
+ if(pItem != NULL)
+ {
+ while(pItem != LIST_HEAD())
+ {
+ if(pItem->Weight >= Weight)
+ return pItem;
+
+ pItem = pItem->pPrev;
+ }
+ }
+
+ // If not found, we just get the first item
+ return LIST_HEAD();
+}
+
+THTreeItem * THuffmannTree::CreateNewItem(unsigned int DecompressedValue, unsigned int Weight, TInsertPoint InsertPoint)
+{
+ THTreeItem * pNewItem;
+
+ // Allocate new item from the item pool
+ pNewItem = &ItemBuffer[ItemsUsed++];
+
+ // Insert this item to the top of the tree
+ InsertItem(pNewItem, InsertPoint, NULL);
+
+ // Fill the rest of the item
+ pNewItem->DecompressedValue = DecompressedValue;
+ pNewItem->Weight = Weight;
+ pNewItem->pParent = NULL;
+ pNewItem->pChildLo = NULL;
+ return pNewItem;
+}
+
+unsigned int THuffmannTree::FixupItemPosByWeight(THTreeItem * pNewItem, unsigned int MaxWeight)
+{
+ THTreeItem * pHigherItem;
+
+ if(pNewItem->Weight < MaxWeight)
+ {
+ // Find an item that has higher weight than this one
+ pHigherItem = FindHigherOrEqualItem(pLast, pNewItem->Weight);
+
+ // Remove the item and put it to the new position
+ pNewItem->RemoveItem();
+ LinkTwoItems(pHigherItem, pNewItem);
+ }
+ else
+ {
+ MaxWeight = pNewItem->Weight;
+ }
+
+ // Return the (updated) maximum weight
+ return MaxWeight;
+}
+
+// Builds Huffman tree. Called with the first 8 bits loaded from input stream
+bool THuffmannTree::BuildTree(unsigned int CompressionType)
+{
+ THTreeItem * pNewItem;
+ THTreeItem * pChildLo;
+ THTreeItem * pChildHi;
+ unsigned char * WeightTable;
+ unsigned int MaxWeight; // [ESP+10] - The greatest character found in table
+
+ // Clear all pointers in HTree item array
+ memset(ItemsByByte, 0, sizeof(ItemsByByte));
+ MaxWeight = 0;
+
+ // Ensure that the compression type is in range
+ if((CompressionType & 0x0F) > 0x08)
+ return false;
+ WeightTable = WeightTables[CompressionType & 0x0F];
+
+ // Build the linear list of entries that is sorted by byte weight
+ for(unsigned int i = 0; i < 0x100; i++)
+ {
+ // Skip all the bytes which are zero.
+ if(WeightTable[i] != 0)
+ {
+ // Create new tree item
+ ItemsByByte[i] = pNewItem = CreateNewItem(i, WeightTable[i], InsertAfter);
+
+ // We need to put the item to the right place in the list
+ MaxWeight = FixupItemPosByWeight(pNewItem, MaxWeight);
+ }
+ }
+
+ // Insert termination entries at the end of the list
+ ItemsByByte[0x100] = CreateNewItem(0x100, 1, InsertBefore);
+ ItemsByByte[0x101] = CreateNewItem(0x101, 1, InsertBefore);
+
+ // Now we need to build the tree. We start at the last entry
+ // and go backwards to the first one
+ pChildLo = pLast;
+
+ // Work as long as both children are valid
+ // pChildHi is child with higher weight, pChildLo is the one with lower weight
+ while(pChildLo != LIST_HEAD())
+ {
+ // Also get and verify the higher-weight child
+ pChildHi = pChildLo->pPrev;
+ if(pChildHi == LIST_HEAD())
+ break;
+
+ // Create new parent item for the children
+ pNewItem = CreateNewItem(0, pChildHi->Weight + pChildLo->Weight, InsertAfter);
+
+ // Link both child items to their new parent
+ pChildLo->pParent = pNewItem;
+ pChildHi->pParent = pNewItem;
+ pNewItem->pChildLo = pChildLo;
+
+ // Fixup the item's position by its weight
+ MaxWeight = FixupItemPosByWeight(pNewItem, MaxWeight);
+
+ // Get the previous lower-weight child
+ pChildLo = pChildHi->pPrev;
+ }
+
+ // Initialize the MinValidValue to 1, which invalidates all quick-link items
+ MinValidValue = 1;
+ return true;
+}
+
+void THuffmannTree::IncWeightsAndRebalance(THTreeItem * pItem)
+{
+ THTreeItem * pHigherItem; // A previous item with greater or equal weight
+ THTreeItem * pChildHi; // The higher-weight child
+ THTreeItem * pChildLo; // The lower-weight child
+ THTreeItem * pParent;
+
+ // Climb up the tree and increment weight of each tree item
+ for(; pItem != NULL; pItem = pItem->pParent)
+ {
+ // Increment the item's weight
+ pItem->Weight++;
+
+ // Find a previous item with equal or greater weight, which is not equal to this item
+ pHigherItem = FindHigherOrEqualItem(pItem->pPrev, pItem->Weight);
+ pChildHi = pHigherItem->pNext;
+
+ // If the item is not equal to the tree item, we need to rebalance the tree
+ if(pChildHi != pItem)
+ {
+ // Move the previous item to the RIGHT from the given item
+ pChildHi->RemoveItem();
+ LinkTwoItems(pItem, pChildHi);
+
+ // Move the given item AFTER the greater-weight tree item
+ pItem->RemoveItem();
+ LinkTwoItems(pHigherItem, pItem);
+
+ // We need to maintain the tree so that pChildHi->Weight is >= pChildLo->Weight.
+ // Rebalance the tree accordingly.
+ pChildLo = pChildHi->pParent->pChildLo;
+ pParent = pItem->pParent;
+ if(pParent->pChildLo == pItem)
+ pParent->pChildLo = pChildHi;
+ if(pChildLo == pChildHi)
+ pChildHi->pParent->pChildLo = pItem;
+ pParent = pItem->pParent;
+ pItem->pParent = pChildHi->pParent;
+ pChildHi->pParent = pParent;
+
+ // Increment the global valid value. This invalidates all quick-link items.
+ MinValidValue++;
+ }
+ }
+}
+
+void THuffmannTree::InsertNewBranchAndRebalance(unsigned int Value1, unsigned int Value2)
+{
+ THTreeItem * pLastItem = pLast;
+ THTreeItem * pChildHi;
+ THTreeItem * pChildLo;
+
+ // Create higher-weight child
+ pChildHi = CreateNewItem(Value1, pLastItem->Weight, InsertBefore);
+ pChildHi->pParent = pLastItem;
+ ItemsByByte[Value1] = pChildHi;
+
+ // Create lower-weight child
+ pChildLo = CreateNewItem(Value2, 0, InsertBefore);
+ pChildLo->pParent = pLastItem;
+ pLastItem->pChildLo = pChildLo;
+ ItemsByByte[Value2] = pChildLo;
+
+ IncWeightsAndRebalance(pChildLo);
+}
+
+void THuffmannTree::EncodeOneByte(TOutputStream * os, THTreeItem * pItem)
+{
+ THTreeItem * pParent = pItem->pParent;
+ unsigned int BitBuffer = 0;
+ unsigned int BitCount = 0;
+
+ // Put 1's as long as there is parent
+ while(pParent != NULL)
+ {
+ // Fill the bit buffer
+ BitBuffer = (BitBuffer << 1) | ((pParent->pChildLo != pItem) ? 1 : 0);
+ BitCount++;
+
+ // Move to the parent
+ pItem = pParent;
+ pParent = pParent->pParent;
+ }
+
+ // Write the bits to the output stream
+ os->PutBits(BitBuffer, BitCount);
+}
+
+unsigned int THuffmannTree::DecodeOneByte(TInputStream * is)
+{
+ THTreeItem * pItemLink = NULL;
+ THTreeItem * pItem;
+ unsigned int ItemLinkIndex;
+ unsigned int BitCount = 0;
+
+ // Check for the end of the input stream
+ if(is->pbInBuffer >= is->pbInBufferEnd && is->BitCount < 7)
+ return 0x1FF;
+
+ // Get the eventual quick-link index
+ ItemLinkIndex = is->Peek7Bits();
+
+ // Is the quick-link item valid?
+ if(QuickLinks[ItemLinkIndex].ValidValue > MinValidValue)
+ {
+ // If that item needs less than 7 bits, we can get decompressed value directly
+ if(QuickLinks[ItemLinkIndex].ValidBits <= 7)
+ {
+ is->SkipBits(QuickLinks[ItemLinkIndex].ValidBits);
+ return QuickLinks[ItemLinkIndex].DecompressedValue;
+ }
+
+ // Otherwise we cannot get decompressed value directly
+ // but we can skip 7 levels of tree parsing
+ pItem = QuickLinks[ItemLinkIndex].pItem;
+ is->SkipBits(7);
+ }
+ else
+ {
+ // Just a sanity check
+ if(pFirst == LIST_HEAD())
+ return 0x1FF;
+
+ // We don't have the quick-link item, we need to parse the tree from its root
+ pItem = pFirst;
+ }
+
+ // Step down the tree until we find a terminal item
+ while(pItem->pChildLo != NULL)
+ {
+ // If the next bit in the compressed stream is set, we get the higher-weight
+ // child. Otherwise, get the lower-weight child.
+ pItem = is->Get1Bit() ? pItem->pChildLo->pPrev : pItem->pChildLo;
+ BitCount++;
+
+ // If the number of loaded bits reached 7,
+ // remember the current item for storing into quick-link item array
+ if(BitCount == 7)
+ pItemLink = pItem;
+ }
+
+ // If we didn't get the item from the quick-link array,
+ // set the entry in it
+ if(QuickLinks[ItemLinkIndex].ValidValue < MinValidValue)
+ {
+ // If the current compressed byte was more than 7 bits,
+ // set a quick-link item with pointer to tree item
+ if(BitCount > 7)
+ {
+ QuickLinks[ItemLinkIndex].ValidValue = MinValidValue;
+ QuickLinks[ItemLinkIndex].ValidBits = BitCount;
+ QuickLinks[ItemLinkIndex].pItem = pItemLink;
+ }
+ else
+ {
+ // Limit the quick-decompress item to lower amount of bits
+ // Coverity fix 84457: (x >> 32) has undefined behavior
+ ItemLinkIndex = (BitCount != 0) ? ItemLinkIndex & (0xFFFFFFFF >> (32 - BitCount)) : 0;
+ while(ItemLinkIndex < LINK_ITEM_COUNT)
+ {
+ // Fill the quick-decompress item
+ QuickLinks[ItemLinkIndex].ValidValue = MinValidValue;
+ QuickLinks[ItemLinkIndex].ValidBits = BitCount;
+ QuickLinks[ItemLinkIndex].DecompressedValue = pItem->DecompressedValue;
+
+ // Increment the index
+ ItemLinkIndex += (1 << BitCount);
+ }
+ }
+ }
+
+ // Return the decompressed value from the found item
+ return pItem->DecompressedValue;
+}
+
+unsigned int THuffmannTree::Compress(TOutputStream * os, void * pvInBuffer, int cbInBuffer, int CompressionType)
+{
+ unsigned char * pbInBufferEnd = (unsigned char *)pvInBuffer + cbInBuffer;
+ unsigned char * pbInBuffer = (unsigned char *)pvInBuffer;
+ unsigned char * pbOutBuff = os->pbOutBuffer;
+ unsigned char InputByte;
+
+ if(!BuildTree(CompressionType))
+ return 0;
+ bIsCmp0 = (CompressionType == 0);
+
+ // Store the compression type into output buffer
+ os->PutBits(CompressionType, 8);
+
+ // Process the entire input buffer
+ while(pbInBuffer < pbInBufferEnd)
+ {
+ // Get the (next) byte from the input buffer
+ InputByte = *pbInBuffer++;
+
+ // Do we have an item for such input value?
+ if(ItemsByByte[InputByte] == NULL)
+ {
+ // Encode the relationship
+ EncodeOneByte(os, ItemsByByte[0x101]);
+
+ // Store the loaded byte into output stream
+ os->PutBits(InputByte, 8);
+
+ InsertNewBranchAndRebalance(pLast->DecompressedValue, InputByte);
+
+ if(bIsCmp0)
+ {
+ IncWeightsAndRebalance(ItemsByByte[InputByte]);
+ continue;
+ }
+
+ IncWeightsAndRebalance(ItemsByByte[InputByte]);
+ }
+ else
+ {
+ EncodeOneByte(os, ItemsByByte[InputByte]);
+ }
+
+ if(bIsCmp0)
+ {
+ IncWeightsAndRebalance(ItemsByByte[InputByte]);
+ }
+ }
+
+ // Put the termination mark to the compressed stream
+ EncodeOneByte(os, ItemsByByte[0x100]);
+
+ // Flush the remaining bits
+ os->Flush();
+ return (unsigned int)(os->pbOutBuffer - pbOutBuff);
+}
+
+// Decompression using Huffman tree (1500E450)
+unsigned int THuffmannTree::Decompress(void * pvOutBuffer, unsigned int cbOutLength, TInputStream * is)
+{
+ unsigned char * pbOutBufferEnd = (unsigned char *)pvOutBuffer + cbOutLength;
+ unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer;
+ unsigned int DecompressedValue = 0;
+ unsigned int CompressionType = 0;
+
+ // Test the output length. Must not be NULL.
+ if(cbOutLength == 0)
+ return 0;
+
+ // Get the compression type from the input stream
+ CompressionType = is->Get8Bits();
+ bIsCmp0 = (CompressionType == 0) ? 1 : 0;
+
+ // Build the Huffman tree
+ if(!BuildTree(CompressionType))
+ return 0;
+
+ // Process the entire input buffer until end of the stream
+ while((DecompressedValue = DecodeOneByte(is)) != 0x100)
+ {
+ // Did an error occur?
+ if(DecompressedValue == 0x1FF) // An error occurred
+ return 0;
+
+ // Huffman tree needs to be modified
+ if(DecompressedValue == 0x101)
+ {
+ // The decompressed byte is stored in the next 8 bits
+ DecompressedValue = is->Get8Bits();
+
+ InsertNewBranchAndRebalance(pLast->DecompressedValue, DecompressedValue);
+
+ if(bIsCmp0 == 0)
+ IncWeightsAndRebalance(ItemsByByte[DecompressedValue]);
+ }
+
+ // A byte successfully decoded - store it in the output stream
+ *pbOutBuffer++ = (unsigned char)DecompressedValue;
+ if(pbOutBuffer >= pbOutBufferEnd)
+ break;
+
+ if(bIsCmp0)
+ {
+ IncWeightsAndRebalance(ItemsByByte[DecompressedValue]);
+ }
+ }
+
+ return (unsigned int)(pbOutBuffer - (unsigned char *)pvOutBuffer);
+}
+
diff --git a/dep/StormLib/src/huffman/huff.h b/dep/StormLib/src/huffman/huff.h
new file mode 100644
index 000000000..89993fdef
--- /dev/null
+++ b/dep/StormLib/src/huffman/huff.h
@@ -0,0 +1,143 @@
+/*****************************************************************************/
+/* huffman.h Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Description : */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.xx 1.00 Lad The first version of huffman.h */
+/* 03.05.03 2.00 Lad Added compression */
+/* 08.12.03 2.01 Dan High-memory handling (> 0x80000000) */
+/*****************************************************************************/
+
+#ifndef __HUFFMAN_H__
+#define __HUFFMAN_H__
+
+//-----------------------------------------------------------------------------
+// Defines
+
+#define HUFF_ITEM_COUNT 0x203 // Number of items in the item pool
+#define LINK_ITEM_COUNT 0x80 // Maximum number of quick-link items
+
+//-----------------------------------------------------------------------------
+// Structures and classes
+
+// Input stream for Huffmann decompression
+class TInputStream
+{
+ public:
+
+ TInputStream(void * pvInBuffer, size_t cbInBuffer);
+ unsigned int Get1Bit();
+ unsigned int Peek7Bits();
+ unsigned int Get8Bits();
+ void SkipBits(unsigned int BitCount);
+
+ unsigned char * pbInBufferEnd; // End position in the the input buffer
+ unsigned char * pbInBuffer; // Current position in the the input buffer
+ unsigned int BitBuffer; // Input bit buffer
+ unsigned int BitCount; // Number of bits remaining in 'dwBitBuff'
+};
+
+
+// Output stream for Huffmann compression
+class TOutputStream
+{
+ public:
+
+ TOutputStream(void * pvOutBuffer, size_t cbOutLength);
+ void PutBits(unsigned int dwValue, unsigned int nBitCount);
+ void Flush();
+
+ unsigned char * pbOutBufferEnd; // End position in the output buffer
+ unsigned char * pbOutBuffer; // Current position in the output buffer
+ unsigned int BitBuffer; // Bit buffer
+ unsigned int BitCount; // Number of bits in the bit buffer
+};
+
+// A virtual tree item that represents the head of the item list
+#define LIST_HEAD() ((THTreeItem *)(&pFirst))
+
+enum TInsertPoint
+{
+ InsertAfter = 1,
+ InsertBefore = 2
+};
+
+// Huffmann tree item
+struct THTreeItem
+{
+ THTreeItem() { pPrev = pNext = NULL; DecompressedValue = 0; Weight = 0; pParent = pChildLo = NULL; }
+// ~THTreeItem() { RemoveItem(); }
+
+ void RemoveItem();
+// void RemoveEntry();
+
+ THTreeItem * pNext; // Pointer to lower-weight tree item
+ THTreeItem * pPrev; // Pointer to higher-weight item
+ unsigned int DecompressedValue; // 08 - Decompressed byte value (also index in the array)
+ unsigned int Weight; // 0C - Weight
+ THTreeItem * pParent; // 10 - Pointer to parent item (NULL if none)
+ THTreeItem * pChildLo; // 14 - Pointer to the child with lower-weight child ("left child")
+};
+
+
+// Structure used for quick navigating in the huffmann tree.
+// Allows skipping up to 7 bits in the compressed stream, thus
+// decompressing a bit faster. Sometimes it can even get the decompressed
+// byte directly.
+struct TQuickLink
+{
+ unsigned int ValidValue; // If greater than THuffmannTree::MinValidValue, the entry is valid
+ unsigned int ValidBits; // Number of bits that are valid for this item link
+ union
+ {
+ THTreeItem * pItem; // Pointer to the item within the Huffmann tree
+ unsigned int DecompressedValue; // Value for direct decompression
+ };
+};
+
+
+// Structure for Huffman tree (Size 0x3674 bytes). Because I'm not expert
+// for the decompression, I do not know actually if the class is really a Hufmann
+// tree. If someone knows the decompression details, please let me know
+class THuffmannTree
+{
+ public:
+
+ THuffmannTree(bool bCompression);
+ ~THuffmannTree();
+
+ void LinkTwoItems(THTreeItem * pItem1, THTreeItem * pItem2);
+ void InsertItem(THTreeItem * item, TInsertPoint InsertPoint, THTreeItem * item2);
+
+ THTreeItem * FindHigherOrEqualItem(THTreeItem * pItem, unsigned int Weight);
+ THTreeItem * CreateNewItem(unsigned int DecompressedValue, unsigned int Weight, TInsertPoint InsertPoint);
+
+ unsigned int FixupItemPosByWeight(THTreeItem * pItem, unsigned int MaxWeight);
+ bool BuildTree(unsigned int CompressionType);
+
+ void IncWeightsAndRebalance(THTreeItem * pItem);
+ void InsertNewBranchAndRebalance(unsigned int Value1, unsigned int Value2);
+
+ void EncodeOneByte(TOutputStream * os, THTreeItem * pItem);
+ unsigned int DecodeOneByte(TInputStream * is);
+
+ unsigned int Compress(TOutputStream * os, void * pvInBuffer, int cbInBuffer, int nCmpType);
+ unsigned int Decompress(void * pvOutBuffer, unsigned int cbOutLength, TInputStream * is);
+
+ THTreeItem ItemBuffer[HUFF_ITEM_COUNT]; // Buffer for tree items. No memory allocation is needed
+ unsigned int ItemsUsed; // Number of tree items used from ItemBuffer
+
+ // Head of the linear item list
+ THTreeItem * pFirst; // Pointer to the highest weight item
+ THTreeItem * pLast; // Pointer to the lowest weight item
+
+ THTreeItem * ItemsByByte[0x102]; // Array of item pointers, one for each possible byte value
+ TQuickLink QuickLinks[LINK_ITEM_COUNT]; // Array of quick-link items
+
+ unsigned int MinValidValue; // A minimum value of TQDecompress::ValidValue to be considered valid
+ unsigned int bIsCmp0; // 1 if compression type 0
+};
+
+#endif // __HUFFMAN_H__
diff --git a/dep/StormLib/src/jenkins/lookup.h b/dep/StormLib/src/jenkins/lookup.h
new file mode 100644
index 000000000..54ccc979c
--- /dev/null
+++ b/dep/StormLib/src/jenkins/lookup.h
@@ -0,0 +1,24 @@
+#ifndef __LOOKUP3_H__
+#define __LOOKUP3_H__
+
+#ifdef WIN32
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+#else
+#include /* defines uint32_t etc */
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+uint32_t hashlittle(const void *key, size_t length, uint32_t initval);
+void hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __LOOKUP3_H__
diff --git a/dep/StormLib/src/jenkins/lookup3.c b/dep/StormLib/src/jenkins/lookup3.c
new file mode 100644
index 000000000..6af56b481
--- /dev/null
+++ b/dep/StormLib/src/jenkins/lookup3.c
@@ -0,0 +1,1003 @@
+/*
+-------------------------------------------------------------------------------
+lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+
+These are functions for producing 32-bit hashes for hash table lookup.
+hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions. Routines to test the hash are included
+if SELF_TEST is defined. You can use this free for any purpose. It's in
+the public domain. It has no warranty.
+
+You probably want to use hashlittle(). hashlittle() and hashbig()
+hash byte arrays. hashlittle() is is faster than hashbig() on
+little-endian machines. Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+ a = i1; b = i2; c = i3;
+ mix(a,b,c);
+ a += i4; b += i5; c += i6;
+ mix(a,b,c);
+ a += i7;
+ final(a,b,c);
+then use c as the hash value. If you have a variable length array of
+4-byte integers to hash, use hashword(). If you have a byte array (like
+a character string), use hashlittle(). If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers. This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+-------------------------------------------------------------------------------
+*/
+//#define SELF_TEST 1
+
+#include /* defines printf for tests */
+#include /* defines time_t for timings in the test */
+
+#ifdef linux
+#include /* attempt to define endianness */
+#include /* attempt to define endianness */
+#endif
+
+#include "lookup.h"
+
+/*
+ * My best guess at if you are big-endian or little-endian. This may
+ * need adjustment.
+ */
+#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
+ __BYTE_ORDER == __LITTLE_ENDIAN) || \
+ (defined(i386) || defined(__i386__) || defined(__i486__) || \
+ defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL))
+# define HASH_LITTLE_ENDIAN 1
+# define HASH_BIG_ENDIAN 0
+#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
+ __BYTE_ORDER == __BIG_ENDIAN) || \
+ (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel))
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 0
+#endif
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+ 4 6 8 16 19 4
+ 9 15 3 18 27 15
+ 14 9 3 7 17 3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta. I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche. There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a. The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism. Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism. I did what I could. Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different. This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+ 4 8 15 26 3 22 24
+ 10 8 15 26 3 22 24
+ 11 8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+}
+
+/*
+--------------------------------------------------------------------
+ This works on all machines. To be useful, it requires
+ -- that the key be an array of uint32_t's, and
+ -- that the length be the number of uint32_t's in the key
+
+ The function hashword() is identical to hashlittle() on little-endian
+ machines, and identical to hashbig() on big-endian machines,
+ except that the length has to be measured in uint32_ts rather than in
+ bytes. hashlittle() is more complicated than hashword() only because
+ hashlittle() has to dance around fitting the key bytes into registers.
+--------------------------------------------------------------------
+*/
+uint32_t hashword(
+const uint32_t *k, /* the key, an array of uint32_t values */
+size_t length, /* the length of the key, in uint32_ts */
+uint32_t initval) /* the previous hash, or an arbitrary value */
+{
+ uint32_t a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval;
+
+ /*------------------------------------------------- handle most of the key */
+ while (length > 3)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 3;
+ k += 3;
+ }
+
+ /*------------------------------------------- handle the last 3 uint32_t's */
+ switch(length) /* all the case statements fall through */
+ {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ final(a,b,c);
+ case 0: /* case 0: nothing left to add */
+ break;
+ }
+ /*------------------------------------------------------ report the result */
+ return c;
+}
+
+
+/*
+--------------------------------------------------------------------
+hashword2() -- same as hashword(), but take two seeds and return two
+32-bit values. pc and pb must both be nonnull, and *pc and *pb must
+both be initialized with seeds. If you pass in (*pb)==0, the output
+(*pc) will be the same as the return value from hashword().
+--------------------------------------------------------------------
+*/
+void hashword2 (
+const uint32_t *k, /* the key, an array of uint32_t values */
+size_t length, /* the length of the key, in uint32_ts */
+uint32_t *pc, /* IN: seed OUT: primary hash value */
+uint32_t *pb) /* IN: more seed OUT: secondary hash value */
+{
+ uint32_t a,b,c;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc;
+ c += *pb;
+
+ /*------------------------------------------------- handle most of the key */
+ while (length > 3)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 3;
+ k += 3;
+ }
+
+ /*------------------------------------------- handle the last 3 uint32_t's */
+ switch(length) /* all the case statements fall through */
+ {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ final(a,b,c);
+ case 0: /* case 0: nothing left to add */
+ break;
+ }
+ /*------------------------------------------------------ report the result */
+ *pc=c; *pb=b;
+}
+
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+ k : the key (the unaligned variable-length array of bytes)
+ length : the length of the key, counting by bytes
+ initval : can be any 4-byte value
+Returns a 32-bit value. Every bit of the key affects every bit of
+the return value. Two keys differing by one or two bits will have
+totally different hash values.
+
+The best hash table sizes are powers of 2. There is no need to do
+mod a prime (mod is sooo slow!). If you need less than 32 bits,
+use a bitmask. For example, if you need only 10 bits, do
+ h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (uint8_t **)k, do it like this:
+ for (i=0, h=0; i 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : return c;
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : return c; /* zero length requires no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((uint32_t)k[11])<<24;
+ case 11: c+=((uint32_t)k[10])<<16;
+ case 10: c+=((uint32_t)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((uint32_t)k[7])<<24;
+ case 7 : b+=((uint32_t)k[6])<<16;
+ case 6 : b+=((uint32_t)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((uint32_t)k[3])<<24;
+ case 3 : a+=((uint32_t)k[2])<<16;
+ case 2 : a+=((uint32_t)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+
+/*
+ * hashlittle2: return 2 32-bit hash values
+ *
+ * This is identical to hashlittle(), except it returns two 32-bit hash
+ * values instead of just one. This is good enough for hash table
+ * lookup with 2^^64 buckets, or if you want a second hash if you're not
+ * happy with the first, or if you want a probably-unique 64-bit ID for
+ * the key. *pc is better mixed than *pb, so use *pc first. If you want
+ * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
+ */
+void hashlittle2(
+ const void *key, /* the key to hash */
+ size_t length, /* length of the key */
+ uint32_t *pc, /* IN: primary initval, OUT: primary hash */
+ uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */
+{
+ uint32_t a,b,c; /* internal state */
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc;
+ c += *pb;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+ const uint8_t *k8;
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((uint32_t)k[11])<<24;
+ case 11: c+=((uint32_t)k[10])<<16;
+ case 10: c+=((uint32_t)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((uint32_t)k[7])<<24;
+ case 7 : b+=((uint32_t)k[6])<<16;
+ case 6 : b+=((uint32_t)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((uint32_t)k[3])<<24;
+ case 3 : a+=((uint32_t)k[2])<<16;
+ case 2 : a+=((uint32_t)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
+ }
+ }
+
+ final(a,b,c);
+ *pc=c; *pb=b;
+}
+
+
+
+/*
+ * hashbig():
+ * This is the same as hashword() on big-endian machines. It is different
+ * from hashlittle() on all machines. hashbig() takes advantage of
+ * big-endian byte ordering.
+ */
+uint32_t hashbig( const void *key, size_t length, uint32_t initval)
+{
+ uint32_t a,b,c;
+ union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+ u.ptr = key;
+ if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+ const uint8_t *k8;
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]<<8" actually reads beyond the end of the string, but
+ * then shifts out the part it's not allowed to read. Because the
+ * string is aligned, the illegal read is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff00; break;
+ case 2 : a+=k[0]&0xffff0000; break;
+ case 1 : a+=k[0]&0xff000000; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<8; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<16; /* fall through */
+ case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */
+ case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */
+ case 1 : a+=((uint32_t)k8[0])<<24; break;
+ case 0 : return c;
+ }
+
+#endif /* !VALGRIND */
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += ((uint32_t)k[0])<<24;
+ a += ((uint32_t)k[1])<<16;
+ a += ((uint32_t)k[2])<<8;
+ a += ((uint32_t)k[3]);
+ b += ((uint32_t)k[4])<<24;
+ b += ((uint32_t)k[5])<<16;
+ b += ((uint32_t)k[6])<<8;
+ b += ((uint32_t)k[7]);
+ c += ((uint32_t)k[8])<<24;
+ c += ((uint32_t)k[9])<<16;
+ c += ((uint32_t)k[10])<<8;
+ c += ((uint32_t)k[11]);
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=k[11];
+ case 11: c+=((uint32_t)k[10])<<8;
+ case 10: c+=((uint32_t)k[9])<<16;
+ case 9 : c+=((uint32_t)k[8])<<24;
+ case 8 : b+=k[7];
+ case 7 : b+=((uint32_t)k[6])<<8;
+ case 6 : b+=((uint32_t)k[5])<<16;
+ case 5 : b+=((uint32_t)k[4])<<24;
+ case 4 : a+=k[3];
+ case 3 : a+=((uint32_t)k[2])<<8;
+ case 2 : a+=((uint32_t)k[1])<<16;
+ case 1 : a+=((uint32_t)k[0])<<24;
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+
+#ifdef SELF_TEST
+
+/* used for timings */
+void driver1()
+{
+ uint8_t buf[256];
+ uint32_t i;
+ uint32_t h=0;
+ time_t a,z;
+
+ time(&a);
+ for (i=0; i<256; ++i) buf[i] = 'x';
+ for (i=0; i<1; ++i)
+ {
+ h = hashlittle(&buf[0],1,h);
+ }
+ time(&z);
+ if (z-a > 0) printf("time %d %.8x\n", z-a, h);
+}
+
+/* check that every input bit changes every output bit half the time */
+#define HASHSTATE 1
+#define HASHLEN 1
+#define MAXPAIR 60
+#define MAXLEN 70
+void driver2()
+{
+ uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1];
+ uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z;
+ uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE];
+ uint32_t x[HASHSTATE],y[HASHSTATE];
+ uint32_t hlen;
+
+ printf("No more than %d trials should ever be needed \n",MAXPAIR/2);
+ for (hlen=0; hlen < MAXLEN; ++hlen)
+ {
+ z=0;
+ for (i=0; i>(8-j));
+ c[0] = hashlittle(a, hlen, m);
+ b[i] ^= ((k+1)<>(8-j));
+ d[0] = hashlittle(b, hlen, m);
+ /* check every bit is 1, 0, set, and not set at least once */
+ for (l=0; lz) z=k;
+ if (k==MAXPAIR)
+ {
+ printf("Some bit didn't change: ");
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x ",
+ e[0],f[0],g[0],h[0],x[0],y[0]);
+ printf("i %d j %d m %d len %d\n", i, j, m, hlen);
+ }
+ if (z==MAXPAIR) goto done;
+ }
+ }
+ }
+ done:
+ if (z < MAXPAIR)
+ {
+ printf("Mix success %2d bytes %2d initvals ",i,m);
+ printf("required %d trials\n", z/2);
+ }
+ }
+ printf("\n");
+}
+
+/* Check for reading beyond the end of the buffer and alignment problems */
+void driver3()
+{
+ uint8_t buf[MAXLEN+20], *b;
+ uint32_t len;
+ uint8_t q[] = "This is the time for all good men to come to the aid of their country...";
+ uint32_t h;
+ uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country...";
+ uint32_t i;
+ uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country...";
+ uint32_t j;
+ uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country...";
+ uint32_t ref,x,y;
+ uint8_t *p;
+
+ printf("Endianness. These lines should all be the same (for values filled in):\n");
+ printf("%.8x %.8x %.8x\n",
+ hashword((const uint32_t *)q, (sizeof(q)-1)/4, 13),
+ hashword((const uint32_t *)q, (sizeof(q)-5)/4, 13),
+ hashword((const uint32_t *)q, (sizeof(q)-9)/4, 13));
+ p = q;
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ p = &qq[1];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ p = &qqq[2];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ p = &qqqq[3];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ hashlittle(p, sizeof(q)-1, 13), hashlittle(p, sizeof(q)-2, 13),
+ hashlittle(p, sizeof(q)-3, 13), hashlittle(p, sizeof(q)-4, 13),
+ hashlittle(p, sizeof(q)-5, 13), hashlittle(p, sizeof(q)-6, 13),
+ hashlittle(p, sizeof(q)-7, 13), hashlittle(p, sizeof(q)-8, 13),
+ hashlittle(p, sizeof(q)-9, 13), hashlittle(p, sizeof(q)-10, 13),
+ hashlittle(p, sizeof(q)-11, 13), hashlittle(p, sizeof(q)-12, 13));
+ printf("\n");
+
+ /* check that hashlittle2 and hashlittle produce the same results */
+ i=47; j=0;
+ hashlittle2(q, sizeof(q), &i, &j);
+ if (hashlittle(q, sizeof(q), 47) != i)
+ printf("hashlittle2 and hashlittle mismatch\n");
+
+ /* check that hashword2 and hashword produce the same results */
+ len = 0xdeadbeef;
+ i=47, j=0;
+ hashword2(&len, 1, &i, &j);
+ if (hashword(&len, 1, 47) != i)
+ printf("hashword2 and hashword mismatch %x %x\n",
+ i, hashword(&len, 1, 47));
+
+ /* check hashlittle doesn't read before or after the ends of the string */
+ for (h=0, b=buf+1; h<8; ++h, ++b)
+ {
+ for (i=0; i
+
+#include "LzFind.h"
+#include "LzHash.h"
+
+#define kEmptyHashValue 0
+#define kMaxValForNormalize ((UInt32)0xFFFFFFFF)
+#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */
+#define kNormalizeMask (~(kNormalizeStepMin - 1))
+#define kMaxHistorySize ((UInt32)3 << 30)
+
+#define kStartMaxLen 3
+
+static void LzInWindow_Free(CMatchFinder *p, ISzAlloc *alloc)
+{
+ if (!p->directInput)
+ {
+ alloc->Free(alloc, p->bufferBase);
+ p->bufferBase = 0;
+ }
+}
+
+/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */
+
+static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAlloc *alloc)
+{
+ UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv;
+ if (p->directInput)
+ {
+ p->blockSize = blockSize;
+ return 1;
+ }
+ if (p->bufferBase == 0 || p->blockSize != blockSize)
+ {
+ LzInWindow_Free(p, alloc);
+ p->blockSize = blockSize;
+ p->bufferBase = (Byte *)alloc->Alloc(alloc, (size_t)blockSize);
+ }
+ return (p->bufferBase != 0);
+}
+
+Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; }
+Byte MatchFinder_GetIndexByte(CMatchFinder *p, Int32 index) { return p->buffer[index]; }
+
+UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; }
+
+void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue)
+{
+ p->posLimit -= subValue;
+ p->pos -= subValue;
+ p->streamPos -= subValue;
+}
+
+static void MatchFinder_ReadBlock(CMatchFinder *p)
+{
+ if (p->streamEndWasReached || p->result != SZ_OK)
+ return;
+ if (p->directInput)
+ {
+ UInt32 curSize = 0xFFFFFFFF - p->streamPos;
+ if (curSize > p->directInputRem)
+ curSize = (UInt32)p->directInputRem;
+ p->directInputRem -= curSize;
+ p->streamPos += curSize;
+ if (p->directInputRem == 0)
+ p->streamEndWasReached = 1;
+ return;
+ }
+ for (;;)
+ {
+ Byte *dest = p->buffer + (p->streamPos - p->pos);
+ size_t size = (p->bufferBase + p->blockSize - dest);
+ if (size == 0)
+ return;
+ p->result = p->stream->Read(p->stream, dest, &size);
+ if (p->result != SZ_OK)
+ return;
+ if (size == 0)
+ {
+ p->streamEndWasReached = 1;
+ return;
+ }
+ p->streamPos += (UInt32)size;
+ if (p->streamPos - p->pos > p->keepSizeAfter)
+ return;
+ }
+}
+
+void MatchFinder_MoveBlock(CMatchFinder *p)
+{
+ memmove(p->bufferBase,
+ p->buffer - p->keepSizeBefore,
+ (size_t)(p->streamPos - p->pos + p->keepSizeBefore));
+ p->buffer = p->bufferBase + p->keepSizeBefore;
+}
+
+int MatchFinder_NeedMove(CMatchFinder *p)
+{
+ if (p->directInput)
+ return 0;
+ /* if (p->streamEndWasReached) return 0; */
+ return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter);
+}
+
+void MatchFinder_ReadIfRequired(CMatchFinder *p)
+{
+ if (p->streamEndWasReached)
+ return;
+ if (p->keepSizeAfter >= p->streamPos - p->pos)
+ MatchFinder_ReadBlock(p);
+}
+
+static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p)
+{
+ if (MatchFinder_NeedMove(p))
+ MatchFinder_MoveBlock(p);
+ MatchFinder_ReadBlock(p);
+}
+
+static void MatchFinder_SetDefaultSettings(CMatchFinder *p)
+{
+ p->cutValue = 32;
+ p->btMode = 1;
+ p->numHashBytes = 4;
+ p->bigHash = 0;
+}
+
+#define kCrcPoly 0xEDB88320
+
+void MatchFinder_Construct(CMatchFinder *p)
+{
+ UInt32 i;
+ p->bufferBase = 0;
+ p->directInput = 0;
+ p->hash = 0;
+ MatchFinder_SetDefaultSettings(p);
+
+ for (i = 0; i < 256; i++)
+ {
+ UInt32 r = i;
+ int j;
+ for (j = 0; j < 8; j++)
+ r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
+ p->crc[i] = r;
+ }
+}
+
+static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->hash);
+ p->hash = 0;
+}
+
+void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc)
+{
+ MatchFinder_FreeThisClassMemory(p, alloc);
+ LzInWindow_Free(p, alloc);
+}
+
+static CLzRef* AllocRefs(UInt32 num, ISzAlloc *alloc)
+{
+ size_t sizeInBytes = (size_t)num * sizeof(CLzRef);
+ if (sizeInBytes / sizeof(CLzRef) != num)
+ return 0;
+ return (CLzRef *)alloc->Alloc(alloc, sizeInBytes);
+}
+
+int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
+ UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
+ ISzAlloc *alloc)
+{
+ UInt32 sizeReserv;
+ if (historySize > kMaxHistorySize)
+ {
+ MatchFinder_Free(p, alloc);
+ return 0;
+ }
+ sizeReserv = historySize >> 1;
+ if (historySize > ((UInt32)2 << 30))
+ sizeReserv = historySize >> 2;
+ sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19);
+
+ p->keepSizeBefore = historySize + keepAddBufferBefore + 1;
+ p->keepSizeAfter = matchMaxLen + keepAddBufferAfter;
+ /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */
+ if (LzInWindow_Create(p, sizeReserv, alloc))
+ {
+ UInt32 newCyclicBufferSize = historySize + 1;
+ UInt32 hs;
+ p->matchMaxLen = matchMaxLen;
+ {
+ p->fixedHashSize = 0;
+ if (p->numHashBytes == 2)
+ hs = (1 << 16) - 1;
+ else
+ {
+ hs = historySize - 1;
+ hs |= (hs >> 1);
+ hs |= (hs >> 2);
+ hs |= (hs >> 4);
+ hs |= (hs >> 8);
+ hs >>= 1;
+ hs |= 0xFFFF; /* don't change it! It's required for Deflate */
+ if (hs > (1 << 24))
+ {
+ if (p->numHashBytes == 3)
+ hs = (1 << 24) - 1;
+ else
+ hs >>= 1;
+ }
+ }
+ p->hashMask = hs;
+ hs++;
+ if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size;
+ if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size;
+ if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size;
+ hs += p->fixedHashSize;
+ }
+
+ {
+ UInt32 prevSize = p->hashSizeSum + p->numSons;
+ UInt32 newSize;
+ p->historySize = historySize;
+ p->hashSizeSum = hs;
+ p->cyclicBufferSize = newCyclicBufferSize;
+ p->numSons = (p->btMode ? newCyclicBufferSize * 2 : newCyclicBufferSize);
+ newSize = p->hashSizeSum + p->numSons;
+ if (p->hash != 0 && prevSize == newSize)
+ return 1;
+ MatchFinder_FreeThisClassMemory(p, alloc);
+ p->hash = AllocRefs(newSize, alloc);
+ if (p->hash != 0)
+ {
+ p->son = p->hash + p->hashSizeSum;
+ return 1;
+ }
+ }
+ }
+ MatchFinder_Free(p, alloc);
+ return 0;
+}
+
+static void MatchFinder_SetLimits(CMatchFinder *p)
+{
+ UInt32 limit = kMaxValForNormalize - p->pos;
+ UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos;
+ if (limit2 < limit)
+ limit = limit2;
+ limit2 = p->streamPos - p->pos;
+ if (limit2 <= p->keepSizeAfter)
+ {
+ if (limit2 > 0)
+ limit2 = 1;
+ }
+ else
+ limit2 -= p->keepSizeAfter;
+ if (limit2 < limit)
+ limit = limit2;
+ {
+ UInt32 lenLimit = p->streamPos - p->pos;
+ if (lenLimit > p->matchMaxLen)
+ lenLimit = p->matchMaxLen;
+ p->lenLimit = lenLimit;
+ }
+ p->posLimit = p->pos + limit;
+}
+
+void MatchFinder_Init(CMatchFinder *p)
+{
+ UInt32 i;
+ for (i = 0; i < p->hashSizeSum; i++)
+ p->hash[i] = kEmptyHashValue;
+ p->cyclicBufferPos = 0;
+ p->buffer = p->bufferBase;
+ p->pos = p->streamPos = p->cyclicBufferSize;
+ p->result = SZ_OK;
+ p->streamEndWasReached = 0;
+ MatchFinder_ReadBlock(p);
+ MatchFinder_SetLimits(p);
+}
+
+static UInt32 MatchFinder_GetSubValue(CMatchFinder *p)
+{
+ return (p->pos - p->historySize - 1) & kNormalizeMask;
+}
+
+void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems)
+{
+ UInt32 i;
+ for (i = 0; i < numItems; i++)
+ {
+ UInt32 value = items[i];
+ if (value <= subValue)
+ value = kEmptyHashValue;
+ else
+ value -= subValue;
+ items[i] = value;
+ }
+}
+
+static void MatchFinder_Normalize(CMatchFinder *p)
+{
+ UInt32 subValue = MatchFinder_GetSubValue(p);
+ MatchFinder_Normalize3(subValue, p->hash, p->hashSizeSum + p->numSons);
+ MatchFinder_ReduceOffsets(p, subValue);
+}
+
+static void MatchFinder_CheckLimits(CMatchFinder *p)
+{
+ if (p->pos == kMaxValForNormalize)
+ MatchFinder_Normalize(p);
+ if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos)
+ MatchFinder_CheckAndMoveAndRead(p);
+ if (p->cyclicBufferPos == p->cyclicBufferSize)
+ p->cyclicBufferPos = 0;
+ MatchFinder_SetLimits(p);
+}
+
+static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
+ UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
+ UInt32 *distances, UInt32 maxLen)
+{
+ son[_cyclicBufferPos] = curMatch;
+ for (;;)
+ {
+ UInt32 delta = pos - curMatch;
+ if (cutValue-- == 0 || delta >= _cyclicBufferSize)
+ return distances;
+ {
+ const Byte *pb = cur - delta;
+ curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)];
+ if (pb[maxLen] == cur[maxLen] && *pb == *cur)
+ {
+ UInt32 len = 0;
+ while (++len != lenLimit)
+ if (pb[len] != cur[len])
+ break;
+ if (maxLen < len)
+ {
+ *distances++ = maxLen = len;
+ *distances++ = delta - 1;
+ if (len == lenLimit)
+ return distances;
+ }
+ }
+ }
+ }
+}
+
+UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
+ UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
+ UInt32 *distances, UInt32 maxLen)
+{
+ CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
+ CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
+ UInt32 len0 = 0, len1 = 0;
+ for (;;)
+ {
+ UInt32 delta = pos - curMatch;
+ if (cutValue-- == 0 || delta >= _cyclicBufferSize)
+ {
+ *ptr0 = *ptr1 = kEmptyHashValue;
+ return distances;
+ }
+ {
+ CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
+ const Byte *pb = cur - delta;
+ UInt32 len = (len0 < len1 ? len0 : len1);
+ if (pb[len] == cur[len])
+ {
+ if (++len != lenLimit && pb[len] == cur[len])
+ while (++len != lenLimit)
+ if (pb[len] != cur[len])
+ break;
+ if (maxLen < len)
+ {
+ *distances++ = maxLen = len;
+ *distances++ = delta - 1;
+ if (len == lenLimit)
+ {
+ *ptr1 = pair[0];
+ *ptr0 = pair[1];
+ return distances;
+ }
+ }
+ }
+ if (pb[len] < cur[len])
+ {
+ *ptr1 = curMatch;
+ ptr1 = pair + 1;
+ curMatch = *ptr1;
+ len1 = len;
+ }
+ else
+ {
+ *ptr0 = curMatch;
+ ptr0 = pair;
+ curMatch = *ptr0;
+ len0 = len;
+ }
+ }
+ }
+}
+
+static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
+ UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue)
+{
+ CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
+ CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
+ UInt32 len0 = 0, len1 = 0;
+ for (;;)
+ {
+ UInt32 delta = pos - curMatch;
+ if (cutValue-- == 0 || delta >= _cyclicBufferSize)
+ {
+ *ptr0 = *ptr1 = kEmptyHashValue;
+ return;
+ }
+ {
+ CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
+ const Byte *pb = cur - delta;
+ UInt32 len = (len0 < len1 ? len0 : len1);
+ if (pb[len] == cur[len])
+ {
+ while (++len != lenLimit)
+ if (pb[len] != cur[len])
+ break;
+ {
+ if (len == lenLimit)
+ {
+ *ptr1 = pair[0];
+ *ptr0 = pair[1];
+ return;
+ }
+ }
+ }
+ if (pb[len] < cur[len])
+ {
+ *ptr1 = curMatch;
+ ptr1 = pair + 1;
+ curMatch = *ptr1;
+ len1 = len;
+ }
+ else
+ {
+ *ptr0 = curMatch;
+ ptr0 = pair;
+ curMatch = *ptr0;
+ len0 = len;
+ }
+ }
+ }
+}
+
+#define MOVE_POS \
+ ++p->cyclicBufferPos; \
+ p->buffer++; \
+ if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p);
+
+#define MOVE_POS_RET MOVE_POS return offset;
+
+static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; }
+
+#define GET_MATCHES_HEADER2(minLen, ret_op) \
+ UInt32 lenLimit; UInt32 hashValue; const Byte *cur; UInt32 curMatch; \
+ lenLimit = p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \
+ cur = p->buffer;
+
+#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0)
+#define SKIP_HEADER(minLen) GET_MATCHES_HEADER2(minLen, continue)
+
+#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue
+
+#define GET_MATCHES_FOOTER(offset, maxLen) \
+ offset = (UInt32)(GetMatchesSpec1(lenLimit, curMatch, MF_PARAMS(p), \
+ distances + offset, maxLen) - distances); MOVE_POS_RET;
+
+#define SKIP_FOOTER \
+ SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS;
+
+static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+{
+ UInt32 offset;
+ GET_MATCHES_HEADER(2)
+ HASH2_CALC;
+ curMatch = p->hash[hashValue];
+ p->hash[hashValue] = p->pos;
+ offset = 0;
+ GET_MATCHES_FOOTER(offset, 1)
+}
+
+UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+{
+ UInt32 offset;
+ GET_MATCHES_HEADER(3)
+ HASH_ZIP_CALC;
+ curMatch = p->hash[hashValue];
+ p->hash[hashValue] = p->pos;
+ offset = 0;
+ GET_MATCHES_FOOTER(offset, 2)
+}
+
+static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+{
+ UInt32 hash2Value, delta2, maxLen, offset;
+ GET_MATCHES_HEADER(3)
+
+ HASH3_CALC;
+
+ delta2 = p->pos - p->hash[hash2Value];
+ curMatch = p->hash[kFix3HashSize + hashValue];
+
+ p->hash[hash2Value] =
+ p->hash[kFix3HashSize + hashValue] = p->pos;
+
+
+ maxLen = 2;
+ offset = 0;
+ if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
+ {
+ for (; maxLen != lenLimit; maxLen++)
+ if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
+ break;
+ distances[0] = maxLen;
+ distances[1] = delta2 - 1;
+ offset = 2;
+ if (maxLen == lenLimit)
+ {
+ SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
+ MOVE_POS_RET;
+ }
+ }
+ GET_MATCHES_FOOTER(offset, maxLen)
+}
+
+static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+{
+ UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset;
+ GET_MATCHES_HEADER(4)
+
+ HASH4_CALC;
+
+ delta2 = p->pos - p->hash[ hash2Value];
+ delta3 = p->pos - p->hash[kFix3HashSize + hash3Value];
+ curMatch = p->hash[kFix4HashSize + hashValue];
+
+ p->hash[ hash2Value] =
+ p->hash[kFix3HashSize + hash3Value] =
+ p->hash[kFix4HashSize + hashValue] = p->pos;
+
+ maxLen = 1;
+ offset = 0;
+ if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
+ {
+ distances[0] = maxLen = 2;
+ distances[1] = delta2 - 1;
+ offset = 2;
+ }
+ if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur)
+ {
+ maxLen = 3;
+ distances[offset + 1] = delta3 - 1;
+ offset += 2;
+ delta2 = delta3;
+ }
+ if (offset != 0)
+ {
+ for (; maxLen != lenLimit; maxLen++)
+ if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
+ break;
+ distances[offset - 2] = maxLen;
+ if (maxLen == lenLimit)
+ {
+ SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
+ MOVE_POS_RET;
+ }
+ }
+ if (maxLen < 3)
+ maxLen = 3;
+ GET_MATCHES_FOOTER(offset, maxLen)
+}
+
+static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+{
+ UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset;
+ GET_MATCHES_HEADER(4)
+
+ HASH4_CALC;
+
+ delta2 = p->pos - p->hash[ hash2Value];
+ delta3 = p->pos - p->hash[kFix3HashSize + hash3Value];
+ curMatch = p->hash[kFix4HashSize + hashValue];
+
+ p->hash[ hash2Value] =
+ p->hash[kFix3HashSize + hash3Value] =
+ p->hash[kFix4HashSize + hashValue] = p->pos;
+
+ maxLen = 1;
+ offset = 0;
+ if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur)
+ {
+ distances[0] = maxLen = 2;
+ distances[1] = delta2 - 1;
+ offset = 2;
+ }
+ if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur)
+ {
+ maxLen = 3;
+ distances[offset + 1] = delta3 - 1;
+ offset += 2;
+ delta2 = delta3;
+ }
+ if (offset != 0)
+ {
+ for (; maxLen != lenLimit; maxLen++)
+ if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen])
+ break;
+ distances[offset - 2] = maxLen;
+ if (maxLen == lenLimit)
+ {
+ p->son[p->cyclicBufferPos] = curMatch;
+ MOVE_POS_RET;
+ }
+ }
+ if (maxLen < 3)
+ maxLen = 3;
+ offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
+ distances + offset, maxLen) - (distances));
+ MOVE_POS_RET
+}
+
+UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+{
+ UInt32 offset;
+ GET_MATCHES_HEADER(3)
+ HASH_ZIP_CALC;
+ curMatch = p->hash[hashValue];
+ p->hash[hashValue] = p->pos;
+ offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
+ distances, 2) - (distances));
+ MOVE_POS_RET
+}
+
+static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
+{
+ do
+ {
+ SKIP_HEADER(2)
+ HASH2_CALC;
+ curMatch = p->hash[hashValue];
+ p->hash[hashValue] = p->pos;
+ SKIP_FOOTER
+ }
+ while (--num != 0);
+}
+
+void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
+{
+ do
+ {
+ SKIP_HEADER(3)
+ HASH_ZIP_CALC;
+ curMatch = p->hash[hashValue];
+ p->hash[hashValue] = p->pos;
+ SKIP_FOOTER
+ }
+ while (--num != 0);
+}
+
+static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
+{
+ do
+ {
+ UInt32 hash2Value;
+ SKIP_HEADER(3)
+ HASH3_CALC;
+ curMatch = p->hash[kFix3HashSize + hashValue];
+ p->hash[hash2Value] =
+ p->hash[kFix3HashSize + hashValue] = p->pos;
+ SKIP_FOOTER
+ }
+ while (--num != 0);
+}
+
+static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
+{
+ do
+ {
+ UInt32 hash2Value, hash3Value;
+ SKIP_HEADER(4)
+ HASH4_CALC;
+ curMatch = p->hash[kFix4HashSize + hashValue];
+ p->hash[ hash2Value] =
+ p->hash[kFix3HashSize + hash3Value] = p->pos;
+ p->hash[kFix4HashSize + hashValue] = p->pos;
+ SKIP_FOOTER
+ }
+ while (--num != 0);
+}
+
+static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
+{
+ do
+ {
+ UInt32 hash2Value, hash3Value;
+ SKIP_HEADER(4)
+ HASH4_CALC;
+ curMatch = p->hash[kFix4HashSize + hashValue];
+ p->hash[ hash2Value] =
+ p->hash[kFix3HashSize + hash3Value] =
+ p->hash[kFix4HashSize + hashValue] = p->pos;
+ p->son[p->cyclicBufferPos] = curMatch;
+ MOVE_POS
+ }
+ while (--num != 0);
+}
+
+void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
+{
+ do
+ {
+ SKIP_HEADER(3)
+ HASH_ZIP_CALC;
+ curMatch = p->hash[hashValue];
+ p->hash[hashValue] = p->pos;
+ p->son[p->cyclicBufferPos] = curMatch;
+ MOVE_POS
+ }
+ while (--num != 0);
+}
+
+void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable)
+{
+ vTable->Init = (Mf_Init_Func)MatchFinder_Init;
+ vTable->GetIndexByte = (Mf_GetIndexByte_Func)MatchFinder_GetIndexByte;
+ vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes;
+ vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos;
+ if (!p->btMode)
+ {
+ vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches;
+ vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip;
+ }
+ else if (p->numHashBytes == 2)
+ {
+ vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches;
+ vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip;
+ }
+ else if (p->numHashBytes == 3)
+ {
+ vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches;
+ vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip;
+ }
+ else
+ {
+ vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches;
+ vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip;
+ }
+}
diff --git a/dep/StormLib/src/lzma/C/LzFind.h b/dep/StormLib/src/lzma/C/LzFind.h
new file mode 100644
index 000000000..010c4b92b
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzFind.h
@@ -0,0 +1,115 @@
+/* LzFind.h -- Match finder for LZ algorithms
+2009-04-22 : Igor Pavlov : Public domain */
+
+#ifndef __LZ_FIND_H
+#define __LZ_FIND_H
+
+#include "Types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef UInt32 CLzRef;
+
+typedef struct _CMatchFinder
+{
+ Byte *buffer;
+ UInt32 pos;
+ UInt32 posLimit;
+ UInt32 streamPos;
+ UInt32 lenLimit;
+
+ UInt32 cyclicBufferPos;
+ UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */
+
+ UInt32 matchMaxLen;
+ CLzRef *hash;
+ CLzRef *son;
+ UInt32 hashMask;
+ UInt32 cutValue;
+
+ Byte *bufferBase;
+ ISeqInStream *stream;
+ int streamEndWasReached;
+
+ UInt32 blockSize;
+ UInt32 keepSizeBefore;
+ UInt32 keepSizeAfter;
+
+ UInt32 numHashBytes;
+ int directInput;
+ size_t directInputRem;
+ int btMode;
+ int bigHash;
+ UInt32 historySize;
+ UInt32 fixedHashSize;
+ UInt32 hashSizeSum;
+ UInt32 numSons;
+ SRes result;
+ UInt32 crc[256];
+} CMatchFinder;
+
+#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer)
+#define Inline_MatchFinder_GetIndexByte(p, index) ((p)->buffer[(Int32)(index)])
+
+#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos)
+
+int MatchFinder_NeedMove(CMatchFinder *p);
+Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p);
+void MatchFinder_MoveBlock(CMatchFinder *p);
+void MatchFinder_ReadIfRequired(CMatchFinder *p);
+
+void MatchFinder_Construct(CMatchFinder *p);
+
+/* Conditions:
+ historySize <= 3 GB
+ keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB
+*/
+int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
+ UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
+ ISzAlloc *alloc);
+void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc);
+void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems);
+void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue);
+
+UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son,
+ UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue,
+ UInt32 *distances, UInt32 maxLen);
+
+/*
+Conditions:
+ Mf_GetNumAvailableBytes_Func must be called before each Mf_GetMatchLen_Func.
+ Mf_GetPointerToCurrentPos_Func's result must be used only before any other function
+*/
+
+typedef void (*Mf_Init_Func)(void *object);
+typedef Byte (*Mf_GetIndexByte_Func)(void *object, Int32 index);
+typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object);
+typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object);
+typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances);
+typedef void (*Mf_Skip_Func)(void *object, UInt32);
+
+typedef struct _IMatchFinder
+{
+ Mf_Init_Func Init;
+ Mf_GetIndexByte_Func GetIndexByte;
+ Mf_GetNumAvailableBytes_Func GetNumAvailableBytes;
+ Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos;
+ Mf_GetMatches_Func GetMatches;
+ Mf_Skip_Func Skip;
+} IMatchFinder;
+
+void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable);
+
+void MatchFinder_Init(CMatchFinder *p);
+UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
+UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
+void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
+void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dep/StormLib/src/lzma/C/LzFindMt.c b/dep/StormLib/src/lzma/C/LzFindMt.c
new file mode 100644
index 000000000..aa41ed98a
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzFindMt.c
@@ -0,0 +1,793 @@
+/* LzFindMt.c -- multithreaded Match finder for LZ algorithms
+2009-09-20 : Igor Pavlov : Public domain */
+
+#include "LzHash.h"
+
+#include "LzFindMt.h"
+
+void MtSync_Construct(CMtSync *p)
+{
+ p->wasCreated = False;
+ p->csWasInitialized = False;
+ p->csWasEntered = False;
+ Thread_Construct(&p->thread);
+ Event_Construct(&p->canStart);
+ Event_Construct(&p->wasStarted);
+ Event_Construct(&p->wasStopped);
+ Semaphore_Construct(&p->freeSemaphore);
+ Semaphore_Construct(&p->filledSemaphore);
+}
+
+void MtSync_GetNextBlock(CMtSync *p)
+{
+ if (p->needStart)
+ {
+ p->numProcessedBlocks = 1;
+ p->needStart = False;
+ p->stopWriting = False;
+ p->exit = False;
+ Event_Reset(&p->wasStarted);
+ Event_Reset(&p->wasStopped);
+
+ Event_Set(&p->canStart);
+ Event_Wait(&p->wasStarted);
+ }
+ else
+ {
+ CriticalSection_Leave(&p->cs);
+ p->csWasEntered = False;
+ p->numProcessedBlocks++;
+ Semaphore_Release1(&p->freeSemaphore);
+ }
+ Semaphore_Wait(&p->filledSemaphore);
+ CriticalSection_Enter(&p->cs);
+ p->csWasEntered = True;
+}
+
+/* MtSync_StopWriting must be called if Writing was started */
+
+void MtSync_StopWriting(CMtSync *p)
+{
+ UInt32 myNumBlocks = p->numProcessedBlocks;
+ if (!Thread_WasCreated(&p->thread) || p->needStart)
+ return;
+ p->stopWriting = True;
+ if (p->csWasEntered)
+ {
+ CriticalSection_Leave(&p->cs);
+ p->csWasEntered = False;
+ }
+ Semaphore_Release1(&p->freeSemaphore);
+
+ Event_Wait(&p->wasStopped);
+
+ while (myNumBlocks++ != p->numProcessedBlocks)
+ {
+ Semaphore_Wait(&p->filledSemaphore);
+ Semaphore_Release1(&p->freeSemaphore);
+ }
+ p->needStart = True;
+}
+
+void MtSync_Destruct(CMtSync *p)
+{
+ if (Thread_WasCreated(&p->thread))
+ {
+ MtSync_StopWriting(p);
+ p->exit = True;
+ if (p->needStart)
+ Event_Set(&p->canStart);
+ Thread_Wait(&p->thread);
+ Thread_Close(&p->thread);
+ }
+ if (p->csWasInitialized)
+ {
+ CriticalSection_Delete(&p->cs);
+ p->csWasInitialized = False;
+ }
+
+ Event_Close(&p->canStart);
+ Event_Close(&p->wasStarted);
+ Event_Close(&p->wasStopped);
+ Semaphore_Close(&p->freeSemaphore);
+ Semaphore_Close(&p->filledSemaphore);
+
+ p->wasCreated = False;
+}
+
+#define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; }
+
+static SRes MtSync_Create2(CMtSync *p, unsigned (MY_STD_CALL *startAddress)(void *), void *obj, UInt32 numBlocks)
+{
+ if (p->wasCreated)
+ return SZ_OK;
+
+ RINOK_THREAD(CriticalSection_Init(&p->cs));
+ p->csWasInitialized = True;
+
+ RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canStart));
+ RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStarted));
+ RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStopped));
+
+ RINOK_THREAD(Semaphore_Create(&p->freeSemaphore, numBlocks, numBlocks));
+ RINOK_THREAD(Semaphore_Create(&p->filledSemaphore, 0, numBlocks));
+
+ p->needStart = True;
+
+ RINOK_THREAD(Thread_Create(&p->thread, startAddress, obj));
+ p->wasCreated = True;
+ return SZ_OK;
+}
+
+static SRes MtSync_Create(CMtSync *p, unsigned (MY_STD_CALL *startAddress)(void *), void *obj, UInt32 numBlocks)
+{
+ SRes res = MtSync_Create2(p, startAddress, obj, numBlocks);
+ if (res != SZ_OK)
+ MtSync_Destruct(p);
+ return res;
+}
+
+void MtSync_Init(CMtSync *p) { p->needStart = True; }
+
+#define kMtMaxValForNormalize 0xFFFFFFFF
+
+#define DEF_GetHeads2(name, v, action) \
+static void GetHeads ## name(const Byte *p, UInt32 pos, \
+UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc) \
+{ action; for (; numHeads != 0; numHeads--) { \
+const UInt32 value = (v); p++; *heads++ = pos - hash[value]; hash[value] = pos++; } }
+
+#define DEF_GetHeads(name, v) DEF_GetHeads2(name, v, ;)
+
+DEF_GetHeads2(2, (p[0] | ((UInt32)p[1] << 8)), hashMask = hashMask; crc = crc; )
+DEF_GetHeads(3, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8)) & hashMask)
+DEF_GetHeads(4, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5)) & hashMask)
+DEF_GetHeads(4b, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ ((UInt32)p[3] << 16)) & hashMask)
+/* DEF_GetHeads(5, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5) ^ (crc[p[4]] << 3)) & hashMask) */
+
+void HashThreadFunc(CMatchFinderMt *mt)
+{
+ CMtSync *p = &mt->hashSync;
+ for (;;)
+ {
+ UInt32 numProcessedBlocks = 0;
+ Event_Wait(&p->canStart);
+ Event_Set(&p->wasStarted);
+ for (;;)
+ {
+ if (p->exit)
+ return;
+ if (p->stopWriting)
+ {
+ p->numProcessedBlocks = numProcessedBlocks;
+ Event_Set(&p->wasStopped);
+ break;
+ }
+
+ {
+ CMatchFinder *mf = mt->MatchFinder;
+ if (MatchFinder_NeedMove(mf))
+ {
+ CriticalSection_Enter(&mt->btSync.cs);
+ CriticalSection_Enter(&mt->hashSync.cs);
+ {
+ const Byte *beforePtr = MatchFinder_GetPointerToCurrentPos(mf);
+ const Byte *afterPtr;
+ MatchFinder_MoveBlock(mf);
+ afterPtr = MatchFinder_GetPointerToCurrentPos(mf);
+ mt->pointerToCurPos -= beforePtr - afterPtr;
+ mt->buffer -= beforePtr - afterPtr;
+ }
+ CriticalSection_Leave(&mt->btSync.cs);
+ CriticalSection_Leave(&mt->hashSync.cs);
+ continue;
+ }
+
+ Semaphore_Wait(&p->freeSemaphore);
+
+ MatchFinder_ReadIfRequired(mf);
+ if (mf->pos > (kMtMaxValForNormalize - kMtHashBlockSize))
+ {
+ UInt32 subValue = (mf->pos - mf->historySize - 1);
+ MatchFinder_ReduceOffsets(mf, subValue);
+ MatchFinder_Normalize3(subValue, mf->hash + mf->fixedHashSize, mf->hashMask + 1);
+ }
+ {
+ UInt32 *heads = mt->hashBuf + ((numProcessedBlocks++) & kMtHashNumBlocksMask) * kMtHashBlockSize;
+ UInt32 num = mf->streamPos - mf->pos;
+ heads[0] = 2;
+ heads[1] = num;
+ if (num >= mf->numHashBytes)
+ {
+ num = num - mf->numHashBytes + 1;
+ if (num > kMtHashBlockSize - 2)
+ num = kMtHashBlockSize - 2;
+ mt->GetHeadsFunc(mf->buffer, mf->pos, mf->hash + mf->fixedHashSize, mf->hashMask, heads + 2, num, mf->crc);
+ heads[0] += num;
+ }
+ mf->pos += num;
+ mf->buffer += num;
+ }
+ }
+
+ Semaphore_Release1(&p->filledSemaphore);
+ }
+ }
+}
+
+void MatchFinderMt_GetNextBlock_Hash(CMatchFinderMt *p)
+{
+ MtSync_GetNextBlock(&p->hashSync);
+ p->hashBufPosLimit = p->hashBufPos = ((p->hashSync.numProcessedBlocks - 1) & kMtHashNumBlocksMask) * kMtHashBlockSize;
+ p->hashBufPosLimit += p->hashBuf[p->hashBufPos++];
+ p->hashNumAvail = p->hashBuf[p->hashBufPos++];
+}
+
+#define kEmptyHashValue 0
+
+/* #define MFMT_GM_INLINE */
+
+#ifdef MFMT_GM_INLINE
+
+#define NO_INLINE MY_FAST_CALL
+
+Int32 NO_INLINE GetMatchesSpecN(UInt32 lenLimit, UInt32 pos, const Byte *cur, CLzRef *son,
+ UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue,
+ UInt32 *_distances, UInt32 _maxLen, const UInt32 *hash, Int32 limit, UInt32 size, UInt32 *posRes)
+{
+ do
+ {
+ UInt32 *distances = _distances + 1;
+ UInt32 curMatch = pos - *hash++;
+
+ CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1;
+ CLzRef *ptr1 = son + (_cyclicBufferPos << 1);
+ UInt32 len0 = 0, len1 = 0;
+ UInt32 cutValue = _cutValue;
+ UInt32 maxLen = _maxLen;
+ for (;;)
+ {
+ UInt32 delta = pos - curMatch;
+ if (cutValue-- == 0 || delta >= _cyclicBufferSize)
+ {
+ *ptr0 = *ptr1 = kEmptyHashValue;
+ break;
+ }
+ {
+ CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
+ const Byte *pb = cur - delta;
+ UInt32 len = (len0 < len1 ? len0 : len1);
+ if (pb[len] == cur[len])
+ {
+ if (++len != lenLimit && pb[len] == cur[len])
+ while (++len != lenLimit)
+ if (pb[len] != cur[len])
+ break;
+ if (maxLen < len)
+ {
+ *distances++ = maxLen = len;
+ *distances++ = delta - 1;
+ if (len == lenLimit)
+ {
+ *ptr1 = pair[0];
+ *ptr0 = pair[1];
+ break;
+ }
+ }
+ }
+ if (pb[len] < cur[len])
+ {
+ *ptr1 = curMatch;
+ ptr1 = pair + 1;
+ curMatch = *ptr1;
+ len1 = len;
+ }
+ else
+ {
+ *ptr0 = curMatch;
+ ptr0 = pair;
+ curMatch = *ptr0;
+ len0 = len;
+ }
+ }
+ }
+ pos++;
+ _cyclicBufferPos++;
+ cur++;
+ {
+ UInt32 num = (UInt32)(distances - _distances);
+ *_distances = num - 1;
+ _distances += num;
+ limit -= num;
+ }
+ }
+ while (limit > 0 && --size != 0);
+ *posRes = pos;
+ return limit;
+}
+
+#endif
+
+void BtGetMatches(CMatchFinderMt *p, UInt32 *distances)
+{
+ UInt32 numProcessed = 0;
+ UInt32 curPos = 2;
+ UInt32 limit = kMtBtBlockSize - (p->matchMaxLen * 2);
+ distances[1] = p->hashNumAvail;
+ while (curPos < limit)
+ {
+ if (p->hashBufPos == p->hashBufPosLimit)
+ {
+ MatchFinderMt_GetNextBlock_Hash(p);
+ distances[1] = numProcessed + p->hashNumAvail;
+ if (p->hashNumAvail >= p->numHashBytes)
+ continue;
+ for (; p->hashNumAvail != 0; p->hashNumAvail--)
+ distances[curPos++] = 0;
+ break;
+ }
+ {
+ UInt32 size = p->hashBufPosLimit - p->hashBufPos;
+ UInt32 lenLimit = p->matchMaxLen;
+ UInt32 pos = p->pos;
+ UInt32 cyclicBufferPos = p->cyclicBufferPos;
+ if (lenLimit >= p->hashNumAvail)
+ lenLimit = p->hashNumAvail;
+ {
+ UInt32 size2 = p->hashNumAvail - lenLimit + 1;
+ if (size2 < size)
+ size = size2;
+ size2 = p->cyclicBufferSize - cyclicBufferPos;
+ if (size2 < size)
+ size = size2;
+ }
+ #ifndef MFMT_GM_INLINE
+ while (curPos < limit && size-- != 0)
+ {
+ UInt32 *startDistances = distances + curPos;
+ UInt32 num = (UInt32)(GetMatchesSpec1(lenLimit, pos - p->hashBuf[p->hashBufPos++],
+ pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue,
+ startDistances + 1, p->numHashBytes - 1) - startDistances);
+ *startDistances = num - 1;
+ curPos += num;
+ cyclicBufferPos++;
+ pos++;
+ p->buffer++;
+ }
+ #else
+ {
+ UInt32 posRes;
+ curPos = limit - GetMatchesSpecN(lenLimit, pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue,
+ distances + curPos, p->numHashBytes - 1, p->hashBuf + p->hashBufPos, (Int32)(limit - curPos) , size, &posRes);
+ p->hashBufPos += posRes - pos;
+ cyclicBufferPos += posRes - pos;
+ p->buffer += posRes - pos;
+ pos = posRes;
+ }
+ #endif
+
+ numProcessed += pos - p->pos;
+ p->hashNumAvail -= pos - p->pos;
+ p->pos = pos;
+ if (cyclicBufferPos == p->cyclicBufferSize)
+ cyclicBufferPos = 0;
+ p->cyclicBufferPos = cyclicBufferPos;
+ }
+ }
+ distances[0] = curPos;
+}
+
+void BtFillBlock(CMatchFinderMt *p, UInt32 globalBlockIndex)
+{
+ CMtSync *sync = &p->hashSync;
+ if (!sync->needStart)
+ {
+ CriticalSection_Enter(&sync->cs);
+ sync->csWasEntered = True;
+ }
+
+ BtGetMatches(p, p->btBuf + (globalBlockIndex & kMtBtNumBlocksMask) * kMtBtBlockSize);
+
+ if (p->pos > kMtMaxValForNormalize - kMtBtBlockSize)
+ {
+ UInt32 subValue = p->pos - p->cyclicBufferSize;
+ MatchFinder_Normalize3(subValue, p->son, p->cyclicBufferSize * 2);
+ p->pos -= subValue;
+ }
+
+ if (!sync->needStart)
+ {
+ CriticalSection_Leave(&sync->cs);
+ sync->csWasEntered = False;
+ }
+}
+
+void BtThreadFunc(CMatchFinderMt *mt)
+{
+ CMtSync *p = &mt->btSync;
+ for (;;)
+ {
+ UInt32 blockIndex = 0;
+ Event_Wait(&p->canStart);
+ Event_Set(&p->wasStarted);
+ for (;;)
+ {
+ if (p->exit)
+ return;
+ if (p->stopWriting)
+ {
+ p->numProcessedBlocks = blockIndex;
+ MtSync_StopWriting(&mt->hashSync);
+ Event_Set(&p->wasStopped);
+ break;
+ }
+ Semaphore_Wait(&p->freeSemaphore);
+ BtFillBlock(mt, blockIndex++);
+ Semaphore_Release1(&p->filledSemaphore);
+ }
+ }
+}
+
+void MatchFinderMt_Construct(CMatchFinderMt *p)
+{
+ p->hashBuf = 0;
+ MtSync_Construct(&p->hashSync);
+ MtSync_Construct(&p->btSync);
+}
+
+void MatchFinderMt_FreeMem(CMatchFinderMt *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->hashBuf);
+ p->hashBuf = 0;
+}
+
+void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc)
+{
+ MtSync_Destruct(&p->hashSync);
+ MtSync_Destruct(&p->btSync);
+ MatchFinderMt_FreeMem(p, alloc);
+}
+
+#define kHashBufferSize (kMtHashBlockSize * kMtHashNumBlocks)
+#define kBtBufferSize (kMtBtBlockSize * kMtBtNumBlocks)
+
+static unsigned MY_STD_CALL HashThreadFunc2(void *p) { HashThreadFunc((CMatchFinderMt *)p); return 0; }
+static unsigned MY_STD_CALL BtThreadFunc2(void *p)
+{
+ Byte allocaDummy[0x180];
+ int i = 0;
+ for (i = 0; i < 16; i++)
+ allocaDummy[i] = (Byte)i;
+ BtThreadFunc((CMatchFinderMt *)p);
+ return 0;
+}
+
+SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore,
+ UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc)
+{
+ CMatchFinder *mf = p->MatchFinder;
+ p->historySize = historySize;
+ if (kMtBtBlockSize <= matchMaxLen * 4)
+ return SZ_ERROR_PARAM;
+ if (p->hashBuf == 0)
+ {
+ p->hashBuf = (UInt32 *)alloc->Alloc(alloc, (kHashBufferSize + kBtBufferSize) * sizeof(UInt32));
+ if (p->hashBuf == 0)
+ return SZ_ERROR_MEM;
+ p->btBuf = p->hashBuf + kHashBufferSize;
+ }
+ keepAddBufferBefore += (kHashBufferSize + kBtBufferSize);
+ keepAddBufferAfter += kMtHashBlockSize;
+ if (!MatchFinder_Create(mf, historySize, keepAddBufferBefore, matchMaxLen, keepAddBufferAfter, alloc))
+ return SZ_ERROR_MEM;
+
+ RINOK(MtSync_Create(&p->hashSync, HashThreadFunc2, p, kMtHashNumBlocks));
+ RINOK(MtSync_Create(&p->btSync, BtThreadFunc2, p, kMtBtNumBlocks));
+ return SZ_OK;
+}
+
+/* Call it after ReleaseStream / SetStream */
+void MatchFinderMt_Init(CMatchFinderMt *p)
+{
+ CMatchFinder *mf = p->MatchFinder;
+ p->btBufPos = p->btBufPosLimit = 0;
+ p->hashBufPos = p->hashBufPosLimit = 0;
+ MatchFinder_Init(mf);
+ p->pointerToCurPos = MatchFinder_GetPointerToCurrentPos(mf);
+ p->btNumAvailBytes = 0;
+ p->lzPos = p->historySize + 1;
+
+ p->hash = mf->hash;
+ p->fixedHashSize = mf->fixedHashSize;
+ p->crc = mf->crc;
+
+ p->son = mf->son;
+ p->matchMaxLen = mf->matchMaxLen;
+ p->numHashBytes = mf->numHashBytes;
+ p->pos = mf->pos;
+ p->buffer = mf->buffer;
+ p->cyclicBufferPos = mf->cyclicBufferPos;
+ p->cyclicBufferSize = mf->cyclicBufferSize;
+ p->cutValue = mf->cutValue;
+}
+
+/* ReleaseStream is required to finish multithreading */
+void MatchFinderMt_ReleaseStream(CMatchFinderMt *p)
+{
+ MtSync_StopWriting(&p->btSync);
+ /* p->MatchFinder->ReleaseStream(); */
+}
+
+void MatchFinderMt_Normalize(CMatchFinderMt *p)
+{
+ MatchFinder_Normalize3(p->lzPos - p->historySize - 1, p->hash, p->fixedHashSize);
+ p->lzPos = p->historySize + 1;
+}
+
+void MatchFinderMt_GetNextBlock_Bt(CMatchFinderMt *p)
+{
+ UInt32 blockIndex;
+ MtSync_GetNextBlock(&p->btSync);
+ blockIndex = ((p->btSync.numProcessedBlocks - 1) & kMtBtNumBlocksMask);
+ p->btBufPosLimit = p->btBufPos = blockIndex * kMtBtBlockSize;
+ p->btBufPosLimit += p->btBuf[p->btBufPos++];
+ p->btNumAvailBytes = p->btBuf[p->btBufPos++];
+ if (p->lzPos >= kMtMaxValForNormalize - kMtBtBlockSize)
+ MatchFinderMt_Normalize(p);
+}
+
+const Byte * MatchFinderMt_GetPointerToCurrentPos(CMatchFinderMt *p)
+{
+ return p->pointerToCurPos;
+}
+
+#define GET_NEXT_BLOCK_IF_REQUIRED if (p->btBufPos == p->btBufPosLimit) MatchFinderMt_GetNextBlock_Bt(p);
+
+UInt32 MatchFinderMt_GetNumAvailableBytes(CMatchFinderMt *p)
+{
+ GET_NEXT_BLOCK_IF_REQUIRED;
+ return p->btNumAvailBytes;
+}
+
+Byte MatchFinderMt_GetIndexByte(CMatchFinderMt *p, Int32 index)
+{
+ return p->pointerToCurPos[index];
+}
+
+UInt32 * MixMatches2(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances)
+{
+ UInt32 hash2Value, curMatch2;
+ UInt32 *hash = p->hash;
+ const Byte *cur = p->pointerToCurPos;
+ UInt32 lzPos = p->lzPos;
+ MT_HASH2_CALC
+
+ curMatch2 = hash[hash2Value];
+ hash[hash2Value] = lzPos;
+
+ if (curMatch2 >= matchMinPos)
+ if (cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0])
+ {
+ *distances++ = 2;
+ *distances++ = lzPos - curMatch2 - 1;
+ }
+ return distances;
+}
+
+UInt32 * MixMatches3(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances)
+{
+ UInt32 hash2Value, hash3Value, curMatch2, curMatch3;
+ UInt32 *hash = p->hash;
+ const Byte *cur = p->pointerToCurPos;
+ UInt32 lzPos = p->lzPos;
+ MT_HASH3_CALC
+
+ curMatch2 = hash[ hash2Value];
+ curMatch3 = hash[kFix3HashSize + hash3Value];
+
+ hash[ hash2Value] =
+ hash[kFix3HashSize + hash3Value] =
+ lzPos;
+
+ if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0])
+ {
+ distances[1] = lzPos - curMatch2 - 1;
+ if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2])
+ {
+ distances[0] = 3;
+ return distances + 2;
+ }
+ distances[0] = 2;
+ distances += 2;
+ }
+ if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0])
+ {
+ *distances++ = 3;
+ *distances++ = lzPos - curMatch3 - 1;
+ }
+ return distances;
+}
+
+/*
+UInt32 *MixMatches4(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances)
+{
+ UInt32 hash2Value, hash3Value, hash4Value, curMatch2, curMatch3, curMatch4;
+ UInt32 *hash = p->hash;
+ const Byte *cur = p->pointerToCurPos;
+ UInt32 lzPos = p->lzPos;
+ MT_HASH4_CALC
+
+ curMatch2 = hash[ hash2Value];
+ curMatch3 = hash[kFix3HashSize + hash3Value];
+ curMatch4 = hash[kFix4HashSize + hash4Value];
+
+ hash[ hash2Value] =
+ hash[kFix3HashSize + hash3Value] =
+ hash[kFix4HashSize + hash4Value] =
+ lzPos;
+
+ if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0])
+ {
+ distances[1] = lzPos - curMatch2 - 1;
+ if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2])
+ {
+ distances[0] = (cur[(ptrdiff_t)curMatch2 - lzPos + 3] == cur[3]) ? 4 : 3;
+ return distances + 2;
+ }
+ distances[0] = 2;
+ distances += 2;
+ }
+ if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0])
+ {
+ distances[1] = lzPos - curMatch3 - 1;
+ if (cur[(ptrdiff_t)curMatch3 - lzPos + 3] == cur[3])
+ {
+ distances[0] = 4;
+ return distances + 2;
+ }
+ distances[0] = 3;
+ distances += 2;
+ }
+
+ if (curMatch4 >= matchMinPos)
+ if (
+ cur[(ptrdiff_t)curMatch4 - lzPos] == cur[0] &&
+ cur[(ptrdiff_t)curMatch4 - lzPos + 3] == cur[3]
+ )
+ {
+ *distances++ = 4;
+ *distances++ = lzPos - curMatch4 - 1;
+ }
+ return distances;
+}
+*/
+
+#define INCREASE_LZ_POS p->lzPos++; p->pointerToCurPos++;
+
+UInt32 MatchFinderMt2_GetMatches(CMatchFinderMt *p, UInt32 *distances)
+{
+ const UInt32 *btBuf = p->btBuf + p->btBufPos;
+ UInt32 len = *btBuf++;
+ p->btBufPos += 1 + len;
+ p->btNumAvailBytes--;
+ {
+ UInt32 i;
+ for (i = 0; i < len; i += 2)
+ {
+ *distances++ = *btBuf++;
+ *distances++ = *btBuf++;
+ }
+ }
+ INCREASE_LZ_POS
+ return len;
+}
+
+UInt32 MatchFinderMt_GetMatches(CMatchFinderMt *p, UInt32 *distances)
+{
+ const UInt32 *btBuf = p->btBuf + p->btBufPos;
+ UInt32 len = *btBuf++;
+ p->btBufPos += 1 + len;
+
+ if (len == 0)
+ {
+ if (p->btNumAvailBytes-- >= 4)
+ len = (UInt32)(p->MixMatchesFunc(p, p->lzPos - p->historySize, distances) - (distances));
+ }
+ else
+ {
+ /* Condition: there are matches in btBuf with length < p->numHashBytes */
+ UInt32 *distances2;
+ p->btNumAvailBytes--;
+ distances2 = p->MixMatchesFunc(p, p->lzPos - btBuf[1], distances);
+ do
+ {
+ *distances2++ = *btBuf++;
+ *distances2++ = *btBuf++;
+ }
+ while ((len -= 2) != 0);
+ len = (UInt32)(distances2 - (distances));
+ }
+ INCREASE_LZ_POS
+ return len;
+}
+
+#define SKIP_HEADER2_MT do { GET_NEXT_BLOCK_IF_REQUIRED
+#define SKIP_HEADER_MT(n) SKIP_HEADER2_MT if (p->btNumAvailBytes-- >= (n)) { const Byte *cur = p->pointerToCurPos; UInt32 *hash = p->hash;
+#define SKIP_FOOTER_MT } INCREASE_LZ_POS p->btBufPos += p->btBuf[p->btBufPos] + 1; } while (--num != 0);
+
+void MatchFinderMt0_Skip(CMatchFinderMt *p, UInt32 num)
+{
+ SKIP_HEADER2_MT { p->btNumAvailBytes--;
+ SKIP_FOOTER_MT
+}
+
+void MatchFinderMt2_Skip(CMatchFinderMt *p, UInt32 num)
+{
+ SKIP_HEADER_MT(2)
+ UInt32 hash2Value;
+ MT_HASH2_CALC
+ hash[hash2Value] = p->lzPos;
+ SKIP_FOOTER_MT
+}
+
+void MatchFinderMt3_Skip(CMatchFinderMt *p, UInt32 num)
+{
+ SKIP_HEADER_MT(3)
+ UInt32 hash2Value, hash3Value;
+ MT_HASH3_CALC
+ hash[kFix3HashSize + hash3Value] =
+ hash[ hash2Value] =
+ p->lzPos;
+ SKIP_FOOTER_MT
+}
+
+/*
+void MatchFinderMt4_Skip(CMatchFinderMt *p, UInt32 num)
+{
+ SKIP_HEADER_MT(4)
+ UInt32 hash2Value, hash3Value, hash4Value;
+ MT_HASH4_CALC
+ hash[kFix4HashSize + hash4Value] =
+ hash[kFix3HashSize + hash3Value] =
+ hash[ hash2Value] =
+ p->lzPos;
+ SKIP_FOOTER_MT
+}
+*/
+
+void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable)
+{
+ vTable->Init = (Mf_Init_Func)MatchFinderMt_Init;
+ vTable->GetIndexByte = (Mf_GetIndexByte_Func)MatchFinderMt_GetIndexByte;
+ vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinderMt_GetNumAvailableBytes;
+ vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinderMt_GetPointerToCurrentPos;
+ vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt_GetMatches;
+ switch(p->MatchFinder->numHashBytes)
+ {
+ case 2:
+ p->GetHeadsFunc = GetHeads2;
+ p->MixMatchesFunc = (Mf_Mix_Matches)0;
+ vTable->Skip = (Mf_Skip_Func)MatchFinderMt0_Skip;
+ vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt2_GetMatches;
+ break;
+ case 3:
+ p->GetHeadsFunc = GetHeads3;
+ p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches2;
+ vTable->Skip = (Mf_Skip_Func)MatchFinderMt2_Skip;
+ break;
+ default:
+ /* case 4: */
+ p->GetHeadsFunc = p->MatchFinder->bigHash ? GetHeads4b : GetHeads4;
+ /* p->GetHeadsFunc = GetHeads4; */
+ p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches3;
+ vTable->Skip = (Mf_Skip_Func)MatchFinderMt3_Skip;
+ break;
+ /*
+ default:
+ p->GetHeadsFunc = GetHeads5;
+ p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches4;
+ vTable->Skip = (Mf_Skip_Func)MatchFinderMt4_Skip;
+ break;
+ */
+ }
+}
diff --git a/dep/StormLib/src/lzma/C/LzFindMt.h b/dep/StormLib/src/lzma/C/LzFindMt.h
new file mode 100644
index 000000000..b985af5fe
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzFindMt.h
@@ -0,0 +1,105 @@
+/* LzFindMt.h -- multithreaded Match finder for LZ algorithms
+2009-02-07 : Igor Pavlov : Public domain */
+
+#ifndef __LZ_FIND_MT_H
+#define __LZ_FIND_MT_H
+
+#include "LzFind.h"
+#include "Threads.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kMtHashBlockSize (1 << 13)
+#define kMtHashNumBlocks (1 << 3)
+#define kMtHashNumBlocksMask (kMtHashNumBlocks - 1)
+
+#define kMtBtBlockSize (1 << 14)
+#define kMtBtNumBlocks (1 << 6)
+#define kMtBtNumBlocksMask (kMtBtNumBlocks - 1)
+
+typedef struct _CMtSync
+{
+ Bool wasCreated;
+ Bool needStart;
+ Bool exit;
+ Bool stopWriting;
+
+ CThread thread;
+ CAutoResetEvent canStart;
+ CAutoResetEvent wasStarted;
+ CAutoResetEvent wasStopped;
+ CSemaphore freeSemaphore;
+ CSemaphore filledSemaphore;
+ Bool csWasInitialized;
+ Bool csWasEntered;
+ CCriticalSection cs;
+ UInt32 numProcessedBlocks;
+} CMtSync;
+
+typedef UInt32 * (*Mf_Mix_Matches)(void *p, UInt32 matchMinPos, UInt32 *distances);
+
+/* kMtCacheLineDummy must be >= size_of_CPU_cache_line */
+#define kMtCacheLineDummy 128
+
+typedef void (*Mf_GetHeads)(const Byte *buffer, UInt32 pos,
+ UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc);
+
+typedef struct _CMatchFinderMt
+{
+ /* LZ */
+ const Byte *pointerToCurPos;
+ UInt32 *btBuf;
+ UInt32 btBufPos;
+ UInt32 btBufPosLimit;
+ UInt32 lzPos;
+ UInt32 btNumAvailBytes;
+
+ UInt32 *hash;
+ UInt32 fixedHashSize;
+ UInt32 historySize;
+ const UInt32 *crc;
+
+ Mf_Mix_Matches MixMatchesFunc;
+
+ /* LZ + BT */
+ CMtSync btSync;
+ Byte btDummy[kMtCacheLineDummy];
+
+ /* BT */
+ UInt32 *hashBuf;
+ UInt32 hashBufPos;
+ UInt32 hashBufPosLimit;
+ UInt32 hashNumAvail;
+
+ CLzRef *son;
+ UInt32 matchMaxLen;
+ UInt32 numHashBytes;
+ UInt32 pos;
+ Byte *buffer;
+ UInt32 cyclicBufferPos;
+ UInt32 cyclicBufferSize; /* it must be historySize + 1 */
+ UInt32 cutValue;
+
+ /* BT + Hash */
+ CMtSync hashSync;
+ /* Byte hashDummy[kMtCacheLineDummy]; */
+
+ /* Hash */
+ Mf_GetHeads GetHeadsFunc;
+ CMatchFinder *MatchFinder;
+} CMatchFinderMt;
+
+void MatchFinderMt_Construct(CMatchFinderMt *p);
+void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc);
+SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore,
+ UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc);
+void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable);
+void MatchFinderMt_ReleaseStream(CMatchFinderMt *p);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dep/StormLib/src/lzma/C/LzHash.h b/dep/StormLib/src/lzma/C/LzHash.h
new file mode 100644
index 000000000..f3e89966c
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzHash.h
@@ -0,0 +1,54 @@
+/* LzHash.h -- HASH functions for LZ algorithms
+2009-02-07 : Igor Pavlov : Public domain */
+
+#ifndef __LZ_HASH_H
+#define __LZ_HASH_H
+
+#define kHash2Size (1 << 10)
+#define kHash3Size (1 << 16)
+#define kHash4Size (1 << 20)
+
+#define kFix3HashSize (kHash2Size)
+#define kFix4HashSize (kHash2Size + kHash3Size)
+#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size)
+
+#define HASH2_CALC hashValue = cur[0] | ((UInt32)cur[1] << 8);
+
+#define HASH3_CALC { \
+ UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+ hash2Value = temp & (kHash2Size - 1); \
+ hashValue = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; }
+
+#define HASH4_CALC { \
+ UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+ hash2Value = temp & (kHash2Size - 1); \
+ hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
+ hashValue = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & p->hashMask; }
+
+#define HASH5_CALC { \
+ UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+ hash2Value = temp & (kHash2Size - 1); \
+ hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
+ hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)); \
+ hashValue = (hash4Value ^ (p->crc[cur[4]] << 3)) & p->hashMask; \
+ hash4Value &= (kHash4Size - 1); }
+
+/* #define HASH_ZIP_CALC hashValue = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */
+#define HASH_ZIP_CALC hashValue = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF;
+
+
+#define MT_HASH2_CALC \
+ hash2Value = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1);
+
+#define MT_HASH3_CALC { \
+ UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+ hash2Value = temp & (kHash2Size - 1); \
+ hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); }
+
+#define MT_HASH4_CALC { \
+ UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+ hash2Value = temp & (kHash2Size - 1); \
+ hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \
+ hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); }
+
+#endif
diff --git a/dep/StormLib/src/lzma/C/LzmaDec.c b/dep/StormLib/src/lzma/C/LzmaDec.c
new file mode 100644
index 000000000..2036761bf
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzmaDec.c
@@ -0,0 +1,999 @@
+/* LzmaDec.c -- LZMA Decoder
+2009-09-20 : Igor Pavlov : Public domain */
+
+#include "LzmaDec.h"
+
+#include
+
+#define kNumTopBits 24
+#define kTopValue ((UInt32)1 << kNumTopBits)
+
+#define kNumBitModelTotalBits 11
+#define kBitModelTotal (1 << kNumBitModelTotalBits)
+#define kNumMoveBits 5
+
+#define RC_INIT_SIZE 5
+
+#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); }
+
+#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
+#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
+#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits));
+#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \
+ { UPDATE_0(p); i = (i + i); A0; } else \
+ { UPDATE_1(p); i = (i + i) + 1; A1; }
+#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;)
+
+#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); }
+#define TREE_DECODE(probs, limit, i) \
+ { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; }
+
+/* #define _LZMA_SIZE_OPT */
+
+#ifdef _LZMA_SIZE_OPT
+#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i)
+#else
+#define TREE_6_DECODE(probs, i) \
+ { i = 1; \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ i -= 0x40; }
+#endif
+
+#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); }
+
+#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
+#define UPDATE_0_CHECK range = bound;
+#define UPDATE_1_CHECK range -= bound; code -= bound;
+#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \
+ { UPDATE_0_CHECK; i = (i + i); A0; } else \
+ { UPDATE_1_CHECK; i = (i + i) + 1; A1; }
+#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;)
+#define TREE_DECODE_CHECK(probs, limit, i) \
+ { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; }
+
+
+#define kNumPosBitsMax 4
+#define kNumPosStatesMax (1 << kNumPosBitsMax)
+
+#define kLenNumLowBits 3
+#define kLenNumLowSymbols (1 << kLenNumLowBits)
+#define kLenNumMidBits 3
+#define kLenNumMidSymbols (1 << kLenNumMidBits)
+#define kLenNumHighBits 8
+#define kLenNumHighSymbols (1 << kLenNumHighBits)
+
+#define LenChoice 0
+#define LenChoice2 (LenChoice + 1)
+#define LenLow (LenChoice2 + 1)
+#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits))
+#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits))
+#define kNumLenProbs (LenHigh + kLenNumHighSymbols)
+
+
+#define kNumStates 12
+#define kNumLitStates 7
+
+#define kStartPosModelIndex 4
+#define kEndPosModelIndex 14
+#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
+
+#define kNumPosSlotBits 6
+#define kNumLenToPosStates 4
+
+#define kNumAlignBits 4
+#define kAlignTableSize (1 << kNumAlignBits)
+
+#define kMatchMinLen 2
+#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
+
+#define IsMatch 0
+#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax))
+#define IsRepG0 (IsRep + kNumStates)
+#define IsRepG1 (IsRepG0 + kNumStates)
+#define IsRepG2 (IsRepG1 + kNumStates)
+#define IsRep0Long (IsRepG2 + kNumStates)
+#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax))
+#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits))
+#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex)
+#define LenCoder (Align + kAlignTableSize)
+#define RepLenCoder (LenCoder + kNumLenProbs)
+#define Literal (RepLenCoder + kNumLenProbs)
+
+#define LZMA_BASE_SIZE 1846
+#define LZMA_LIT_SIZE 768
+
+#define LzmaProps_GetNumProbs(p) ((UInt32)LZMA_BASE_SIZE + (LZMA_LIT_SIZE << ((p)->lc + (p)->lp)))
+
+#if Literal != LZMA_BASE_SIZE
+StopCompilingDueBUG
+#endif
+
+#define LZMA_DIC_MIN (1 << 12)
+
+/* First LZMA-symbol is always decoded.
+And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization
+Out:
+ Result:
+ SZ_OK - OK
+ SZ_ERROR_DATA - Error
+ p->remainLen:
+ < kMatchSpecLenStart : normal remain
+ = kMatchSpecLenStart : finished
+ = kMatchSpecLenStart + 1 : Flush marker
+ = kMatchSpecLenStart + 2 : State Init Marker
+*/
+
+static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
+{
+ CLzmaProb *probs = p->probs;
+
+ unsigned state = p->state;
+ UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3];
+ unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1;
+ unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1;
+ unsigned lc = p->prop.lc;
+
+ Byte *dic = p->dic;
+ SizeT dicBufSize = p->dicBufSize;
+ SizeT dicPos = p->dicPos;
+
+ UInt32 processedPos = p->processedPos;
+ UInt32 checkDicSize = p->checkDicSize;
+ unsigned len = 0;
+
+ const Byte *buf = p->buf;
+ UInt32 range = p->range;
+ UInt32 code = p->code;
+
+ do
+ {
+ CLzmaProb *prob;
+ UInt32 bound;
+ unsigned ttt;
+ unsigned posState = processedPos & pbMask;
+
+ prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0(prob)
+ {
+ unsigned symbol;
+ UPDATE_0(prob);
+ prob = probs + Literal;
+ if (checkDicSize != 0 || processedPos != 0)
+ prob += (LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) +
+ (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc))));
+
+ if (state < kNumLitStates)
+ {
+ state -= (state < 4) ? state : 3;
+ symbol = 1;
+ do { GET_BIT(prob + symbol, symbol) } while (symbol < 0x100);
+ }
+ else
+ {
+ unsigned matchByte = p->dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
+ unsigned offs = 0x100;
+ state -= (state < 10) ? 3 : 6;
+ symbol = 1;
+ do
+ {
+ unsigned bit;
+ CLzmaProb *probLit;
+ matchByte <<= 1;
+ bit = (matchByte & offs);
+ probLit = prob + offs + bit + symbol;
+ GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit)
+ }
+ while (symbol < 0x100);
+ }
+ dic[dicPos++] = (Byte)symbol;
+ processedPos++;
+ continue;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ prob = probs + IsRep + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ state += kNumStates;
+ prob = probs + LenCoder;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ if (checkDicSize == 0 && processedPos == 0)
+ return SZ_ERROR_DATA;
+ prob = probs + IsRepG0 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
+ dicPos++;
+ processedPos++;
+ state = state < kNumLitStates ? 9 : 11;
+ continue;
+ }
+ UPDATE_1(prob);
+ }
+ else
+ {
+ UInt32 distance;
+ UPDATE_1(prob);
+ prob = probs + IsRepG1 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ distance = rep1;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ prob = probs + IsRepG2 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ distance = rep2;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ distance = rep3;
+ rep3 = rep2;
+ }
+ rep2 = rep1;
+ }
+ rep1 = rep0;
+ rep0 = distance;
+ }
+ state = state < kNumLitStates ? 8 : 11;
+ prob = probs + RepLenCoder;
+ }
+ {
+ unsigned limit, offset;
+ CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ offset = 0;
+ limit = (1 << kLenNumLowBits);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenChoice2;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ offset = kLenNumLowSymbols;
+ limit = (1 << kLenNumMidBits);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenHigh;
+ offset = kLenNumLowSymbols + kLenNumMidSymbols;
+ limit = (1 << kLenNumHighBits);
+ }
+ }
+ TREE_DECODE(probLen, limit, len);
+ len += offset;
+ }
+
+ if (state >= kNumStates)
+ {
+ UInt32 distance;
+ prob = probs + PosSlot +
+ ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits);
+ TREE_6_DECODE(prob, distance);
+ if (distance >= kStartPosModelIndex)
+ {
+ unsigned posSlot = (unsigned)distance;
+ int numDirectBits = (int)(((distance >> 1) - 1));
+ distance = (2 | (distance & 1));
+ if (posSlot < kEndPosModelIndex)
+ {
+ distance <<= numDirectBits;
+ prob = probs + SpecPos + distance - posSlot - 1;
+ {
+ UInt32 mask = 1;
+ unsigned i = 1;
+ do
+ {
+ GET_BIT2(prob + i, i, ; , distance |= mask);
+ mask <<= 1;
+ }
+ while (--numDirectBits != 0);
+ }
+ }
+ else
+ {
+ numDirectBits -= kNumAlignBits;
+ do
+ {
+ NORMALIZE
+ range >>= 1;
+
+ {
+ UInt32 t;
+ code -= range;
+ t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */
+ distance = (distance << 1) + (t + 1);
+ code += range & t;
+ }
+ /*
+ distance <<= 1;
+ if (code >= range)
+ {
+ code -= range;
+ distance |= 1;
+ }
+ */
+ }
+ while (--numDirectBits != 0);
+ prob = probs + Align;
+ distance <<= kNumAlignBits;
+ {
+ unsigned i = 1;
+ GET_BIT2(prob + i, i, ; , distance |= 1);
+ GET_BIT2(prob + i, i, ; , distance |= 2);
+ GET_BIT2(prob + i, i, ; , distance |= 4);
+ GET_BIT2(prob + i, i, ; , distance |= 8);
+ }
+ if (distance == (UInt32)0xFFFFFFFF)
+ {
+ len += kMatchSpecLenStart;
+ state -= kNumStates;
+ break;
+ }
+ }
+ }
+ rep3 = rep2;
+ rep2 = rep1;
+ rep1 = rep0;
+ rep0 = distance + 1;
+ if (checkDicSize == 0)
+ {
+ if (distance >= processedPos)
+ return SZ_ERROR_DATA;
+ }
+ else if (distance >= checkDicSize)
+ return SZ_ERROR_DATA;
+ state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3;
+ }
+
+ len += kMatchMinLen;
+
+ if (limit == dicPos)
+ return SZ_ERROR_DATA;
+ {
+ SizeT rem = limit - dicPos;
+ unsigned curLen = ((rem < len) ? (unsigned)rem : len);
+ SizeT pos = (dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0);
+
+ processedPos += curLen;
+
+ len -= curLen;
+ if (pos + curLen <= dicBufSize)
+ {
+ Byte *dest = dic + dicPos;
+ ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos;
+ const Byte *lim = dest + curLen;
+ dicPos += curLen;
+ do
+ *(dest) = (Byte)*(dest + src);
+ while (++dest != lim);
+ }
+ else
+ {
+ do
+ {
+ dic[dicPos++] = dic[pos];
+ if (++pos == dicBufSize)
+ pos = 0;
+ }
+ while (--curLen != 0);
+ }
+ }
+ }
+ }
+ while (dicPos < limit && buf < bufLimit);
+ NORMALIZE;
+ p->buf = buf;
+ p->range = range;
+ p->code = code;
+ p->remainLen = len;
+ p->dicPos = dicPos;
+ p->processedPos = processedPos;
+ p->reps[0] = rep0;
+ p->reps[1] = rep1;
+ p->reps[2] = rep2;
+ p->reps[3] = rep3;
+ p->state = state;
+
+ return SZ_OK;
+}
+
+static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit)
+{
+ if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart)
+ {
+ Byte *dic = p->dic;
+ SizeT dicPos = p->dicPos;
+ SizeT dicBufSize = p->dicBufSize;
+ unsigned len = p->remainLen;
+ UInt32 rep0 = p->reps[0];
+ if (limit - dicPos < len)
+ len = (unsigned)(limit - dicPos);
+
+ if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len)
+ p->checkDicSize = p->prop.dicSize;
+
+ p->processedPos += len;
+ p->remainLen -= len;
+ while (len-- != 0)
+ {
+ dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)];
+ dicPos++;
+ }
+ p->dicPos = dicPos;
+ }
+}
+
+static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
+{
+ do
+ {
+ SizeT limit2 = limit;
+ if (p->checkDicSize == 0)
+ {
+ UInt32 rem = p->prop.dicSize - p->processedPos;
+ if (limit - p->dicPos > rem)
+ limit2 = p->dicPos + rem;
+ }
+ RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit));
+ if (p->processedPos >= p->prop.dicSize)
+ p->checkDicSize = p->prop.dicSize;
+ LzmaDec_WriteRem(p, limit);
+ }
+ while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart);
+
+ if (p->remainLen > kMatchSpecLenStart)
+ {
+ p->remainLen = kMatchSpecLenStart;
+ }
+ return 0;
+}
+
+typedef enum
+{
+ DUMMY_ERROR, /* unexpected end of input stream */
+ DUMMY_LIT,
+ DUMMY_MATCH,
+ DUMMY_REP
+} ELzmaDummy;
+
+static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize)
+{
+ UInt32 range = p->range;
+ UInt32 code = p->code;
+ const Byte *bufLimit = buf + inSize;
+ CLzmaProb *probs = p->probs;
+ unsigned state = p->state;
+ ELzmaDummy res;
+
+ {
+ CLzmaProb *prob;
+ UInt32 bound;
+ unsigned ttt;
+ unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1);
+
+ prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK
+
+ /* if (bufLimit - buf >= 7) return DUMMY_LIT; */
+
+ prob = probs + Literal;
+ if (p->checkDicSize != 0 || p->processedPos != 0)
+ prob += (LZMA_LIT_SIZE *
+ ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) +
+ (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
+
+ if (state < kNumLitStates)
+ {
+ unsigned symbol = 1;
+ do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100);
+ }
+ else
+ {
+ unsigned matchByte = p->dic[p->dicPos - p->reps[0] +
+ ((p->dicPos < p->reps[0]) ? p->dicBufSize : 0)];
+ unsigned offs = 0x100;
+ unsigned symbol = 1;
+ do
+ {
+ unsigned bit;
+ CLzmaProb *probLit;
+ matchByte <<= 1;
+ bit = (matchByte & offs);
+ probLit = prob + offs + bit + symbol;
+ GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit)
+ }
+ while (symbol < 0x100);
+ }
+ res = DUMMY_LIT;
+ }
+ else
+ {
+ unsigned len;
+ UPDATE_1_CHECK;
+
+ prob = probs + IsRep + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ state = 0;
+ prob = probs + LenCoder;
+ res = DUMMY_MATCH;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ res = DUMMY_REP;
+ prob = probs + IsRepG0 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ NORMALIZE_CHECK;
+ return DUMMY_REP;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ }
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ prob = probs + IsRepG1 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ prob = probs + IsRepG2 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ }
+ }
+ }
+ state = kNumStates;
+ prob = probs + RepLenCoder;
+ }
+ {
+ unsigned limit, offset;
+ CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0_CHECK(probLen)
+ {
+ UPDATE_0_CHECK;
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ offset = 0;
+ limit = 1 << kLenNumLowBits;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ probLen = prob + LenChoice2;
+ IF_BIT_0_CHECK(probLen)
+ {
+ UPDATE_0_CHECK;
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ offset = kLenNumLowSymbols;
+ limit = 1 << kLenNumMidBits;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ probLen = prob + LenHigh;
+ offset = kLenNumLowSymbols + kLenNumMidSymbols;
+ limit = 1 << kLenNumHighBits;
+ }
+ }
+ TREE_DECODE_CHECK(probLen, limit, len);
+ len += offset;
+ }
+
+ if (state < 4)
+ {
+ unsigned posSlot;
+ prob = probs + PosSlot +
+ ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) <<
+ kNumPosSlotBits);
+ TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot);
+ if (posSlot >= kStartPosModelIndex)
+ {
+ int numDirectBits = ((posSlot >> 1) - 1);
+
+ /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */
+
+ if (posSlot < kEndPosModelIndex)
+ {
+ prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1;
+ }
+ else
+ {
+ numDirectBits -= kNumAlignBits;
+ do
+ {
+ NORMALIZE_CHECK
+ range >>= 1;
+ code -= range & (((code - range) >> 31) - 1);
+ /* if (code >= range) code -= range; */
+ }
+ while (--numDirectBits != 0);
+ prob = probs + Align;
+ numDirectBits = kNumAlignBits;
+ }
+ {
+ unsigned i = 1;
+ do
+ {
+ GET_BIT_CHECK(prob + i, i);
+ }
+ while (--numDirectBits != 0);
+ }
+ }
+ }
+ }
+ }
+ NORMALIZE_CHECK;
+ return res;
+}
+
+
+static void LzmaDec_InitRc(CLzmaDec *p, const Byte *data)
+{
+ p->code = ((UInt32)data[1] << 24) | ((UInt32)data[2] << 16) | ((UInt32)data[3] << 8) | ((UInt32)data[4]);
+ p->range = 0xFFFFFFFF;
+ p->needFlush = 0;
+}
+
+void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState)
+{
+ p->needFlush = 1;
+ p->remainLen = 0;
+ p->tempBufSize = 0;
+
+ if (initDic)
+ {
+ p->processedPos = 0;
+ p->checkDicSize = 0;
+ p->needInitState = 1;
+ }
+ if (initState)
+ p->needInitState = 1;
+}
+
+void LzmaDec_Init(CLzmaDec *p)
+{
+ p->dicPos = 0;
+ LzmaDec_InitDicAndState(p, True, True);
+}
+
+static void LzmaDec_InitStateReal(CLzmaDec *p)
+{
+ UInt32 numProbs = Literal + ((UInt32)LZMA_LIT_SIZE << (p->prop.lc + p->prop.lp));
+ UInt32 i;
+ CLzmaProb *probs = p->probs;
+ for (i = 0; i < numProbs; i++)
+ probs[i] = kBitModelTotal >> 1;
+ p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1;
+ p->state = 0;
+ p->needInitState = 0;
+}
+
+SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen,
+ ELzmaFinishMode finishMode, ELzmaStatus *status)
+{
+ SizeT inSize = *srcLen;
+ (*srcLen) = 0;
+ LzmaDec_WriteRem(p, dicLimit);
+
+ *status = LZMA_STATUS_NOT_SPECIFIED;
+
+ while (p->remainLen != kMatchSpecLenStart)
+ {
+ int checkEndMarkNow;
+
+ if (p->needFlush != 0)
+ {
+ for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--)
+ p->tempBuf[p->tempBufSize++] = *src++;
+ if (p->tempBufSize < RC_INIT_SIZE)
+ {
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (p->tempBuf[0] != 0)
+ return SZ_ERROR_DATA;
+
+ LzmaDec_InitRc(p, p->tempBuf);
+ p->tempBufSize = 0;
+ }
+
+ checkEndMarkNow = 0;
+ if (p->dicPos >= dicLimit)
+ {
+ if (p->remainLen == 0 && p->code == 0)
+ {
+ *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK;
+ return SZ_OK;
+ }
+ if (finishMode == LZMA_FINISH_ANY)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_OK;
+ }
+ if (p->remainLen != 0)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ checkEndMarkNow = 1;
+ }
+
+ if (p->needInitState)
+ LzmaDec_InitStateReal(p);
+
+ if (p->tempBufSize == 0)
+ {
+ SizeT processed;
+ const Byte *bufLimit;
+ if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+ {
+ int dummyRes = LzmaDec_TryDummy(p, src, inSize);
+ if (dummyRes == DUMMY_ERROR)
+ {
+ memcpy(p->tempBuf, src, inSize);
+ p->tempBufSize = (unsigned)inSize;
+ (*srcLen) += inSize;
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ bufLimit = src;
+ }
+ else
+ bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX;
+ p->buf = src;
+ if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0)
+ return SZ_ERROR_DATA;
+ processed = (SizeT)(p->buf - src);
+ (*srcLen) += processed;
+ src += processed;
+ inSize -= processed;
+ }
+ else
+ {
+ unsigned rem = p->tempBufSize, lookAhead = 0;
+ while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize)
+ p->tempBuf[rem++] = src[lookAhead++];
+ p->tempBufSize = rem;
+ if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+ {
+ int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem);
+ if (dummyRes == DUMMY_ERROR)
+ {
+ (*srcLen) += lookAhead;
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ }
+ p->buf = p->tempBuf;
+ if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0)
+ return SZ_ERROR_DATA;
+ lookAhead -= (rem - (unsigned)(p->buf - p->tempBuf));
+ (*srcLen) += lookAhead;
+ src += lookAhead;
+ inSize -= lookAhead;
+ p->tempBufSize = 0;
+ }
+ }
+ if (p->code == 0)
+ *status = LZMA_STATUS_FINISHED_WITH_MARK;
+ return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA;
+}
+
+SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status)
+{
+ SizeT outSize = *destLen;
+ SizeT inSize = *srcLen;
+ *srcLen = *destLen = 0;
+ for (;;)
+ {
+ SizeT inSizeCur = inSize, outSizeCur, dicPos;
+ ELzmaFinishMode curFinishMode;
+ SRes res;
+ if (p->dicPos == p->dicBufSize)
+ p->dicPos = 0;
+ dicPos = p->dicPos;
+ if (outSize > p->dicBufSize - dicPos)
+ {
+ outSizeCur = p->dicBufSize;
+ curFinishMode = LZMA_FINISH_ANY;
+ }
+ else
+ {
+ outSizeCur = dicPos + outSize;
+ curFinishMode = finishMode;
+ }
+
+ res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status);
+ src += inSizeCur;
+ inSize -= inSizeCur;
+ *srcLen += inSizeCur;
+ outSizeCur = p->dicPos - dicPos;
+ memcpy(dest, p->dic + dicPos, outSizeCur);
+ dest += outSizeCur;
+ outSize -= outSizeCur;
+ *destLen += outSizeCur;
+ if (res != 0)
+ return res;
+ if (outSizeCur == 0 || outSize == 0)
+ return SZ_OK;
+ }
+}
+
+void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->probs);
+ p->probs = 0;
+}
+
+static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->dic);
+ p->dic = 0;
+}
+
+void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc)
+{
+ LzmaDec_FreeProbs(p, alloc);
+ LzmaDec_FreeDict(p, alloc);
+}
+
+SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size)
+{
+ UInt32 dicSize;
+ Byte d;
+
+ if (size < LZMA_PROPS_SIZE)
+ return SZ_ERROR_UNSUPPORTED;
+ else
+ dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24);
+
+ if (dicSize < LZMA_DIC_MIN)
+ dicSize = LZMA_DIC_MIN;
+ p->dicSize = dicSize;
+
+ d = data[0];
+ if (d >= (9 * 5 * 5))
+ return SZ_ERROR_UNSUPPORTED;
+
+ p->lc = d % 9;
+ d /= 9;
+ p->pb = d / 5;
+ p->lp = d % 5;
+
+ return SZ_OK;
+}
+
+static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc)
+{
+ UInt32 numProbs = LzmaProps_GetNumProbs(propNew);
+ if (p->probs == 0 || numProbs != p->numProbs)
+ {
+ LzmaDec_FreeProbs(p, alloc);
+ p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb));
+ p->numProbs = numProbs;
+ if (p->probs == 0)
+ return SZ_ERROR_MEM;
+ }
+ return SZ_OK;
+}
+
+SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
+{
+ CLzmaProps propNew;
+ RINOK(LzmaProps_Decode(&propNew, props, propsSize));
+ RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
+ p->prop = propNew;
+ return SZ_OK;
+}
+
+SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
+{
+ CLzmaProps propNew;
+ SizeT dicBufSize;
+ RINOK(LzmaProps_Decode(&propNew, props, propsSize));
+ RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
+ dicBufSize = propNew.dicSize;
+ if (p->dic == 0 || dicBufSize != p->dicBufSize)
+ {
+ LzmaDec_FreeDict(p, alloc);
+ p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize);
+ if (p->dic == 0)
+ {
+ LzmaDec_FreeProbs(p, alloc);
+ return SZ_ERROR_MEM;
+ }
+ }
+ p->dicBufSize = dicBufSize;
+ p->prop = propNew;
+ return SZ_OK;
+}
+
+SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
+ const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
+ ELzmaStatus *status, ISzAlloc *alloc)
+{
+ CLzmaDec p;
+ SRes res;
+ SizeT inSize = *srcLen;
+ SizeT outSize = *destLen;
+ *srcLen = *destLen = 0;
+ if (inSize < RC_INIT_SIZE)
+ return SZ_ERROR_INPUT_EOF;
+
+ LzmaDec_Construct(&p);
+ res = LzmaDec_AllocateProbs(&p, propData, propSize, alloc);
+ if (res != 0)
+ return res;
+ p.dic = dest;
+ p.dicBufSize = outSize;
+
+ LzmaDec_Init(&p);
+
+ *srcLen = inSize;
+ res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status);
+
+ if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT)
+ res = SZ_ERROR_INPUT_EOF;
+
+ (*destLen) = p.dicPos;
+ LzmaDec_FreeProbs(&p, alloc);
+ return res;
+}
diff --git a/dep/StormLib/src/lzma/C/LzmaDec.h b/dep/StormLib/src/lzma/C/LzmaDec.h
new file mode 100644
index 000000000..bf7f084ba
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzmaDec.h
@@ -0,0 +1,231 @@
+/* LzmaDec.h -- LZMA Decoder
+2009-02-07 : Igor Pavlov : Public domain */
+
+#ifndef __LZMA_DEC_H
+#define __LZMA_DEC_H
+
+#include "Types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* #define _LZMA_PROB32 */
+/* _LZMA_PROB32 can increase the speed on some CPUs,
+ but memory usage for CLzmaDec::probs will be doubled in that case */
+
+#ifdef _LZMA_PROB32
+#define CLzmaProb UInt32
+#else
+#define CLzmaProb UInt16
+#endif
+
+
+/* ---------- LZMA Properties ---------- */
+
+#define LZMA_PROPS_SIZE 5
+
+typedef struct _CLzmaProps
+{
+ unsigned lc, lp, pb;
+ UInt32 dicSize;
+} CLzmaProps;
+
+/* LzmaProps_Decode - decodes properties
+Returns:
+ SZ_OK
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+*/
+
+SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size);
+
+
+/* ---------- LZMA Decoder state ---------- */
+
+/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case.
+ Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */
+
+#define LZMA_REQUIRED_INPUT_MAX 20
+
+typedef struct
+{
+ CLzmaProps prop;
+ CLzmaProb *probs;
+ Byte *dic;
+ const Byte *buf;
+ UInt32 range, code;
+ SizeT dicPos;
+ SizeT dicBufSize;
+ UInt32 processedPos;
+ UInt32 checkDicSize;
+ unsigned state;
+ UInt32 reps[4];
+ unsigned remainLen;
+ int needFlush;
+ int needInitState;
+ UInt32 numProbs;
+ unsigned tempBufSize;
+ Byte tempBuf[LZMA_REQUIRED_INPUT_MAX];
+} CLzmaDec;
+
+#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; }
+
+void LzmaDec_Init(CLzmaDec *p);
+
+/* There are two types of LZMA streams:
+ 0) Stream with end mark. That end mark adds about 6 bytes to compressed size.
+ 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */
+
+typedef enum
+{
+ LZMA_FINISH_ANY, /* finish at any point */
+ LZMA_FINISH_END /* block must be finished at the end */
+} ELzmaFinishMode;
+
+/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!!
+
+ You must use LZMA_FINISH_END, when you know that current output buffer
+ covers last bytes of block. In other cases you must use LZMA_FINISH_ANY.
+
+ If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK,
+ and output value of destLen will be less than output buffer size limit.
+ You can check status result also.
+
+ You can use multiple checks to test data integrity after full decompression:
+ 1) Check Result and "status" variable.
+ 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize.
+ 3) Check that output(srcLen) = compressedSize, if you know real compressedSize.
+ You must use correct finish mode in that case. */
+
+typedef enum
+{
+ LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */
+ LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
+ LZMA_STATUS_NOT_FINISHED, /* stream was not finished */
+ LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */
+} ELzmaStatus;
+
+/* ELzmaStatus is used only as output value for function call */
+
+
+/* ---------- Interfaces ---------- */
+
+/* There are 3 levels of interfaces:
+ 1) Dictionary Interface
+ 2) Buffer Interface
+ 3) One Call Interface
+ You can select any of these interfaces, but don't mix functions from different
+ groups for same object. */
+
+
+/* There are two variants to allocate state for Dictionary Interface:
+ 1) LzmaDec_Allocate / LzmaDec_Free
+ 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs
+ You can use variant 2, if you set dictionary buffer manually.
+ For Buffer Interface you must always use variant 1.
+
+LzmaDec_Allocate* can return:
+ SZ_OK
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+*/
+
+SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc);
+void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc);
+
+SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc);
+void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc);
+
+/* ---------- Dictionary Interface ---------- */
+
+/* You can use it, if you want to eliminate the overhead for data copying from
+ dictionary to some other external buffer.
+ You must work with CLzmaDec variables directly in this interface.
+
+ STEPS:
+ LzmaDec_Constr()
+ LzmaDec_Allocate()
+ for (each new stream)
+ {
+ LzmaDec_Init()
+ while (it needs more decompression)
+ {
+ LzmaDec_DecodeToDic()
+ use data from CLzmaDec::dic and update CLzmaDec::dicPos
+ }
+ }
+ LzmaDec_Free()
+*/
+
+/* LzmaDec_DecodeToDic
+
+ The decoding to internal dictionary buffer (CLzmaDec::dic).
+ You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!!
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (dicLimit).
+ LZMA_FINISH_ANY - Decode just dicLimit bytes.
+ LZMA_FINISH_END - Stream must be finished after dicLimit.
+
+Returns:
+ SZ_OK
+ status:
+ LZMA_STATUS_FINISHED_WITH_MARK
+ LZMA_STATUS_NOT_FINISHED
+ LZMA_STATUS_NEEDS_MORE_INPUT
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
+ SZ_ERROR_DATA - Data error
+*/
+
+SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
+
+
+/* ---------- Buffer Interface ---------- */
+
+/* It's zlib-like interface.
+ See LzmaDec_DecodeToDic description for information about STEPS and return results,
+ but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need
+ to work with CLzmaDec variables manually.
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (*destLen).
+ LZMA_FINISH_ANY - Decode just destLen bytes.
+ LZMA_FINISH_END - Stream must be finished after (*destLen).
+*/
+
+SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
+
+
+/* ---------- One Call Interface ---------- */
+
+/* LzmaDecode
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (*destLen).
+ LZMA_FINISH_ANY - Decode just destLen bytes.
+ LZMA_FINISH_END - Stream must be finished after (*destLen).
+
+Returns:
+ SZ_OK
+ status:
+ LZMA_STATUS_FINISHED_WITH_MARK
+ LZMA_STATUS_NOT_FINISHED
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
+ SZ_ERROR_DATA - Data error
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+ SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src).
+*/
+
+SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
+ const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode,
+ ELzmaStatus *status, ISzAlloc *alloc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dep/StormLib/src/lzma/C/LzmaEnc.c b/dep/StormLib/src/lzma/C/LzmaEnc.c
new file mode 100644
index 000000000..169d4f463
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzmaEnc.c
@@ -0,0 +1,2268 @@
+/* LzmaEnc.c -- LZMA Encoder
+2009-11-24 : Igor Pavlov : Public domain */
+
+#include
+
+/* #define SHOW_STAT */
+/* #define SHOW_STAT2 */
+
+#if defined(SHOW_STAT) || defined(SHOW_STAT2)
+#include
+#endif
+
+#include "LzmaEnc.h"
+
+#include "LzFind.h"
+#ifndef _7ZIP_ST
+#include "LzFindMt.h"
+#endif
+
+#ifdef SHOW_STAT
+static int ttt = 0;
+#endif
+
+#define kBlockSizeMax ((1 << LZMA_NUM_BLOCK_SIZE_BITS) - 1)
+
+#define kBlockSize (9 << 10)
+#define kUnpackBlockSize (1 << 18)
+#define kMatchArraySize (1 << 21)
+#define kMatchRecordMaxSize ((LZMA_MATCH_LEN_MAX * 2 + 3) * LZMA_MATCH_LEN_MAX)
+
+#define kNumMaxDirectBits (31)
+
+#define kNumTopBits 24
+#define kTopValue ((UInt32)1 << kNumTopBits)
+
+#define kNumBitModelTotalBits 11
+#define kBitModelTotal (1 << kNumBitModelTotalBits)
+#define kNumMoveBits 5
+#define kProbInitValue (kBitModelTotal >> 1)
+
+#define kNumMoveReducingBits 4
+#define kNumBitPriceShiftBits 4
+#define kBitPrice (1 << kNumBitPriceShiftBits)
+
+void LzmaEncProps_Init(CLzmaEncProps *p)
+{
+ p->level = 5;
+ p->dictSize = p->mc = 0;
+ p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1;
+ p->writeEndMark = 0;
+}
+
+void LzmaEncProps_Normalize(CLzmaEncProps *p)
+{
+ int level = p->level;
+ if (level < 0) level = 5;
+ p->level = level;
+ if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26)));
+ if (p->lc < 0) p->lc = 3;
+ if (p->lp < 0) p->lp = 0;
+ if (p->pb < 0) p->pb = 2;
+ if (p->algo < 0) p->algo = (level < 5 ? 0 : 1);
+ if (p->fb < 0) p->fb = (level < 7 ? 32 : 64);
+ if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1);
+ if (p->numHashBytes < 0) p->numHashBytes = 4;
+ if (p->mc == 0) p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1);
+ if (p->numThreads < 0)
+ p->numThreads =
+ #ifndef _7ZIP_ST
+ ((p->btMode && p->algo) ? 2 : 1);
+ #else
+ 1;
+ #endif
+}
+
+UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2)
+{
+ CLzmaEncProps props = *props2;
+ LzmaEncProps_Normalize(&props);
+ return props.dictSize;
+}
+
+/* #define LZMA_LOG_BSR */
+/* Define it for Intel's CPU */
+
+
+#ifdef LZMA_LOG_BSR
+
+#define kDicLogSizeMaxCompress 30
+
+#define BSR2_RET(pos, res) { unsigned long i; _BitScanReverse(&i, (pos)); res = (i + i) + ((pos >> (i - 1)) & 1); }
+
+UInt32 GetPosSlot1(UInt32 pos)
+{
+ UInt32 res;
+ BSR2_RET(pos, res);
+ return res;
+}
+#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
+#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); }
+
+#else
+
+#define kNumLogBits (9 + (int)sizeof(size_t) / 2)
+#define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7)
+
+void LzmaEnc_FastPosInit(Byte *g_FastPos)
+{
+ int c = 2, slotFast;
+ g_FastPos[0] = 0;
+ g_FastPos[1] = 1;
+
+ for (slotFast = 2; slotFast < kNumLogBits * 2; slotFast++)
+ {
+ UInt32 k = (1 << ((slotFast >> 1) - 1));
+ UInt32 j;
+ for (j = 0; j < k; j++, c++)
+ g_FastPos[c] = (Byte)slotFast;
+ }
+}
+
+#define BSR2_RET(pos, res) { UInt32 i = 6 + ((kNumLogBits - 1) & \
+ (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \
+ res = p->g_FastPos[pos >> i] + (i * 2); }
+/*
+#define BSR2_RET(pos, res) { res = (pos < (1 << (kNumLogBits + 6))) ? \
+ p->g_FastPos[pos >> 6] + 12 : \
+ p->g_FastPos[pos >> (6 + kNumLogBits - 1)] + (6 + (kNumLogBits - 1)) * 2; }
+*/
+
+#define GetPosSlot1(pos) p->g_FastPos[pos]
+#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
+#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos]; else BSR2_RET(pos, res); }
+
+#endif
+
+
+#define LZMA_NUM_REPS 4
+
+typedef unsigned CState;
+
+typedef struct
+{
+ UInt32 price;
+
+ CState state;
+ int prev1IsChar;
+ int prev2;
+
+ UInt32 posPrev2;
+ UInt32 backPrev2;
+
+ UInt32 posPrev;
+ UInt32 backPrev;
+ UInt32 backs[LZMA_NUM_REPS];
+} COptimal;
+
+#define kNumOpts (1 << 12)
+
+#define kNumLenToPosStates 4
+#define kNumPosSlotBits 6
+#define kDicLogSizeMin 0
+#define kDicLogSizeMax 32
+#define kDistTableSizeMax (kDicLogSizeMax * 2)
+
+
+#define kNumAlignBits 4
+#define kAlignTableSize (1 << kNumAlignBits)
+#define kAlignMask (kAlignTableSize - 1)
+
+#define kStartPosModelIndex 4
+#define kEndPosModelIndex 14
+#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex)
+
+#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
+
+#ifdef _LZMA_PROB32
+#define CLzmaProb UInt32
+#else
+#define CLzmaProb UInt16
+#endif
+
+#define LZMA_PB_MAX 4
+#define LZMA_LC_MAX 8
+#define LZMA_LP_MAX 4
+
+#define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX)
+
+
+#define kLenNumLowBits 3
+#define kLenNumLowSymbols (1 << kLenNumLowBits)
+#define kLenNumMidBits 3
+#define kLenNumMidSymbols (1 << kLenNumMidBits)
+#define kLenNumHighBits 8
+#define kLenNumHighSymbols (1 << kLenNumHighBits)
+
+#define kLenNumSymbolsTotal (kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
+
+#define LZMA_MATCH_LEN_MIN 2
+#define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1)
+
+#define kNumStates 12
+
+typedef struct
+{
+ CLzmaProb choice;
+ CLzmaProb choice2;
+ CLzmaProb low[LZMA_NUM_PB_STATES_MAX << kLenNumLowBits];
+ CLzmaProb mid[LZMA_NUM_PB_STATES_MAX << kLenNumMidBits];
+ CLzmaProb high[kLenNumHighSymbols];
+} CLenEnc;
+
+typedef struct
+{
+ CLenEnc p;
+ UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal];
+ UInt32 tableSize;
+ UInt32 counters[LZMA_NUM_PB_STATES_MAX];
+} CLenPriceEnc;
+
+typedef struct
+{
+ UInt32 range;
+ Byte cache;
+ UInt64 low;
+ UInt64 cacheSize;
+ Byte *buf;
+ Byte *bufLim;
+ Byte *bufBase;
+ ISeqOutStream *outStream;
+ UInt64 processed;
+ SRes res;
+} CRangeEnc;
+
+typedef struct
+{
+ CLzmaProb *litProbs;
+
+ CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX];
+ CLzmaProb isRep[kNumStates];
+ CLzmaProb isRepG0[kNumStates];
+ CLzmaProb isRepG1[kNumStates];
+ CLzmaProb isRepG2[kNumStates];
+ CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX];
+
+ CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits];
+ CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex];
+ CLzmaProb posAlignEncoder[1 << kNumAlignBits];
+
+ CLenPriceEnc lenEnc;
+ CLenPriceEnc repLenEnc;
+
+ UInt32 reps[LZMA_NUM_REPS];
+ UInt32 state;
+} CSaveState;
+
+typedef struct
+{
+ IMatchFinder matchFinder;
+ void *matchFinderObj;
+
+ #ifndef _7ZIP_ST
+ Bool mtMode;
+ CMatchFinderMt matchFinderMt;
+ #endif
+
+ CMatchFinder matchFinderBase;
+
+ #ifndef _7ZIP_ST
+ Byte pad[128];
+ #endif
+
+ UInt32 optimumEndIndex;
+ UInt32 optimumCurrentIndex;
+
+ UInt32 longestMatchLength;
+ UInt32 numPairs;
+ UInt32 numAvail;
+ COptimal opt[kNumOpts];
+
+ #ifndef LZMA_LOG_BSR
+ Byte g_FastPos[1 << kNumLogBits];
+ #endif
+
+ UInt32 ProbPrices[kBitModelTotal >> kNumMoveReducingBits];
+ UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1];
+ UInt32 numFastBytes;
+ UInt32 additionalOffset;
+ UInt32 reps[LZMA_NUM_REPS];
+ UInt32 state;
+
+ UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax];
+ UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances];
+ UInt32 alignPrices[kAlignTableSize];
+ UInt32 alignPriceCount;
+
+ UInt32 distTableSize;
+
+ unsigned lc, lp, pb;
+ unsigned lpMask, pbMask;
+
+ CLzmaProb *litProbs;
+
+ CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX];
+ CLzmaProb isRep[kNumStates];
+ CLzmaProb isRepG0[kNumStates];
+ CLzmaProb isRepG1[kNumStates];
+ CLzmaProb isRepG2[kNumStates];
+ CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX];
+
+ CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits];
+ CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex];
+ CLzmaProb posAlignEncoder[1 << kNumAlignBits];
+
+ CLenPriceEnc lenEnc;
+ CLenPriceEnc repLenEnc;
+
+ unsigned lclp;
+
+ Bool fastMode;
+
+ CRangeEnc rc;
+
+ Bool writeEndMark;
+ UInt64 nowPos64;
+ UInt32 matchPriceCount;
+ Bool finished;
+ Bool multiThread;
+
+ SRes result;
+ UInt32 dictSize;
+ UInt32 matchFinderCycles;
+
+ int needInit;
+
+ CSaveState saveState;
+} CLzmaEnc;
+
+void LzmaEnc_SaveState(CLzmaEncHandle pp)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ CSaveState *dest = &p->saveState;
+ int i;
+ dest->lenEnc = p->lenEnc;
+ dest->repLenEnc = p->repLenEnc;
+ dest->state = p->state;
+
+ for (i = 0; i < kNumStates; i++)
+ {
+ memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i]));
+ memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i]));
+ }
+ for (i = 0; i < kNumLenToPosStates; i++)
+ memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i]));
+ memcpy(dest->isRep, p->isRep, sizeof(p->isRep));
+ memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0));
+ memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1));
+ memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2));
+ memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders));
+ memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder));
+ memcpy(dest->reps, p->reps, sizeof(p->reps));
+ memcpy(dest->litProbs, p->litProbs, (0x300 << p->lclp) * sizeof(CLzmaProb));
+}
+
+void LzmaEnc_RestoreState(CLzmaEncHandle pp)
+{
+ CLzmaEnc *dest = (CLzmaEnc *)pp;
+ const CSaveState *p = &dest->saveState;
+ int i;
+ dest->lenEnc = p->lenEnc;
+ dest->repLenEnc = p->repLenEnc;
+ dest->state = p->state;
+
+ for (i = 0; i < kNumStates; i++)
+ {
+ memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i]));
+ memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i]));
+ }
+ for (i = 0; i < kNumLenToPosStates; i++)
+ memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i]));
+ memcpy(dest->isRep, p->isRep, sizeof(p->isRep));
+ memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0));
+ memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1));
+ memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2));
+ memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders));
+ memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder));
+ memcpy(dest->reps, p->reps, sizeof(p->reps));
+ memcpy(dest->litProbs, p->litProbs, (0x300 << dest->lclp) * sizeof(CLzmaProb));
+}
+
+SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ CLzmaEncProps props = *props2;
+ LzmaEncProps_Normalize(&props);
+
+ if (props.lc > LZMA_LC_MAX || props.lp > LZMA_LP_MAX || props.pb > LZMA_PB_MAX ||
+ props.dictSize > (1 << kDicLogSizeMaxCompress) || props.dictSize > (1 << 30))
+ return SZ_ERROR_PARAM;
+ p->dictSize = props.dictSize;
+ p->matchFinderCycles = props.mc;
+ {
+ unsigned fb = props.fb;
+ if (fb < 5)
+ fb = 5;
+ if (fb > LZMA_MATCH_LEN_MAX)
+ fb = LZMA_MATCH_LEN_MAX;
+ p->numFastBytes = fb;
+ }
+ p->lc = props.lc;
+ p->lp = props.lp;
+ p->pb = props.pb;
+ p->fastMode = (props.algo == 0);
+ p->matchFinderBase.btMode = props.btMode;
+ {
+ UInt32 numHashBytes = 4;
+ if (props.btMode)
+ {
+ if (props.numHashBytes < 2)
+ numHashBytes = 2;
+ else if (props.numHashBytes < 4)
+ numHashBytes = props.numHashBytes;
+ }
+ p->matchFinderBase.numHashBytes = numHashBytes;
+ }
+
+ p->matchFinderBase.cutValue = props.mc;
+
+ p->writeEndMark = props.writeEndMark;
+
+ #ifndef _7ZIP_ST
+ /*
+ if (newMultiThread != _multiThread)
+ {
+ ReleaseMatchFinder();
+ _multiThread = newMultiThread;
+ }
+ */
+ p->multiThread = (props.numThreads > 1);
+ #endif
+
+ return SZ_OK;
+}
+
+static const int kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5};
+static const int kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10};
+static const int kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11};
+static const int kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11};
+
+#define IsCharState(s) ((s) < 7)
+
+#define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1)
+
+#define kInfinityPrice (1 << 30)
+
+static void RangeEnc_Construct(CRangeEnc *p)
+{
+ p->outStream = 0;
+ p->bufBase = 0;
+}
+
+#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize)
+
+#define RC_BUF_SIZE (1 << 16)
+static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc)
+{
+ if (p->bufBase == 0)
+ {
+ p->bufBase = (Byte *)alloc->Alloc(alloc, RC_BUF_SIZE);
+ if (p->bufBase == 0)
+ return 0;
+ p->bufLim = p->bufBase + RC_BUF_SIZE;
+ }
+ return 1;
+}
+
+static void RangeEnc_Free(CRangeEnc *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->bufBase);
+ p->bufBase = 0;
+}
+
+static void RangeEnc_Init(CRangeEnc *p)
+{
+ /* Stream.Init(); */
+ p->low = 0;
+ p->range = 0xFFFFFFFF;
+ p->cacheSize = 1;
+ p->cache = 0;
+
+ p->buf = p->bufBase;
+
+ p->processed = 0;
+ p->res = SZ_OK;
+}
+
+static void RangeEnc_FlushStream(CRangeEnc *p)
+{
+ size_t num;
+ if (p->res != SZ_OK)
+ return;
+ num = p->buf - p->bufBase;
+ if (num != p->outStream->Write(p->outStream, p->bufBase, num))
+ p->res = SZ_ERROR_WRITE;
+ p->processed += num;
+ p->buf = p->bufBase;
+}
+
+static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p)
+{
+ if ((UInt32)p->low < (UInt32)0xFF000000 || (int)(p->low >> 32) != 0)
+ {
+ Byte temp = p->cache;
+ do
+ {
+ Byte *buf = p->buf;
+ *buf++ = (Byte)(temp + (Byte)(p->low >> 32));
+ p->buf = buf;
+ if (buf == p->bufLim)
+ RangeEnc_FlushStream(p);
+ temp = 0xFF;
+ }
+ while (--p->cacheSize != 0);
+ p->cache = (Byte)((UInt32)p->low >> 24);
+ }
+ p->cacheSize++;
+ p->low = (UInt32)p->low << 8;
+}
+
+static void RangeEnc_FlushData(CRangeEnc *p)
+{
+ int i;
+ for (i = 0; i < 5; i++)
+ RangeEnc_ShiftLow(p);
+}
+
+static void RangeEnc_EncodeDirectBits(CRangeEnc *p, UInt32 value, int numBits)
+{
+ do
+ {
+ p->range >>= 1;
+ p->low += p->range & (0 - ((value >> --numBits) & 1));
+ if (p->range < kTopValue)
+ {
+ p->range <<= 8;
+ RangeEnc_ShiftLow(p);
+ }
+ }
+ while (numBits != 0);
+}
+
+static void RangeEnc_EncodeBit(CRangeEnc *p, CLzmaProb *prob, UInt32 symbol)
+{
+ UInt32 ttt = *prob;
+ UInt32 newBound = (p->range >> kNumBitModelTotalBits) * ttt;
+ if (symbol == 0)
+ {
+ p->range = newBound;
+ ttt += (kBitModelTotal - ttt) >> kNumMoveBits;
+ }
+ else
+ {
+ p->low += newBound;
+ p->range -= newBound;
+ ttt -= ttt >> kNumMoveBits;
+ }
+ *prob = (CLzmaProb)ttt;
+ if (p->range < kTopValue)
+ {
+ p->range <<= 8;
+ RangeEnc_ShiftLow(p);
+ }
+}
+
+static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol)
+{
+ symbol |= 0x100;
+ do
+ {
+ RangeEnc_EncodeBit(p, probs + (symbol >> 8), (symbol >> 7) & 1);
+ symbol <<= 1;
+ }
+ while (symbol < 0x10000);
+}
+
+static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol, UInt32 matchByte)
+{
+ UInt32 offs = 0x100;
+ symbol |= 0x100;
+ do
+ {
+ matchByte <<= 1;
+ RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (symbol >> 8)), (symbol >> 7) & 1);
+ symbol <<= 1;
+ offs &= ~(matchByte ^ symbol);
+ }
+ while (symbol < 0x10000);
+}
+
+void LzmaEnc_InitPriceTables(UInt32 *ProbPrices)
+{
+ UInt32 i;
+ for (i = (1 << kNumMoveReducingBits) / 2; i < kBitModelTotal; i += (1 << kNumMoveReducingBits))
+ {
+ const int kCyclesBits = kNumBitPriceShiftBits;
+ UInt32 w = i;
+ UInt32 bitCount = 0;
+ int j;
+ for (j = 0; j < kCyclesBits; j++)
+ {
+ w = w * w;
+ bitCount <<= 1;
+ while (w >= ((UInt32)1 << 16))
+ {
+ w >>= 1;
+ bitCount++;
+ }
+ }
+ ProbPrices[i >> kNumMoveReducingBits] = ((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount);
+ }
+}
+
+
+#define GET_PRICE(prob, symbol) \
+ p->ProbPrices[((prob) ^ (((-(int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits];
+
+#define GET_PRICEa(prob, symbol) \
+ ProbPrices[((prob) ^ ((-((int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits];
+
+#define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits]
+#define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits]
+
+#define GET_PRICE_0a(prob) ProbPrices[(prob) >> kNumMoveReducingBits]
+#define GET_PRICE_1a(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits]
+
+static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 symbol, UInt32 *ProbPrices)
+{
+ UInt32 price = 0;
+ symbol |= 0x100;
+ do
+ {
+ price += GET_PRICEa(probs[symbol >> 8], (symbol >> 7) & 1);
+ symbol <<= 1;
+ }
+ while (symbol < 0x10000);
+ return price;
+}
+
+static UInt32 LitEnc_GetPriceMatched(const CLzmaProb *probs, UInt32 symbol, UInt32 matchByte, UInt32 *ProbPrices)
+{
+ UInt32 price = 0;
+ UInt32 offs = 0x100;
+ symbol |= 0x100;
+ do
+ {
+ matchByte <<= 1;
+ price += GET_PRICEa(probs[offs + (matchByte & offs) + (symbol >> 8)], (symbol >> 7) & 1);
+ symbol <<= 1;
+ offs &= ~(matchByte ^ symbol);
+ }
+ while (symbol < 0x10000);
+ return price;
+}
+
+
+static void RcTree_Encode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol)
+{
+ UInt32 m = 1;
+ int i;
+ for (i = numBitLevels; i != 0;)
+ {
+ UInt32 bit;
+ i--;
+ bit = (symbol >> i) & 1;
+ RangeEnc_EncodeBit(rc, probs + m, bit);
+ m = (m << 1) | bit;
+ }
+}
+
+static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol)
+{
+ UInt32 m = 1;
+ int i;
+ for (i = 0; i < numBitLevels; i++)
+ {
+ UInt32 bit = symbol & 1;
+ RangeEnc_EncodeBit(rc, probs + m, bit);
+ m = (m << 1) | bit;
+ symbol >>= 1;
+ }
+}
+
+static UInt32 RcTree_GetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices)
+{
+ UInt32 price = 0;
+ symbol |= (1 << numBitLevels);
+ while (symbol != 1)
+ {
+ price += GET_PRICEa(probs[symbol >> 1], symbol & 1);
+ symbol >>= 1;
+ }
+ return price;
+}
+
+static UInt32 RcTree_ReverseGetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices)
+{
+ UInt32 price = 0;
+ UInt32 m = 1;
+ int i;
+ for (i = numBitLevels; i != 0; i--)
+ {
+ UInt32 bit = symbol & 1;
+ symbol >>= 1;
+ price += GET_PRICEa(probs[m], bit);
+ m = (m << 1) | bit;
+ }
+ return price;
+}
+
+
+static void LenEnc_Init(CLenEnc *p)
+{
+ unsigned i;
+ p->choice = p->choice2 = kProbInitValue;
+ for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumLowBits); i++)
+ p->low[i] = kProbInitValue;
+ for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumMidBits); i++)
+ p->mid[i] = kProbInitValue;
+ for (i = 0; i < kLenNumHighSymbols; i++)
+ p->high[i] = kProbInitValue;
+}
+
+static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState)
+{
+ if (symbol < kLenNumLowSymbols)
+ {
+ RangeEnc_EncodeBit(rc, &p->choice, 0);
+ RcTree_Encode(rc, p->low + (posState << kLenNumLowBits), kLenNumLowBits, symbol);
+ }
+ else
+ {
+ RangeEnc_EncodeBit(rc, &p->choice, 1);
+ if (symbol < kLenNumLowSymbols + kLenNumMidSymbols)
+ {
+ RangeEnc_EncodeBit(rc, &p->choice2, 0);
+ RcTree_Encode(rc, p->mid + (posState << kLenNumMidBits), kLenNumMidBits, symbol - kLenNumLowSymbols);
+ }
+ else
+ {
+ RangeEnc_EncodeBit(rc, &p->choice2, 1);
+ RcTree_Encode(rc, p->high, kLenNumHighBits, symbol - kLenNumLowSymbols - kLenNumMidSymbols);
+ }
+ }
+}
+
+static void LenEnc_SetPrices(CLenEnc *p, UInt32 posState, UInt32 numSymbols, UInt32 *prices, UInt32 *ProbPrices)
+{
+ UInt32 a0 = GET_PRICE_0a(p->choice);
+ UInt32 a1 = GET_PRICE_1a(p->choice);
+ UInt32 b0 = a1 + GET_PRICE_0a(p->choice2);
+ UInt32 b1 = a1 + GET_PRICE_1a(p->choice2);
+ UInt32 i = 0;
+ for (i = 0; i < kLenNumLowSymbols; i++)
+ {
+ if (i >= numSymbols)
+ return;
+ prices[i] = a0 + RcTree_GetPrice(p->low + (posState << kLenNumLowBits), kLenNumLowBits, i, ProbPrices);
+ }
+ for (; i < kLenNumLowSymbols + kLenNumMidSymbols; i++)
+ {
+ if (i >= numSymbols)
+ return;
+ prices[i] = b0 + RcTree_GetPrice(p->mid + (posState << kLenNumMidBits), kLenNumMidBits, i - kLenNumLowSymbols, ProbPrices);
+ }
+ for (; i < numSymbols; i++)
+ prices[i] = b1 + RcTree_GetPrice(p->high, kLenNumHighBits, i - kLenNumLowSymbols - kLenNumMidSymbols, ProbPrices);
+}
+
+static void MY_FAST_CALL LenPriceEnc_UpdateTable(CLenPriceEnc *p, UInt32 posState, UInt32 *ProbPrices)
+{
+ LenEnc_SetPrices(&p->p, posState, p->tableSize, p->prices[posState], ProbPrices);
+ p->counters[posState] = p->tableSize;
+}
+
+static void LenPriceEnc_UpdateTables(CLenPriceEnc *p, UInt32 numPosStates, UInt32 *ProbPrices)
+{
+ UInt32 posState;
+ for (posState = 0; posState < numPosStates; posState++)
+ LenPriceEnc_UpdateTable(p, posState, ProbPrices);
+}
+
+static void LenEnc_Encode2(CLenPriceEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState, Bool updatePrice, UInt32 *ProbPrices)
+{
+ LenEnc_Encode(&p->p, rc, symbol, posState);
+ if (updatePrice)
+ if (--p->counters[posState] == 0)
+ LenPriceEnc_UpdateTable(p, posState, ProbPrices);
+}
+
+
+
+
+static void MovePos(CLzmaEnc *p, UInt32 num)
+{
+ #ifdef SHOW_STAT
+ ttt += num;
+ printf("\n MovePos %d", num);
+ #endif
+ if (num != 0)
+ {
+ p->additionalOffset += num;
+ p->matchFinder.Skip(p->matchFinderObj, num);
+ }
+}
+
+static UInt32 ReadMatchDistances(CLzmaEnc *p, UInt32 *numDistancePairsRes)
+{
+ UInt32 lenRes = 0, numPairs;
+ p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
+ numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches);
+ #ifdef SHOW_STAT
+ printf("\n i = %d numPairs = %d ", ttt, numPairs / 2);
+ ttt++;
+ {
+ UInt32 i;
+ for (i = 0; i < numPairs; i += 2)
+ printf("%2d %6d | ", p->matches[i], p->matches[i + 1]);
+ }
+ #endif
+ if (numPairs > 0)
+ {
+ lenRes = p->matches[numPairs - 2];
+ if (lenRes == p->numFastBytes)
+ {
+ const Byte *pby = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
+ UInt32 distance = p->matches[numPairs - 1] + 1;
+ UInt32 numAvail = p->numAvail;
+ if (numAvail > LZMA_MATCH_LEN_MAX)
+ numAvail = LZMA_MATCH_LEN_MAX;
+ {
+ const Byte *pby2 = pby - distance;
+ for (; lenRes < numAvail && pby[lenRes] == pby2[lenRes]; lenRes++);
+ }
+ }
+ }
+ p->additionalOffset++;
+ *numDistancePairsRes = numPairs;
+ return lenRes;
+}
+
+
+#define MakeAsChar(p) (p)->backPrev = (UInt32)(-1); (p)->prev1IsChar = False;
+#define MakeAsShortRep(p) (p)->backPrev = 0; (p)->prev1IsChar = False;
+#define IsShortRep(p) ((p)->backPrev == 0)
+
+static UInt32 GetRepLen1Price(CLzmaEnc *p, UInt32 state, UInt32 posState)
+{
+ return
+ GET_PRICE_0(p->isRepG0[state]) +
+ GET_PRICE_0(p->isRep0Long[state][posState]);
+}
+
+static UInt32 GetPureRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 state, UInt32 posState)
+{
+ UInt32 price;
+ if (repIndex == 0)
+ {
+ price = GET_PRICE_0(p->isRepG0[state]);
+ price += GET_PRICE_1(p->isRep0Long[state][posState]);
+ }
+ else
+ {
+ price = GET_PRICE_1(p->isRepG0[state]);
+ if (repIndex == 1)
+ price += GET_PRICE_0(p->isRepG1[state]);
+ else
+ {
+ price += GET_PRICE_1(p->isRepG1[state]);
+ price += GET_PRICE(p->isRepG2[state], repIndex - 2);
+ }
+ }
+ return price;
+}
+
+static UInt32 GetRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 len, UInt32 state, UInt32 posState)
+{
+ return p->repLenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN] +
+ GetPureRepPrice(p, repIndex, state, posState);
+}
+
+static UInt32 Backward(CLzmaEnc *p, UInt32 *backRes, UInt32 cur)
+{
+ UInt32 posMem = p->opt[cur].posPrev;
+ UInt32 backMem = p->opt[cur].backPrev;
+ p->optimumEndIndex = cur;
+ do
+ {
+ if (p->opt[cur].prev1IsChar)
+ {
+ MakeAsChar(&p->opt[posMem])
+ p->opt[posMem].posPrev = posMem - 1;
+ if (p->opt[cur].prev2)
+ {
+ p->opt[posMem - 1].prev1IsChar = False;
+ p->opt[posMem - 1].posPrev = p->opt[cur].posPrev2;
+ p->opt[posMem - 1].backPrev = p->opt[cur].backPrev2;
+ }
+ }
+ {
+ UInt32 posPrev = posMem;
+ UInt32 backCur = backMem;
+
+ backMem = p->opt[posPrev].backPrev;
+ posMem = p->opt[posPrev].posPrev;
+
+ p->opt[posPrev].backPrev = backCur;
+ p->opt[posPrev].posPrev = cur;
+ cur = posPrev;
+ }
+ }
+ while (cur != 0);
+ *backRes = p->opt[0].backPrev;
+ p->optimumCurrentIndex = p->opt[0].posPrev;
+ return p->optimumCurrentIndex;
+}
+
+#define LIT_PROBS(pos, prevByte) (p->litProbs + ((((pos) & p->lpMask) << p->lc) + ((prevByte) >> (8 - p->lc))) * 0x300)
+
+static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes)
+{
+ UInt32 numAvail, mainLen, numPairs, repMaxIndex, i, posState, lenEnd, len, cur;
+ UInt32 matchPrice, repMatchPrice, normalMatchPrice;
+ UInt32 reps[LZMA_NUM_REPS], repLens[LZMA_NUM_REPS];
+ UInt32 *matches;
+ const Byte *data;
+ Byte curByte, matchByte;
+ if (p->optimumEndIndex != p->optimumCurrentIndex)
+ {
+ const COptimal *opt = &p->opt[p->optimumCurrentIndex];
+ UInt32 lenRes = opt->posPrev - p->optimumCurrentIndex;
+ *backRes = opt->backPrev;
+ p->optimumCurrentIndex = opt->posPrev;
+ return lenRes;
+ }
+ p->optimumCurrentIndex = p->optimumEndIndex = 0;
+
+ if (p->additionalOffset == 0)
+ mainLen = ReadMatchDistances(p, &numPairs);
+ else
+ {
+ mainLen = p->longestMatchLength;
+ numPairs = p->numPairs;
+ }
+
+ numAvail = p->numAvail;
+ if (numAvail < 2)
+ {
+ *backRes = (UInt32)(-1);
+ return 1;
+ }
+ if (numAvail > LZMA_MATCH_LEN_MAX)
+ numAvail = LZMA_MATCH_LEN_MAX;
+
+ data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
+ repMaxIndex = 0;
+ for (i = 0; i < LZMA_NUM_REPS; i++)
+ {
+ UInt32 lenTest;
+ const Byte *data2;
+ reps[i] = p->reps[i];
+ data2 = data - (reps[i] + 1);
+ if (data[0] != data2[0] || data[1] != data2[1])
+ {
+ repLens[i] = 0;
+ continue;
+ }
+ for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++);
+ repLens[i] = lenTest;
+ if (lenTest > repLens[repMaxIndex])
+ repMaxIndex = i;
+ }
+ if (repLens[repMaxIndex] >= p->numFastBytes)
+ {
+ UInt32 lenRes;
+ *backRes = repMaxIndex;
+ lenRes = repLens[repMaxIndex];
+ MovePos(p, lenRes - 1);
+ return lenRes;
+ }
+
+ matches = p->matches;
+ if (mainLen >= p->numFastBytes)
+ {
+ *backRes = matches[numPairs - 1] + LZMA_NUM_REPS;
+ MovePos(p, mainLen - 1);
+ return mainLen;
+ }
+ curByte = *data;
+ matchByte = *(data - (reps[0] + 1));
+
+ if (mainLen < 2 && curByte != matchByte && repLens[repMaxIndex] < 2)
+ {
+ *backRes = (UInt32)-1;
+ return 1;
+ }
+
+ p->opt[0].state = (CState)p->state;
+
+ posState = (position & p->pbMask);
+
+ {
+ const CLzmaProb *probs = LIT_PROBS(position, *(data - 1));
+ p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) +
+ (!IsCharState(p->state) ?
+ LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) :
+ LitEnc_GetPrice(probs, curByte, p->ProbPrices));
+ }
+
+ MakeAsChar(&p->opt[1]);
+
+ matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]);
+ repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]);
+
+ if (matchByte == curByte)
+ {
+ UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, p->state, posState);
+ if (shortRepPrice < p->opt[1].price)
+ {
+ p->opt[1].price = shortRepPrice;
+ MakeAsShortRep(&p->opt[1]);
+ }
+ }
+ lenEnd = ((mainLen >= repLens[repMaxIndex]) ? mainLen : repLens[repMaxIndex]);
+
+ if (lenEnd < 2)
+ {
+ *backRes = p->opt[1].backPrev;
+ return 1;
+ }
+
+ p->opt[1].posPrev = 0;
+ for (i = 0; i < LZMA_NUM_REPS; i++)
+ p->opt[0].backs[i] = reps[i];
+
+ len = lenEnd;
+ do
+ p->opt[len--].price = kInfinityPrice;
+ while (len >= 2);
+
+ for (i = 0; i < LZMA_NUM_REPS; i++)
+ {
+ UInt32 repLen = repLens[i];
+ UInt32 price;
+ if (repLen < 2)
+ continue;
+ price = repMatchPrice + GetPureRepPrice(p, i, p->state, posState);
+ do
+ {
+ UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][repLen - 2];
+ COptimal *opt = &p->opt[repLen];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = 0;
+ opt->backPrev = i;
+ opt->prev1IsChar = False;
+ }
+ }
+ while (--repLen >= 2);
+ }
+
+ normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]);
+
+ len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2);
+ if (len <= mainLen)
+ {
+ UInt32 offs = 0;
+ while (len > matches[offs])
+ offs += 2;
+ for (; ; len++)
+ {
+ COptimal *opt;
+ UInt32 distance = matches[offs + 1];
+
+ UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN];
+ UInt32 lenToPosState = GetLenToPosState(len);
+ if (distance < kNumFullDistances)
+ curAndLenPrice += p->distancesPrices[lenToPosState][distance];
+ else
+ {
+ UInt32 slot;
+ GetPosSlot2(distance, slot);
+ curAndLenPrice += p->alignPrices[distance & kAlignMask] + p->posSlotPrices[lenToPosState][slot];
+ }
+ opt = &p->opt[len];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = 0;
+ opt->backPrev = distance + LZMA_NUM_REPS;
+ opt->prev1IsChar = False;
+ }
+ if (len == matches[offs])
+ {
+ offs += 2;
+ if (offs == numPairs)
+ break;
+ }
+ }
+ }
+
+ cur = 0;
+
+ #ifdef SHOW_STAT2
+ if (position >= 0)
+ {
+ unsigned i;
+ printf("\n pos = %4X", position);
+ for (i = cur; i <= lenEnd; i++)
+ printf("\nprice[%4X] = %d", position - cur + i, p->opt[i].price);
+ }
+ #endif
+
+ for (;;)
+ {
+ UInt32 numAvailFull, newLen, numPairs, posPrev, state, posState, startLen;
+ UInt32 curPrice, curAnd1Price, matchPrice, repMatchPrice;
+ Bool nextIsChar;
+ Byte curByte, matchByte;
+ const Byte *data;
+ COptimal *curOpt;
+ COptimal *nextOpt;
+
+ cur++;
+ if (cur == lenEnd)
+ return Backward(p, backRes, cur);
+
+ newLen = ReadMatchDistances(p, &numPairs);
+ if (newLen >= p->numFastBytes)
+ {
+ p->numPairs = numPairs;
+ p->longestMatchLength = newLen;
+ return Backward(p, backRes, cur);
+ }
+ position++;
+ curOpt = &p->opt[cur];
+ posPrev = curOpt->posPrev;
+ if (curOpt->prev1IsChar)
+ {
+ posPrev--;
+ if (curOpt->prev2)
+ {
+ state = p->opt[curOpt->posPrev2].state;
+ if (curOpt->backPrev2 < LZMA_NUM_REPS)
+ state = kRepNextStates[state];
+ else
+ state = kMatchNextStates[state];
+ }
+ else
+ state = p->opt[posPrev].state;
+ state = kLiteralNextStates[state];
+ }
+ else
+ state = p->opt[posPrev].state;
+ if (posPrev == cur - 1)
+ {
+ if (IsShortRep(curOpt))
+ state = kShortRepNextStates[state];
+ else
+ state = kLiteralNextStates[state];
+ }
+ else
+ {
+ UInt32 pos;
+ const COptimal *prevOpt;
+ if (curOpt->prev1IsChar && curOpt->prev2)
+ {
+ posPrev = curOpt->posPrev2;
+ pos = curOpt->backPrev2;
+ state = kRepNextStates[state];
+ }
+ else
+ {
+ pos = curOpt->backPrev;
+ if (pos < LZMA_NUM_REPS)
+ state = kRepNextStates[state];
+ else
+ state = kMatchNextStates[state];
+ }
+ prevOpt = &p->opt[posPrev];
+ if (pos < LZMA_NUM_REPS)
+ {
+ UInt32 i;
+ reps[0] = prevOpt->backs[pos];
+ for (i = 1; i <= pos; i++)
+ reps[i] = prevOpt->backs[i - 1];
+ for (; i < LZMA_NUM_REPS; i++)
+ reps[i] = prevOpt->backs[i];
+ }
+ else
+ {
+ UInt32 i;
+ reps[0] = (pos - LZMA_NUM_REPS);
+ for (i = 1; i < LZMA_NUM_REPS; i++)
+ reps[i] = prevOpt->backs[i - 1];
+ }
+ }
+ curOpt->state = (CState)state;
+
+ curOpt->backs[0] = reps[0];
+ curOpt->backs[1] = reps[1];
+ curOpt->backs[2] = reps[2];
+ curOpt->backs[3] = reps[3];
+
+ curPrice = curOpt->price;
+ nextIsChar = False;
+ data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
+ curByte = *data;
+ matchByte = *(data - (reps[0] + 1));
+
+ posState = (position & p->pbMask);
+
+ curAnd1Price = curPrice + GET_PRICE_0(p->isMatch[state][posState]);
+ {
+ const CLzmaProb *probs = LIT_PROBS(position, *(data - 1));
+ curAnd1Price +=
+ (!IsCharState(state) ?
+ LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) :
+ LitEnc_GetPrice(probs, curByte, p->ProbPrices));
+ }
+
+ nextOpt = &p->opt[cur + 1];
+
+ if (curAnd1Price < nextOpt->price)
+ {
+ nextOpt->price = curAnd1Price;
+ nextOpt->posPrev = cur;
+ MakeAsChar(nextOpt);
+ nextIsChar = True;
+ }
+
+ matchPrice = curPrice + GET_PRICE_1(p->isMatch[state][posState]);
+ repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]);
+
+ if (matchByte == curByte && !(nextOpt->posPrev < cur && nextOpt->backPrev == 0))
+ {
+ UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, state, posState);
+ if (shortRepPrice <= nextOpt->price)
+ {
+ nextOpt->price = shortRepPrice;
+ nextOpt->posPrev = cur;
+ MakeAsShortRep(nextOpt);
+ nextIsChar = True;
+ }
+ }
+ numAvailFull = p->numAvail;
+ {
+ UInt32 temp = kNumOpts - 1 - cur;
+ if (temp < numAvailFull)
+ numAvailFull = temp;
+ }
+
+ if (numAvailFull < 2)
+ continue;
+ numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes);
+
+ if (!nextIsChar && matchByte != curByte) /* speed optimization */
+ {
+ /* try Literal + rep0 */
+ UInt32 temp;
+ UInt32 lenTest2;
+ const Byte *data2 = data - (reps[0] + 1);
+ UInt32 limit = p->numFastBytes + 1;
+ if (limit > numAvailFull)
+ limit = numAvailFull;
+
+ for (temp = 1; temp < limit && data[temp] == data2[temp]; temp++);
+ lenTest2 = temp - 1;
+ if (lenTest2 >= 2)
+ {
+ UInt32 state2 = kLiteralNextStates[state];
+ UInt32 posStateNext = (position + 1) & p->pbMask;
+ UInt32 nextRepMatchPrice = curAnd1Price +
+ GET_PRICE_1(p->isMatch[state2][posStateNext]) +
+ GET_PRICE_1(p->isRep[state2]);
+ /* for (; lenTest2 >= 2; lenTest2--) */
+ {
+ UInt32 curAndLenPrice;
+ COptimal *opt;
+ UInt32 offset = cur + 1 + lenTest2;
+ while (lenEnd < offset)
+ p->opt[++lenEnd].price = kInfinityPrice;
+ curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
+ opt = &p->opt[offset];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = cur + 1;
+ opt->backPrev = 0;
+ opt->prev1IsChar = True;
+ opt->prev2 = False;
+ }
+ }
+ }
+ }
+
+ startLen = 2; /* speed optimization */
+ {
+ UInt32 repIndex;
+ for (repIndex = 0; repIndex < LZMA_NUM_REPS; repIndex++)
+ {
+ UInt32 lenTest;
+ UInt32 lenTestTemp;
+ UInt32 price;
+ const Byte *data2 = data - (reps[repIndex] + 1);
+ if (data[0] != data2[0] || data[1] != data2[1])
+ continue;
+ for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++);
+ while (lenEnd < cur + lenTest)
+ p->opt[++lenEnd].price = kInfinityPrice;
+ lenTestTemp = lenTest;
+ price = repMatchPrice + GetPureRepPrice(p, repIndex, state, posState);
+ do
+ {
+ UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][lenTest - 2];
+ COptimal *opt = &p->opt[cur + lenTest];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = cur;
+ opt->backPrev = repIndex;
+ opt->prev1IsChar = False;
+ }
+ }
+ while (--lenTest >= 2);
+ lenTest = lenTestTemp;
+
+ if (repIndex == 0)
+ startLen = lenTest + 1;
+
+ /* if (_maxMode) */
+ {
+ UInt32 lenTest2 = lenTest + 1;
+ UInt32 limit = lenTest2 + p->numFastBytes;
+ UInt32 nextRepMatchPrice;
+ if (limit > numAvailFull)
+ limit = numAvailFull;
+ for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++);
+ lenTest2 -= lenTest + 1;
+ if (lenTest2 >= 2)
+ {
+ UInt32 state2 = kRepNextStates[state];
+ UInt32 posStateNext = (position + lenTest) & p->pbMask;
+ UInt32 curAndLenCharPrice =
+ price + p->repLenEnc.prices[posState][lenTest - 2] +
+ GET_PRICE_0(p->isMatch[state2][posStateNext]) +
+ LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]),
+ data[lenTest], data2[lenTest], p->ProbPrices);
+ state2 = kLiteralNextStates[state2];
+ posStateNext = (position + lenTest + 1) & p->pbMask;
+ nextRepMatchPrice = curAndLenCharPrice +
+ GET_PRICE_1(p->isMatch[state2][posStateNext]) +
+ GET_PRICE_1(p->isRep[state2]);
+
+ /* for (; lenTest2 >= 2; lenTest2--) */
+ {
+ UInt32 curAndLenPrice;
+ COptimal *opt;
+ UInt32 offset = cur + lenTest + 1 + lenTest2;
+ while (lenEnd < offset)
+ p->opt[++lenEnd].price = kInfinityPrice;
+ curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
+ opt = &p->opt[offset];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = cur + lenTest + 1;
+ opt->backPrev = 0;
+ opt->prev1IsChar = True;
+ opt->prev2 = True;
+ opt->posPrev2 = cur;
+ opt->backPrev2 = repIndex;
+ }
+ }
+ }
+ }
+ }
+ }
+ /* for (UInt32 lenTest = 2; lenTest <= newLen; lenTest++) */
+ if (newLen > numAvail)
+ {
+ newLen = numAvail;
+ for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2);
+ matches[numPairs] = newLen;
+ numPairs += 2;
+ }
+ if (newLen >= startLen)
+ {
+ UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]);
+ UInt32 offs, curBack, posSlot;
+ UInt32 lenTest;
+ while (lenEnd < cur + newLen)
+ p->opt[++lenEnd].price = kInfinityPrice;
+
+ offs = 0;
+ while (startLen > matches[offs])
+ offs += 2;
+ curBack = matches[offs + 1];
+ GetPosSlot2(curBack, posSlot);
+ for (lenTest = /*2*/ startLen; ; lenTest++)
+ {
+ UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][lenTest - LZMA_MATCH_LEN_MIN];
+ UInt32 lenToPosState = GetLenToPosState(lenTest);
+ COptimal *opt;
+ if (curBack < kNumFullDistances)
+ curAndLenPrice += p->distancesPrices[lenToPosState][curBack];
+ else
+ curAndLenPrice += p->posSlotPrices[lenToPosState][posSlot] + p->alignPrices[curBack & kAlignMask];
+
+ opt = &p->opt[cur + lenTest];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = cur;
+ opt->backPrev = curBack + LZMA_NUM_REPS;
+ opt->prev1IsChar = False;
+ }
+
+ if (/*_maxMode && */lenTest == matches[offs])
+ {
+ /* Try Match + Literal + Rep0 */
+ const Byte *data2 = data - (curBack + 1);
+ UInt32 lenTest2 = lenTest + 1;
+ UInt32 limit = lenTest2 + p->numFastBytes;
+ UInt32 nextRepMatchPrice;
+ if (limit > numAvailFull)
+ limit = numAvailFull;
+ for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++);
+ lenTest2 -= lenTest + 1;
+ if (lenTest2 >= 2)
+ {
+ UInt32 state2 = kMatchNextStates[state];
+ UInt32 posStateNext = (position + lenTest) & p->pbMask;
+ UInt32 curAndLenCharPrice = curAndLenPrice +
+ GET_PRICE_0(p->isMatch[state2][posStateNext]) +
+ LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]),
+ data[lenTest], data2[lenTest], p->ProbPrices);
+ state2 = kLiteralNextStates[state2];
+ posStateNext = (posStateNext + 1) & p->pbMask;
+ nextRepMatchPrice = curAndLenCharPrice +
+ GET_PRICE_1(p->isMatch[state2][posStateNext]) +
+ GET_PRICE_1(p->isRep[state2]);
+
+ /* for (; lenTest2 >= 2; lenTest2--) */
+ {
+ UInt32 offset = cur + lenTest + 1 + lenTest2;
+ UInt32 curAndLenPrice;
+ COptimal *opt;
+ while (lenEnd < offset)
+ p->opt[++lenEnd].price = kInfinityPrice;
+ curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext);
+ opt = &p->opt[offset];
+ if (curAndLenPrice < opt->price)
+ {
+ opt->price = curAndLenPrice;
+ opt->posPrev = cur + lenTest + 1;
+ opt->backPrev = 0;
+ opt->prev1IsChar = True;
+ opt->prev2 = True;
+ opt->posPrev2 = cur;
+ opt->backPrev2 = curBack + LZMA_NUM_REPS;
+ }
+ }
+ }
+ offs += 2;
+ if (offs == numPairs)
+ break;
+ curBack = matches[offs + 1];
+ if (curBack >= kNumFullDistances)
+ GetPosSlot2(curBack, posSlot);
+ }
+ }
+ }
+ }
+}
+
+#define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist))
+
+static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes)
+{
+ UInt32 numAvail, mainLen, mainDist, numPairs, repIndex, repLen, i;
+ const Byte *data;
+ const UInt32 *matches;
+
+ if (p->additionalOffset == 0)
+ mainLen = ReadMatchDistances(p, &numPairs);
+ else
+ {
+ mainLen = p->longestMatchLength;
+ numPairs = p->numPairs;
+ }
+
+ numAvail = p->numAvail;
+ *backRes = (UInt32)-1;
+ if (numAvail < 2)
+ return 1;
+ if (numAvail > LZMA_MATCH_LEN_MAX)
+ numAvail = LZMA_MATCH_LEN_MAX;
+ data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
+
+ repLen = repIndex = 0;
+ for (i = 0; i < LZMA_NUM_REPS; i++)
+ {
+ UInt32 len;
+ const Byte *data2 = data - (p->reps[i] + 1);
+ if (data[0] != data2[0] || data[1] != data2[1])
+ continue;
+ for (len = 2; len < numAvail && data[len] == data2[len]; len++);
+ if (len >= p->numFastBytes)
+ {
+ *backRes = i;
+ MovePos(p, len - 1);
+ return len;
+ }
+ if (len > repLen)
+ {
+ repIndex = i;
+ repLen = len;
+ }
+ }
+
+ matches = p->matches;
+ if (mainLen >= p->numFastBytes)
+ {
+ *backRes = matches[numPairs - 1] + LZMA_NUM_REPS;
+ MovePos(p, mainLen - 1);
+ return mainLen;
+ }
+
+ mainDist = 0; /* for GCC */
+ if (mainLen >= 2)
+ {
+ mainDist = matches[numPairs - 1];
+ while (numPairs > 2 && mainLen == matches[numPairs - 4] + 1)
+ {
+ if (!ChangePair(matches[numPairs - 3], mainDist))
+ break;
+ numPairs -= 2;
+ mainLen = matches[numPairs - 2];
+ mainDist = matches[numPairs - 1];
+ }
+ if (mainLen == 2 && mainDist >= 0x80)
+ mainLen = 1;
+ }
+
+ if (repLen >= 2 && (
+ (repLen + 1 >= mainLen) ||
+ (repLen + 2 >= mainLen && mainDist >= (1 << 9)) ||
+ (repLen + 3 >= mainLen && mainDist >= (1 << 15))))
+ {
+ *backRes = repIndex;
+ MovePos(p, repLen - 1);
+ return repLen;
+ }
+
+ if (mainLen < 2 || numAvail <= 2)
+ return 1;
+
+ p->longestMatchLength = ReadMatchDistances(p, &p->numPairs);
+ if (p->longestMatchLength >= 2)
+ {
+ UInt32 newDistance = matches[p->numPairs - 1];
+ if ((p->longestMatchLength >= mainLen && newDistance < mainDist) ||
+ (p->longestMatchLength == mainLen + 1 && !ChangePair(mainDist, newDistance)) ||
+ (p->longestMatchLength > mainLen + 1) ||
+ (p->longestMatchLength + 1 >= mainLen && mainLen >= 3 && ChangePair(newDistance, mainDist)))
+ return 1;
+ }
+
+ data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
+ for (i = 0; i < LZMA_NUM_REPS; i++)
+ {
+ UInt32 len, limit;
+ const Byte *data2 = data - (p->reps[i] + 1);
+ if (data[0] != data2[0] || data[1] != data2[1])
+ continue;
+ limit = mainLen - 1;
+ for (len = 2; len < limit && data[len] == data2[len]; len++);
+ if (len >= limit)
+ return 1;
+ }
+ *backRes = mainDist + LZMA_NUM_REPS;
+ MovePos(p, mainLen - 2);
+ return mainLen;
+}
+
+static void WriteEndMarker(CLzmaEnc *p, UInt32 posState)
+{
+ UInt32 len;
+ RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1);
+ RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0);
+ p->state = kMatchNextStates[p->state];
+ len = LZMA_MATCH_LEN_MIN;
+ LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
+ RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, (1 << kNumPosSlotBits) - 1);
+ RangeEnc_EncodeDirectBits(&p->rc, (((UInt32)1 << 30) - 1) >> kNumAlignBits, 30 - kNumAlignBits);
+ RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask);
+}
+
+static SRes CheckErrors(CLzmaEnc *p)
+{
+ if (p->result != SZ_OK)
+ return p->result;
+ if (p->rc.res != SZ_OK)
+ p->result = SZ_ERROR_WRITE;
+ if (p->matchFinderBase.result != SZ_OK)
+ p->result = SZ_ERROR_READ;
+ if (p->result != SZ_OK)
+ p->finished = True;
+ return p->result;
+}
+
+static SRes Flush(CLzmaEnc *p, UInt32 nowPos)
+{
+ /* ReleaseMFStream(); */
+ p->finished = True;
+ if (p->writeEndMark)
+ WriteEndMarker(p, nowPos & p->pbMask);
+ RangeEnc_FlushData(&p->rc);
+ RangeEnc_FlushStream(&p->rc);
+ return CheckErrors(p);
+}
+
+static void FillAlignPrices(CLzmaEnc *p)
+{
+ UInt32 i;
+ for (i = 0; i < kAlignTableSize; i++)
+ p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices);
+ p->alignPriceCount = 0;
+}
+
+static void FillDistancesPrices(CLzmaEnc *p)
+{
+ UInt32 tempPrices[kNumFullDistances];
+ UInt32 i, lenToPosState;
+ for (i = kStartPosModelIndex; i < kNumFullDistances; i++)
+ {
+ UInt32 posSlot = GetPosSlot1(i);
+ UInt32 footerBits = ((posSlot >> 1) - 1);
+ UInt32 base = ((2 | (posSlot & 1)) << footerBits);
+ tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base - posSlot - 1, footerBits, i - base, p->ProbPrices);
+ }
+
+ for (lenToPosState = 0; lenToPosState < kNumLenToPosStates; lenToPosState++)
+ {
+ UInt32 posSlot;
+ const CLzmaProb *encoder = p->posSlotEncoder[lenToPosState];
+ UInt32 *posSlotPrices = p->posSlotPrices[lenToPosState];
+ for (posSlot = 0; posSlot < p->distTableSize; posSlot++)
+ posSlotPrices[posSlot] = RcTree_GetPrice(encoder, kNumPosSlotBits, posSlot, p->ProbPrices);
+ for (posSlot = kEndPosModelIndex; posSlot < p->distTableSize; posSlot++)
+ posSlotPrices[posSlot] += ((((posSlot >> 1) - 1) - kNumAlignBits) << kNumBitPriceShiftBits);
+
+ {
+ UInt32 *distancesPrices = p->distancesPrices[lenToPosState];
+ UInt32 i;
+ for (i = 0; i < kStartPosModelIndex; i++)
+ distancesPrices[i] = posSlotPrices[i];
+ for (; i < kNumFullDistances; i++)
+ distancesPrices[i] = posSlotPrices[GetPosSlot1(i)] + tempPrices[i];
+ }
+ }
+ p->matchPriceCount = 0;
+}
+
+void LzmaEnc_Construct(CLzmaEnc *p)
+{
+ RangeEnc_Construct(&p->rc);
+ MatchFinder_Construct(&p->matchFinderBase);
+ #ifndef _7ZIP_ST
+ MatchFinderMt_Construct(&p->matchFinderMt);
+ p->matchFinderMt.MatchFinder = &p->matchFinderBase;
+ #endif
+
+ {
+ CLzmaEncProps props;
+ LzmaEncProps_Init(&props);
+ LzmaEnc_SetProps(p, &props);
+ }
+
+ #ifndef LZMA_LOG_BSR
+ LzmaEnc_FastPosInit(p->g_FastPos);
+ #endif
+
+ LzmaEnc_InitPriceTables(p->ProbPrices);
+ p->litProbs = 0;
+ p->saveState.litProbs = 0;
+}
+
+CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc)
+{
+ void *p;
+ p = alloc->Alloc(alloc, sizeof(CLzmaEnc));
+ if (p != 0)
+ LzmaEnc_Construct((CLzmaEnc *)p);
+ return p;
+}
+
+void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->litProbs);
+ alloc->Free(alloc, p->saveState.litProbs);
+ p->litProbs = 0;
+ p->saveState.litProbs = 0;
+}
+
+void LzmaEnc_Destruct(CLzmaEnc *p, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ #ifndef _7ZIP_ST
+ MatchFinderMt_Destruct(&p->matchFinderMt, allocBig);
+ #endif
+ MatchFinder_Free(&p->matchFinderBase, allocBig);
+ LzmaEnc_FreeLits(p, alloc);
+ RangeEnc_Free(&p->rc, alloc);
+}
+
+void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig);
+ alloc->Free(alloc, p);
+}
+
+static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize, UInt32 maxUnpackSize)
+{
+ UInt32 nowPos32, startPos32;
+ if (p->needInit)
+ {
+ p->matchFinder.Init(p->matchFinderObj);
+ p->needInit = 0;
+ }
+
+ if (p->finished)
+ return p->result;
+ RINOK(CheckErrors(p));
+
+ nowPos32 = (UInt32)p->nowPos64;
+ startPos32 = nowPos32;
+
+ if (p->nowPos64 == 0)
+ {
+ UInt32 numPairs;
+ Byte curByte;
+ if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0)
+ return Flush(p, nowPos32);
+ ReadMatchDistances(p, &numPairs);
+ RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][0], 0);
+ p->state = kLiteralNextStates[p->state];
+ curByte = p->matchFinder.GetIndexByte(p->matchFinderObj, 0 - p->additionalOffset);
+ LitEnc_Encode(&p->rc, p->litProbs, curByte);
+ p->additionalOffset--;
+ nowPos32++;
+ }
+
+ if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0)
+ for (;;)
+ {
+ UInt32 pos, len, posState;
+
+ if (p->fastMode)
+ len = GetOptimumFast(p, &pos);
+ else
+ len = GetOptimum(p, nowPos32, &pos);
+
+ #ifdef SHOW_STAT2
+ printf("\n pos = %4X, len = %d pos = %d", nowPos32, len, pos);
+ #endif
+
+ posState = nowPos32 & p->pbMask;
+ if (len == 1 && pos == (UInt32)-1)
+ {
+ Byte curByte;
+ CLzmaProb *probs;
+ const Byte *data;
+
+ RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 0);
+ data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset;
+ curByte = *data;
+ probs = LIT_PROBS(nowPos32, *(data - 1));
+ if (IsCharState(p->state))
+ LitEnc_Encode(&p->rc, probs, curByte);
+ else
+ LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0] - 1));
+ p->state = kLiteralNextStates[p->state];
+ }
+ else
+ {
+ RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1);
+ if (pos < LZMA_NUM_REPS)
+ {
+ RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 1);
+ if (pos == 0)
+ {
+ RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 0);
+ RangeEnc_EncodeBit(&p->rc, &p->isRep0Long[p->state][posState], ((len == 1) ? 0 : 1));
+ }
+ else
+ {
+ UInt32 distance = p->reps[pos];
+ RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 1);
+ if (pos == 1)
+ RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 0);
+ else
+ {
+ RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 1);
+ RangeEnc_EncodeBit(&p->rc, &p->isRepG2[p->state], pos - 2);
+ if (pos == 3)
+ p->reps[3] = p->reps[2];
+ p->reps[2] = p->reps[1];
+ }
+ p->reps[1] = p->reps[0];
+ p->reps[0] = distance;
+ }
+ if (len == 1)
+ p->state = kShortRepNextStates[p->state];
+ else
+ {
+ LenEnc_Encode2(&p->repLenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
+ p->state = kRepNextStates[p->state];
+ }
+ }
+ else
+ {
+ UInt32 posSlot;
+ RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0);
+ p->state = kMatchNextStates[p->state];
+ LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices);
+ pos -= LZMA_NUM_REPS;
+ GetPosSlot(pos, posSlot);
+ RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, posSlot);
+
+ if (posSlot >= kStartPosModelIndex)
+ {
+ UInt32 footerBits = ((posSlot >> 1) - 1);
+ UInt32 base = ((2 | (posSlot & 1)) << footerBits);
+ UInt32 posReduced = pos - base;
+
+ if (posSlot < kEndPosModelIndex)
+ RcTree_ReverseEncode(&p->rc, p->posEncoders + base - posSlot - 1, footerBits, posReduced);
+ else
+ {
+ RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits);
+ RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask);
+ p->alignPriceCount++;
+ }
+ }
+ p->reps[3] = p->reps[2];
+ p->reps[2] = p->reps[1];
+ p->reps[1] = p->reps[0];
+ p->reps[0] = pos;
+ p->matchPriceCount++;
+ }
+ }
+ p->additionalOffset -= len;
+ nowPos32 += len;
+ if (p->additionalOffset == 0)
+ {
+ UInt32 processed;
+ if (!p->fastMode)
+ {
+ if (p->matchPriceCount >= (1 << 7))
+ FillDistancesPrices(p);
+ if (p->alignPriceCount >= kAlignTableSize)
+ FillAlignPrices(p);
+ }
+ if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0)
+ break;
+ processed = nowPos32 - startPos32;
+ if (useLimits)
+ {
+ if (processed + kNumOpts + 300 >= maxUnpackSize ||
+ RangeEnc_GetProcessed(&p->rc) + kNumOpts * 2 >= maxPackSize)
+ break;
+ }
+ else if (processed >= (1 << 15))
+ {
+ p->nowPos64 += nowPos32 - startPos32;
+ return CheckErrors(p);
+ }
+ }
+ }
+ p->nowPos64 += nowPos32 - startPos32;
+ return Flush(p, nowPos32);
+}
+
+#define kBigHashDicLimit ((UInt32)1 << 24)
+
+static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ UInt32 beforeSize = kNumOpts;
+ Bool btMode;
+ if (!RangeEnc_Alloc(&p->rc, alloc))
+ return SZ_ERROR_MEM;
+ btMode = (p->matchFinderBase.btMode != 0);
+ #ifndef _7ZIP_ST
+ p->mtMode = (p->multiThread && !p->fastMode && btMode);
+ #endif
+
+ {
+ unsigned lclp = p->lc + p->lp;
+ if (p->litProbs == 0 || p->saveState.litProbs == 0 || p->lclp != lclp)
+ {
+ LzmaEnc_FreeLits(p, alloc);
+ p->litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb));
+ p->saveState.litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb));
+ if (p->litProbs == 0 || p->saveState.litProbs == 0)
+ {
+ LzmaEnc_FreeLits(p, alloc);
+ return SZ_ERROR_MEM;
+ }
+ p->lclp = lclp;
+ }
+ }
+
+ p->matchFinderBase.bigHash = (p->dictSize > kBigHashDicLimit);
+
+ if (beforeSize + p->dictSize < keepWindowSize)
+ beforeSize = keepWindowSize - p->dictSize;
+
+ #ifndef _7ZIP_ST
+ if (p->mtMode)
+ {
+ RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig));
+ p->matchFinderObj = &p->matchFinderMt;
+ MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder);
+ }
+ else
+ #endif
+ {
+ if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig))
+ return SZ_ERROR_MEM;
+ p->matchFinderObj = &p->matchFinderBase;
+ MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder);
+ }
+ return SZ_OK;
+}
+
+void LzmaEnc_Init(CLzmaEnc *p)
+{
+ UInt32 i;
+ p->state = 0;
+ for (i = 0 ; i < LZMA_NUM_REPS; i++)
+ p->reps[i] = 0;
+
+ RangeEnc_Init(&p->rc);
+
+
+ for (i = 0; i < kNumStates; i++)
+ {
+ UInt32 j;
+ for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++)
+ {
+ p->isMatch[i][j] = kProbInitValue;
+ p->isRep0Long[i][j] = kProbInitValue;
+ }
+ p->isRep[i] = kProbInitValue;
+ p->isRepG0[i] = kProbInitValue;
+ p->isRepG1[i] = kProbInitValue;
+ p->isRepG2[i] = kProbInitValue;
+ }
+
+ {
+ UInt32 num = 0x300 << (p->lp + p->lc);
+ for (i = 0; i < num; i++)
+ p->litProbs[i] = kProbInitValue;
+ }
+
+ {
+ for (i = 0; i < kNumLenToPosStates; i++)
+ {
+ CLzmaProb *probs = p->posSlotEncoder[i];
+ UInt32 j;
+ for (j = 0; j < (1 << kNumPosSlotBits); j++)
+ probs[j] = kProbInitValue;
+ }
+ }
+ {
+ for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++)
+ p->posEncoders[i] = kProbInitValue;
+ }
+
+ LenEnc_Init(&p->lenEnc.p);
+ LenEnc_Init(&p->repLenEnc.p);
+
+ for (i = 0; i < (1 << kNumAlignBits); i++)
+ p->posAlignEncoder[i] = kProbInitValue;
+
+ p->optimumEndIndex = 0;
+ p->optimumCurrentIndex = 0;
+ p->additionalOffset = 0;
+
+ p->pbMask = (1 << p->pb) - 1;
+ p->lpMask = (1 << p->lp) - 1;
+}
+
+void LzmaEnc_InitPrices(CLzmaEnc *p)
+{
+ if (!p->fastMode)
+ {
+ FillDistancesPrices(p);
+ FillAlignPrices(p);
+ }
+
+ p->lenEnc.tableSize =
+ p->repLenEnc.tableSize =
+ p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN;
+ LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, p->ProbPrices);
+ LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, p->ProbPrices);
+}
+
+static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ UInt32 i;
+ for (i = 0; i < (UInt32)kDicLogSizeMaxCompress; i++)
+ if (p->dictSize <= ((UInt32)1 << i))
+ break;
+ p->distTableSize = i * 2;
+
+ p->finished = False;
+ p->result = SZ_OK;
+ RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig));
+ LzmaEnc_Init(p);
+ LzmaEnc_InitPrices(p);
+ p->nowPos64 = 0;
+ return SZ_OK;
+}
+
+static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream,
+ ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ p->matchFinderBase.stream = inStream;
+ p->needInit = 1;
+ p->rc.outStream = outStream;
+ return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig);
+}
+
+SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp,
+ ISeqInStream *inStream, UInt32 keepWindowSize,
+ ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ p->matchFinderBase.stream = inStream;
+ p->needInit = 1;
+ return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
+}
+
+static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen)
+{
+ p->matchFinderBase.directInput = 1;
+ p->matchFinderBase.bufferBase = (Byte *)src;
+ p->matchFinderBase.directInputRem = srcLen;
+}
+
+SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen,
+ UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ LzmaEnc_SetInputBuf(p, src, srcLen);
+ p->needInit = 1;
+
+ return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
+}
+
+void LzmaEnc_Finish(CLzmaEncHandle pp)
+{
+ #ifndef _7ZIP_ST
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ if (p->mtMode)
+ MatchFinderMt_ReleaseStream(&p->matchFinderMt);
+ #else
+ pp = pp;
+ #endif
+}
+
+typedef struct
+{
+ ISeqOutStream funcTable;
+ Byte *data;
+ SizeT rem;
+ Bool overflow;
+} CSeqOutStreamBuf;
+
+static size_t MyWrite(void *pp, const void *data, size_t size)
+{
+ CSeqOutStreamBuf *p = (CSeqOutStreamBuf *)pp;
+ if (p->rem < size)
+ {
+ size = p->rem;
+ p->overflow = True;
+ }
+ memcpy(p->data, data, size);
+ p->rem -= size;
+ p->data += size;
+ return size;
+}
+
+
+UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp)
+{
+ const CLzmaEnc *p = (CLzmaEnc *)pp;
+ return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
+}
+
+const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp)
+{
+ const CLzmaEnc *p = (CLzmaEnc *)pp;
+ return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset;
+}
+
+SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit,
+ Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ UInt64 nowPos64;
+ SRes res;
+ CSeqOutStreamBuf outStream;
+
+ outStream.funcTable.Write = MyWrite;
+ outStream.data = dest;
+ outStream.rem = *destLen;
+ outStream.overflow = False;
+
+ p->writeEndMark = False;
+ p->finished = False;
+ p->result = SZ_OK;
+
+ if (reInit)
+ LzmaEnc_Init(p);
+ LzmaEnc_InitPrices(p);
+ nowPos64 = p->nowPos64;
+ RangeEnc_Init(&p->rc);
+ p->rc.outStream = &outStream.funcTable;
+
+ res = LzmaEnc_CodeOneBlock(p, True, desiredPackSize, *unpackSize);
+
+ *unpackSize = (UInt32)(p->nowPos64 - nowPos64);
+ *destLen -= outStream.rem;
+ if (outStream.overflow)
+ return SZ_ERROR_OUTPUT_EOF;
+
+ return res;
+}
+
+static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress)
+{
+ SRes res = SZ_OK;
+
+ #ifndef _7ZIP_ST
+ Byte allocaDummy[0x300];
+ int i = 0;
+ for (i = 0; i < 16; i++)
+ allocaDummy[i] = (Byte)i;
+ #endif
+
+ for (;;)
+ {
+ res = LzmaEnc_CodeOneBlock(p, False, 0, 0);
+ if (res != SZ_OK || p->finished != 0)
+ break;
+ if (progress != 0)
+ {
+ res = progress->Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc));
+ if (res != SZ_OK)
+ {
+ res = SZ_ERROR_PROGRESS;
+ break;
+ }
+ }
+ }
+ LzmaEnc_Finish(p);
+ return res;
+}
+
+SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress,
+ ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig));
+ return LzmaEnc_Encode2((CLzmaEnc *)pp, progress);
+}
+
+SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size)
+{
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+ int i;
+ UInt32 dictSize = p->dictSize;
+ if (*size < LZMA_PROPS_SIZE)
+ return SZ_ERROR_PARAM;
+ *size = LZMA_PROPS_SIZE;
+ props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc);
+
+ for (i = 11; i <= 30; i++)
+ {
+ if (dictSize <= ((UInt32)2 << i))
+ {
+ dictSize = (2 << i);
+ break;
+ }
+ if (dictSize <= ((UInt32)3 << i))
+ {
+ dictSize = (3 << i);
+ break;
+ }
+ }
+
+ for (i = 0; i < 4; i++)
+ props[1 + i] = (Byte)(dictSize >> (8 * i));
+ return SZ_OK;
+}
+
+SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
+ int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ SRes res;
+ CLzmaEnc *p = (CLzmaEnc *)pp;
+
+ CSeqOutStreamBuf outStream;
+
+ LzmaEnc_SetInputBuf(p, src, srcLen);
+
+ outStream.funcTable.Write = MyWrite;
+ outStream.data = dest;
+ outStream.rem = *destLen;
+ outStream.overflow = False;
+
+ p->writeEndMark = writeEndMark;
+
+ p->rc.outStream = &outStream.funcTable;
+ res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig);
+ if (res == SZ_OK)
+ res = LzmaEnc_Encode2(p, progress);
+
+ *destLen -= outStream.rem;
+ if (outStream.overflow)
+ return SZ_ERROR_OUTPUT_EOF;
+ return res;
+}
+
+SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
+ const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark,
+ ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig)
+{
+ CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc);
+ SRes res;
+ if (p == 0)
+ return SZ_ERROR_MEM;
+
+ res = LzmaEnc_SetProps(p, props);
+ if (res == SZ_OK)
+ {
+ res = LzmaEnc_WriteProperties(p, propsEncoded, propsSize);
+ if (res == SZ_OK)
+ res = LzmaEnc_MemEncode(p, dest, destLen, src, srcLen,
+ writeEndMark, progress, alloc, allocBig);
+ }
+
+ LzmaEnc_Destroy(p, alloc, allocBig);
+ return res;
+}
diff --git a/dep/StormLib/src/lzma/C/LzmaEnc.h b/dep/StormLib/src/lzma/C/LzmaEnc.h
new file mode 100644
index 000000000..200d60eb8
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/LzmaEnc.h
@@ -0,0 +1,80 @@
+/* LzmaEnc.h -- LZMA Encoder
+2009-02-07 : Igor Pavlov : Public domain */
+
+#ifndef __LZMA_ENC_H
+#define __LZMA_ENC_H
+
+#include "Types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LZMA_PROPS_SIZE 5
+
+typedef struct _CLzmaEncProps
+{
+ int level; /* 0 <= level <= 9 */
+ UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version
+ (1 << 12) <= dictSize <= (1 << 30) for 64-bit version
+ default = (1 << 24) */
+ int lc; /* 0 <= lc <= 8, default = 3 */
+ int lp; /* 0 <= lp <= 4, default = 0 */
+ int pb; /* 0 <= pb <= 4, default = 2 */
+ int algo; /* 0 - fast, 1 - normal, default = 1 */
+ int fb; /* 5 <= fb <= 273, default = 32 */
+ int btMode; /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */
+ int numHashBytes; /* 2, 3 or 4, default = 4 */
+ UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */
+ unsigned writeEndMark; /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */
+ int numThreads; /* 1 or 2, default = 2 */
+} CLzmaEncProps;
+
+void LzmaEncProps_Init(CLzmaEncProps *p);
+void LzmaEncProps_Normalize(CLzmaEncProps *p);
+UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2);
+
+
+/* ---------- CLzmaEncHandle Interface ---------- */
+
+/* LzmaEnc_* functions can return the following exit codes:
+Returns:
+ SZ_OK - OK
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_PARAM - Incorrect paramater in props
+ SZ_ERROR_WRITE - Write callback error.
+ SZ_ERROR_PROGRESS - some break from progress callback
+ SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version)
+*/
+
+typedef void * CLzmaEncHandle;
+
+CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc);
+void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig);
+SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props);
+SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size);
+SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream,
+ ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
+SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
+ int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
+
+/* ---------- One Call Interface ---------- */
+
+/* LzmaEncode
+Return code:
+ SZ_OK - OK
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_PARAM - Incorrect paramater
+ SZ_ERROR_OUTPUT_EOF - output buffer overflow
+ SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version)
+*/
+
+SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen,
+ const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark,
+ ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dep/StormLib/src/lzma/C/Threads.c b/dep/StormLib/src/lzma/C/Threads.c
new file mode 100644
index 000000000..7af1da2e2
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/Threads.c
@@ -0,0 +1,84 @@
+/* Threads.c -- multithreading library
+2009-09-20 : Igor Pavlov : Public domain */
+
+#ifndef _WIN32_WCE
+#include
+#endif
+
+#include "Threads.h"
+
+static WRes GetError()
+{
+ DWORD res = GetLastError();
+ return (res) ? (WRes)(res) : 1;
+}
+
+WRes HandleToWRes(HANDLE h) { return (h != 0) ? 0 : GetError(); }
+WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); }
+
+WRes HandlePtr_Close(HANDLE *p)
+{
+ if (*p != NULL)
+ if (!CloseHandle(*p))
+ return GetError();
+ *p = NULL;
+ return 0;
+}
+
+WRes Handle_WaitObject(HANDLE h) { return (WRes)WaitForSingleObject(h, INFINITE); }
+
+WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param)
+{
+ unsigned threadId; /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */
+ *p =
+ #ifdef UNDER_CE
+ CreateThread(0, 0, func, param, 0, &threadId);
+ #else
+ (HANDLE)_beginthreadex(NULL, 0, func, param, 0, &threadId);
+ #endif
+ /* maybe we must use errno here, but probably GetLastError() is also OK. */
+ return HandleToWRes(*p);
+}
+
+WRes Event_Create(CEvent *p, BOOL manualReset, int signaled)
+{
+ *p = CreateEvent(NULL, manualReset, (signaled ? TRUE : FALSE), NULL);
+ return HandleToWRes(*p);
+}
+
+WRes Event_Set(CEvent *p) { return BOOLToWRes(SetEvent(*p)); }
+WRes Event_Reset(CEvent *p) { return BOOLToWRes(ResetEvent(*p)); }
+
+WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled) { return Event_Create(p, TRUE, signaled); }
+WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled) { return Event_Create(p, FALSE, signaled); }
+WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p) { return ManualResetEvent_Create(p, 0); }
+WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) { return AutoResetEvent_Create(p, 0); }
+
+
+WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount)
+{
+ *p = CreateSemaphore(NULL, (LONG)initCount, (LONG)maxCount, NULL);
+ return HandleToWRes(*p);
+}
+
+static WRes Semaphore_Release(CSemaphore *p, LONG releaseCount, LONG *previousCount)
+ { return BOOLToWRes(ReleaseSemaphore(*p, releaseCount, previousCount)); }
+WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num)
+ { return Semaphore_Release(p, (LONG)num, NULL); }
+WRes Semaphore_Release1(CSemaphore *p) { return Semaphore_ReleaseN(p, 1); }
+
+WRes CriticalSection_Init(CCriticalSection *p)
+{
+ /* InitializeCriticalSection can raise only STATUS_NO_MEMORY exception */
+ #ifdef _MSC_VER
+ __try
+ #endif
+ {
+ InitializeCriticalSection(p);
+ /* InitializeCriticalSectionAndSpinCount(p, 0); */
+ }
+ #ifdef _MSC_VER
+ __except (EXCEPTION_EXECUTE_HANDLER) { return 1; }
+ #endif
+ return 0;
+}
diff --git a/dep/StormLib/src/lzma/C/Threads.h b/dep/StormLib/src/lzma/C/Threads.h
new file mode 100644
index 000000000..d0ddd80e2
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/Threads.h
@@ -0,0 +1,59 @@
+/* Threads.h -- multithreading library
+2009-03-27 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_THREADS_H
+#define __7Z_THREADS_H
+
+#include "Types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+WRes HandlePtr_Close(HANDLE *h);
+WRes Handle_WaitObject(HANDLE h);
+
+typedef HANDLE CThread;
+#define Thread_Construct(p) *(p) = NULL
+#define Thread_WasCreated(p) (*(p) != NULL)
+#define Thread_Close(p) HandlePtr_Close(p)
+#define Thread_Wait(p) Handle_WaitObject(*(p))
+typedef unsigned THREAD_FUNC_RET_TYPE;
+#define THREAD_FUNC_CALL_TYPE MY_STD_CALL
+#define THREAD_FUNC_DECL THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE
+typedef THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE * THREAD_FUNC_TYPE)(void *);
+WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param);
+
+typedef HANDLE CEvent;
+typedef CEvent CAutoResetEvent;
+typedef CEvent CManualResetEvent;
+#define Event_Construct(p) *(p) = NULL
+#define Event_IsCreated(p) (*(p) != NULL)
+#define Event_Close(p) HandlePtr_Close(p)
+#define Event_Wait(p) Handle_WaitObject(*(p))
+WRes Event_Set(CEvent *p);
+WRes Event_Reset(CEvent *p);
+WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled);
+WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p);
+WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled);
+WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p);
+
+typedef HANDLE CSemaphore;
+#define Semaphore_Construct(p) (*p) = NULL
+#define Semaphore_Close(p) HandlePtr_Close(p)
+#define Semaphore_Wait(p) Handle_WaitObject(*(p))
+WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount);
+WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num);
+WRes Semaphore_Release1(CSemaphore *p);
+
+typedef CRITICAL_SECTION CCriticalSection;
+WRes CriticalSection_Init(CCriticalSection *p);
+#define CriticalSection_Delete(p) DeleteCriticalSection(p)
+#define CriticalSection_Enter(p) EnterCriticalSection(p)
+#define CriticalSection_Leave(p) LeaveCriticalSection(p)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dep/StormLib/src/lzma/C/Types.h b/dep/StormLib/src/lzma/C/Types.h
new file mode 100644
index 000000000..0526cb47b
--- /dev/null
+++ b/dep/StormLib/src/lzma/C/Types.h
@@ -0,0 +1,236 @@
+/* Types.h -- Basic types
+2010-03-11 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_TYPES_H
+#define __7Z_TYPES_H
+
+#include
+
+#ifdef _WIN32
+#include
+#endif
+
+#ifndef EXTERN_C_BEGIN
+#ifdef __cplusplus
+#define EXTERN_C_BEGIN extern "C" {
+#define EXTERN_C_END }
+#else
+#define EXTERN_C_BEGIN
+#define EXTERN_C_END
+#endif
+#endif
+
+EXTERN_C_BEGIN
+
+#define SZ_OK 0
+
+#define SZ_ERROR_DATA 1
+#define SZ_ERROR_MEM 2
+#define SZ_ERROR_CRC 3
+#define SZ_ERROR_UNSUPPORTED 4
+#define SZ_ERROR_PARAM 5
+#define SZ_ERROR_INPUT_EOF 6
+#define SZ_ERROR_OUTPUT_EOF 7
+#define SZ_ERROR_READ 8
+#define SZ_ERROR_WRITE 9
+#define SZ_ERROR_PROGRESS 10
+#define SZ_ERROR_FAIL 11
+#define SZ_ERROR_THREAD 12
+
+#define SZ_ERROR_ARCHIVE 16
+#define SZ_ERROR_NO_ARCHIVE 17
+
+typedef int SRes;
+
+#ifdef _WIN32
+typedef DWORD WRes;
+#else
+typedef int WRes;
+#endif
+
+#ifndef RINOK
+#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; }
+#endif
+
+typedef unsigned char Byte;
+typedef short Int16;
+typedef unsigned short UInt16;
+
+#ifdef _LZMA_UINT32_IS_ULONG
+typedef long Int32;
+typedef unsigned long UInt32;
+#else
+typedef int Int32;
+typedef unsigned int UInt32;
+#endif
+
+#ifdef _SZ_NO_INT_64
+
+/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers.
+ NOTES: Some code will work incorrectly in that case! */
+
+typedef long Int64;
+typedef unsigned long UInt64;
+
+#else
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+typedef __int64 Int64;
+typedef unsigned __int64 UInt64;
+#else
+typedef long long int Int64;
+typedef unsigned long long int UInt64;
+#endif
+
+#endif
+
+#ifdef _LZMA_NO_SYSTEM_SIZE_T
+typedef UInt32 SizeT;
+#else
+typedef size_t SizeT;
+#endif
+
+typedef int Bool;
+#define True 1
+#define False 0
+
+
+#ifdef _WIN32
+#define MY_STD_CALL __stdcall
+#else
+#define MY_STD_CALL
+#endif
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1300
+#define MY_NO_INLINE __declspec(noinline)
+#else
+#define MY_NO_INLINE
+#endif
+
+#define MY_CDECL __cdecl
+#define MY_FAST_CALL __fastcall
+
+#else
+
+#define MY_CDECL
+#define MY_FAST_CALL
+
+#endif
+
+
+/* The following interfaces use first parameter as pointer to structure */
+
+typedef struct
+{
+ Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */
+} IByteIn;
+
+typedef struct
+{
+ void (*Write)(void *p, Byte b);
+} IByteOut;
+
+typedef struct
+{
+ SRes (*Read)(void *p, void *buf, size_t *size);
+ /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
+ (output(*size) < input(*size)) is allowed */
+} ISeqInStream;
+
+/* it can return SZ_ERROR_INPUT_EOF */
+SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size);
+SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType);
+SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf);
+
+typedef struct
+{
+ size_t (*Write)(void *p, const void *buf, size_t size);
+ /* Returns: result - the number of actually written bytes.
+ (result < size) means error */
+} ISeqOutStream;
+
+typedef enum
+{
+ SZ_SEEK_SET = 0,
+ SZ_SEEK_CUR = 1,
+ SZ_SEEK_END = 2
+} ESzSeek;
+
+typedef struct
+{
+ SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */
+ SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
+} ISeekInStream;
+
+typedef struct
+{
+ SRes (*Look)(void *p, const void **buf, size_t *size);
+ /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
+ (output(*size) > input(*size)) is not allowed
+ (output(*size) < input(*size)) is allowed */
+ SRes (*Skip)(void *p, size_t offset);
+ /* offset must be <= output(*size) of Look */
+
+ SRes (*Read)(void *p, void *buf, size_t *size);
+ /* reads directly (without buffer). It's same as ISeqInStream::Read */
+ SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
+} ILookInStream;
+
+SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size);
+SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset);
+
+/* reads via ILookInStream::Read */
+SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType);
+SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size);
+
+#define LookToRead_BUF_SIZE (1 << 14)
+
+typedef struct
+{
+ ILookInStream s;
+ ISeekInStream *realStream;
+ size_t pos;
+ size_t size;
+ Byte buf[LookToRead_BUF_SIZE];
+} CLookToRead;
+
+void LookToRead_CreateVTable(CLookToRead *p, int lookahead);
+void LookToRead_Init(CLookToRead *p);
+
+typedef struct
+{
+ ISeqInStream s;
+ ILookInStream *realStream;
+} CSecToLook;
+
+void SecToLook_CreateVTable(CSecToLook *p);
+
+typedef struct
+{
+ ISeqInStream s;
+ ILookInStream *realStream;
+} CSecToRead;
+
+void SecToRead_CreateVTable(CSecToRead *p);
+
+typedef struct
+{
+ SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize);
+ /* Returns: result. (result != SZ_OK) means break.
+ Value (UInt64)(Int64)-1 for size means unknown value. */
+} ICompressProgress;
+
+typedef struct
+{
+ void *(*Alloc)(void *p, size_t size);
+ void (*Free)(void *p, void *address); /* address can be 0 */
+} ISzAlloc;
+
+#define IAlloc_Alloc(p, size) (p)->Alloc((p), size)
+#define IAlloc_Free(p, a) (p)->Free((p), a)
+
+EXTERN_C_END
+
+#endif
diff --git a/dep/StormLib/src/lzma/info.txt b/dep/StormLib/src/lzma/info.txt
new file mode 100644
index 000000000..4cee86e35
--- /dev/null
+++ b/dep/StormLib/src/lzma/info.txt
@@ -0,0 +1 @@
+Taken from LZMA SDK v 9.11
\ No newline at end of file
diff --git a/dep/StormLib/src/pklib/crc32.c b/dep/StormLib/src/pklib/crc32.c
new file mode 100644
index 000000000..cd47b1d4a
--- /dev/null
+++ b/dep/StormLib/src/pklib/crc32.c
@@ -0,0 +1,66 @@
+/*****************************************************************************/
+/* crc32.c Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Pkware Data Compression Library Version 1.11 */
+/* Dissassembled method crc32 - cdecl version */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 09.04.03 1.00 Lad The first version of crc32.c */
+/* 02.05.03 1.00 Lad Stress test done */
+/*****************************************************************************/
+
+#include "pklib.h"
+
+static unsigned long crc_table[] =
+{
+ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+ 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+ 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+ 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+ 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+ 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+ 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+ 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+ 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+ 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+ 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+ 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+ 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+ 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+ 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+ 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+ 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+ 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+ 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+ 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+ 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+ 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+ 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+ 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+ 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+ 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+ 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+ 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+ 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+ 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+ 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+ 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+};
+
+
+unsigned long PKEXPORT crc32_pklib(char * buffer, unsigned int * psize, unsigned long * old_crc)
+{
+ unsigned int size = *psize;
+ unsigned long ch;
+ unsigned long crc_value = *old_crc;
+
+ while(size-- != 0)
+ {
+ ch = *buffer++ ^ (char)crc_value;
+ crc_value >>= 8;
+
+ crc_value = crc_table[ch & 0x0FF] ^ crc_value;
+ }
+ return crc_value;
+}
diff --git a/dep/StormLib/src/pklib/explode.c b/dep/StormLib/src/pklib/explode.c
new file mode 100644
index 000000000..979617399
--- /dev/null
+++ b/dep/StormLib/src/pklib/explode.c
@@ -0,0 +1,522 @@
+/*****************************************************************************/
+/* explode.c Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Implode function of PKWARE Data Compression library */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 11.03.03 1.00 Lad Splitted from Pkware.cpp */
+/* 08.04.03 1.01 Lad Renamed to explode.c to be compatible with pklib */
+/* 02.05.03 1.01 Lad Stress test done */
+/* 22.04.10 1.01 Lad Documented */
+/*****************************************************************************/
+
+#include
+#include
+
+#include "pklib.h"
+
+#define PKDCL_OK 0
+#define PKDCL_STREAM_END 1 // All data from the input stream is read
+#define PKDCL_NEED_DICT 2 // Need more data (dictionary)
+#define PKDCL_CONTINUE 10 // Internal flag, not returned to user
+#define PKDCL_GET_INPUT 11 // Internal flag, not returned to user
+
+char CopyrightPkware[] = "PKWARE Data Compression Library for Win32\r\n"
+ "Copyright 1989-1995 PKWARE Inc. All Rights Reserved\r\n"
+ "Patent No. 5,051,745\r\n"
+ "PKWARE Data Compression Library Reg. U.S. Pat. and Tm. Off.\r\n"
+ "Version 1.11\r\n";
+
+//-----------------------------------------------------------------------------
+// Tables
+
+static unsigned char DistBits[] =
+{
+ 0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
+};
+
+static unsigned char DistCode[] =
+{
+ 0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
+ 0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
+ 0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
+ 0xF0, 0x70, 0xB0, 0x30, 0xD0, 0x50, 0x90, 0x10, 0xE0, 0x60, 0xA0, 0x20, 0xC0, 0x40, 0x80, 0x00
+};
+
+static unsigned char ExLenBits[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
+};
+
+static unsigned short LenBase[] =
+{
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x000A, 0x000E, 0x0016, 0x0026, 0x0046, 0x0086, 0x0106
+};
+
+static unsigned char LenBits[] =
+{
+ 0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07
+};
+
+static unsigned char LenCode[] =
+{
+ 0x05, 0x03, 0x01, 0x06, 0x0A, 0x02, 0x0C, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00
+};
+
+static unsigned char ChBitsAsc[] =
+{
+ 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x07, 0x0C, 0x0C, 0x07, 0x0C, 0x0C,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x04, 0x0A, 0x08, 0x0C, 0x0A, 0x0C, 0x0A, 0x08, 0x07, 0x07, 0x08, 0x09, 0x07, 0x06, 0x07, 0x08,
+ 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x07, 0x07, 0x08, 0x08, 0x0C, 0x0B, 0x07, 0x09, 0x0B,
+ 0x0C, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x08, 0x08, 0x06, 0x0B, 0x09, 0x06, 0x07, 0x06, 0x06,
+ 0x07, 0x0B, 0x06, 0x06, 0x06, 0x07, 0x09, 0x08, 0x09, 0x09, 0x0B, 0x08, 0x0B, 0x09, 0x0C, 0x08,
+ 0x0C, 0x05, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x06, 0x05, 0x0B, 0x07, 0x05, 0x06, 0x05, 0x05,
+ 0x06, 0x0A, 0x05, 0x05, 0x05, 0x05, 0x08, 0x07, 0x08, 0x08, 0x0A, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C,
+ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
+ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
+ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D,
+ 0x0D, 0x0D, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D
+};
+
+static unsigned short ChCodeAsc[] =
+{
+ 0x0490, 0x0FE0, 0x07E0, 0x0BE0, 0x03E0, 0x0DE0, 0x05E0, 0x09E0,
+ 0x01E0, 0x00B8, 0x0062, 0x0EE0, 0x06E0, 0x0022, 0x0AE0, 0x02E0,
+ 0x0CE0, 0x04E0, 0x08E0, 0x00E0, 0x0F60, 0x0760, 0x0B60, 0x0360,
+ 0x0D60, 0x0560, 0x1240, 0x0960, 0x0160, 0x0E60, 0x0660, 0x0A60,
+ 0x000F, 0x0250, 0x0038, 0x0260, 0x0050, 0x0C60, 0x0390, 0x00D8,
+ 0x0042, 0x0002, 0x0058, 0x01B0, 0x007C, 0x0029, 0x003C, 0x0098,
+ 0x005C, 0x0009, 0x001C, 0x006C, 0x002C, 0x004C, 0x0018, 0x000C,
+ 0x0074, 0x00E8, 0x0068, 0x0460, 0x0090, 0x0034, 0x00B0, 0x0710,
+ 0x0860, 0x0031, 0x0054, 0x0011, 0x0021, 0x0017, 0x0014, 0x00A8,
+ 0x0028, 0x0001, 0x0310, 0x0130, 0x003E, 0x0064, 0x001E, 0x002E,
+ 0x0024, 0x0510, 0x000E, 0x0036, 0x0016, 0x0044, 0x0030, 0x00C8,
+ 0x01D0, 0x00D0, 0x0110, 0x0048, 0x0610, 0x0150, 0x0060, 0x0088,
+ 0x0FA0, 0x0007, 0x0026, 0x0006, 0x003A, 0x001B, 0x001A, 0x002A,
+ 0x000A, 0x000B, 0x0210, 0x0004, 0x0013, 0x0032, 0x0003, 0x001D,
+ 0x0012, 0x0190, 0x000D, 0x0015, 0x0005, 0x0019, 0x0008, 0x0078,
+ 0x00F0, 0x0070, 0x0290, 0x0410, 0x0010, 0x07A0, 0x0BA0, 0x03A0,
+ 0x0240, 0x1C40, 0x0C40, 0x1440, 0x0440, 0x1840, 0x0840, 0x1040,
+ 0x0040, 0x1F80, 0x0F80, 0x1780, 0x0780, 0x1B80, 0x0B80, 0x1380,
+ 0x0380, 0x1D80, 0x0D80, 0x1580, 0x0580, 0x1980, 0x0980, 0x1180,
+ 0x0180, 0x1E80, 0x0E80, 0x1680, 0x0680, 0x1A80, 0x0A80, 0x1280,
+ 0x0280, 0x1C80, 0x0C80, 0x1480, 0x0480, 0x1880, 0x0880, 0x1080,
+ 0x0080, 0x1F00, 0x0F00, 0x1700, 0x0700, 0x1B00, 0x0B00, 0x1300,
+ 0x0DA0, 0x05A0, 0x09A0, 0x01A0, 0x0EA0, 0x06A0, 0x0AA0, 0x02A0,
+ 0x0CA0, 0x04A0, 0x08A0, 0x00A0, 0x0F20, 0x0720, 0x0B20, 0x0320,
+ 0x0D20, 0x0520, 0x0920, 0x0120, 0x0E20, 0x0620, 0x0A20, 0x0220,
+ 0x0C20, 0x0420, 0x0820, 0x0020, 0x0FC0, 0x07C0, 0x0BC0, 0x03C0,
+ 0x0DC0, 0x05C0, 0x09C0, 0x01C0, 0x0EC0, 0x06C0, 0x0AC0, 0x02C0,
+ 0x0CC0, 0x04C0, 0x08C0, 0x00C0, 0x0F40, 0x0740, 0x0B40, 0x0340,
+ 0x0300, 0x0D40, 0x1D00, 0x0D00, 0x1500, 0x0540, 0x0500, 0x1900,
+ 0x0900, 0x0940, 0x1100, 0x0100, 0x1E00, 0x0E00, 0x0140, 0x1600,
+ 0x0600, 0x1A00, 0x0E40, 0x0640, 0x0A40, 0x0A00, 0x1200, 0x0200,
+ 0x1C00, 0x0C00, 0x1400, 0x0400, 0x1800, 0x0800, 0x1000, 0x0000
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static void GenDecodeTabs(
+ unsigned char * positions, // [out] Table of positions
+ unsigned char * start_indexes, // [in] Table of start indexes
+ unsigned char * length_bits, // [in] Table of lengths. Each length is stored as number of bits
+ size_t elements) // [in] Number of elements in start_indexes and length_bits
+{
+ unsigned int index;
+ unsigned int length;
+ size_t i;
+
+ for(i = 0; i < elements; i++)
+ {
+ length = 1 << length_bits[i]; // Get the length in bytes
+
+ for(index = start_indexes[i]; index < 0x100; index += length)
+ {
+ positions[index] = (unsigned char)i;
+ }
+ }
+}
+
+static void GenAscTabs(TDcmpStruct * pWork)
+{
+ unsigned short * pChCodeAsc = &ChCodeAsc[0xFF];
+ unsigned int acc, add;
+ unsigned short count;
+
+ for(count = 0x00FF; pChCodeAsc >= ChCodeAsc; pChCodeAsc--, count--)
+ {
+ unsigned char * pChBitsAsc = pWork->ChBitsAsc + count;
+ unsigned char bits_asc = *pChBitsAsc;
+
+ if(bits_asc <= 8)
+ {
+ add = (1 << bits_asc);
+ acc = *pChCodeAsc;
+
+ do
+ {
+ pWork->offs2C34[acc] = (unsigned char)count;
+ acc += add;
+ }
+ while(acc < 0x100);
+ }
+ else if((acc = (*pChCodeAsc & 0xFF)) != 0)
+ {
+ pWork->offs2C34[acc] = 0xFF;
+
+ if(*pChCodeAsc & 0x3F)
+ {
+ bits_asc -= 4;
+ *pChBitsAsc = bits_asc;
+
+ add = (1 << bits_asc);
+ acc = *pChCodeAsc >> 4;
+ do
+ {
+ pWork->offs2D34[acc] = (unsigned char)count;
+ acc += add;
+ }
+ while(acc < 0x100);
+ }
+ else
+ {
+ bits_asc -= 6;
+ *pChBitsAsc = bits_asc;
+
+ add = (1 << bits_asc);
+ acc = *pChCodeAsc >> 6;
+ do
+ {
+ pWork->offs2E34[acc] = (unsigned char)count;
+ acc += add;
+ }
+ while(acc < 0x80);
+ }
+ }
+ else
+ {
+ bits_asc -= 8;
+ *pChBitsAsc = bits_asc;
+
+ add = (1 << bits_asc);
+ acc = *pChCodeAsc >> 8;
+ do
+ {
+ pWork->offs2EB4[acc] = (unsigned char)count;
+ acc += add;
+ }
+ while(acc < 0x100);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Removes given number of bits in the bit buffer. New bits are reloaded from
+// the input buffer, if needed.
+// Returns: PKDCL_OK: Operation was successful
+// PKDCL_STREAM_END: There are no more bits in the input buffer
+
+static int WasteBits(TDcmpStruct * pWork, unsigned int nBits)
+{
+ // If number of bits required is less than number of (bits in the buffer) ?
+ if(nBits <= pWork->extra_bits)
+ {
+ pWork->extra_bits -= nBits;
+ pWork->bit_buff >>= nBits;
+ return PKDCL_OK;
+ }
+
+ // Load input buffer if necessary
+ pWork->bit_buff >>= pWork->extra_bits;
+ if(pWork->in_pos == pWork->in_bytes)
+ {
+ pWork->in_pos = sizeof(pWork->in_buff);
+ if((pWork->in_bytes = pWork->read_buf((char *)pWork->in_buff, &pWork->in_pos, pWork->param)) == 0)
+ return PKDCL_STREAM_END;
+ pWork->in_pos = 0;
+ }
+
+ // Update bit buffer
+ pWork->bit_buff |= (pWork->in_buff[pWork->in_pos++] << 8);
+ pWork->bit_buff >>= (nBits - pWork->extra_bits);
+ pWork->extra_bits = (pWork->extra_bits - nBits) + 8;
+ return PKDCL_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Decodes next literal from the input (compressed) data.
+// Returns : 0x000: One byte 0x00
+// 0x001: One byte 0x01
+// ...
+// 0x0FF: One byte 0xFF
+// 0x100: Repetition, length of 0x02 bytes
+// 0x101: Repetition, length of 0x03 bytes
+// ...
+// 0x304: Repetition, length of 0x206 bytes
+// 0x305: End of stream
+// 0x306: Error
+
+static unsigned int DecodeLit(TDcmpStruct * pWork)
+{
+ unsigned int extra_length_bits; // Number of bits of extra literal length
+ unsigned int length_code; // Length code
+ unsigned int value;
+
+ // Test the current bit in byte buffer. If is not set, simply return the next 8 bits.
+ if(pWork->bit_buff & 1)
+ {
+ // Remove one bit from the input data
+ if(WasteBits(pWork, 1))
+ return 0x306;
+
+ // The next 8 bits hold the index to the length code table
+ length_code = pWork->LengthCodes[pWork->bit_buff & 0xFF];
+
+ // Remove the apropriate number of bits
+ if(WasteBits(pWork, pWork->LenBits[length_code]))
+ return 0x306;
+
+ // Are there some extra bits for the obtained length code ?
+ if((extra_length_bits = pWork->ExLenBits[length_code]) != 0)
+ {
+ unsigned int extra_length = pWork->bit_buff & ((1 << extra_length_bits) - 1);
+
+ if(WasteBits(pWork, extra_length_bits))
+ {
+ if((length_code + extra_length) != 0x10E)
+ return 0x306;
+ }
+ length_code = pWork->LenBase[length_code] + extra_length;
+ }
+
+ // In order to distinguish uncompressed byte from repetition length,
+ // we have to add 0x100 to the length.
+ return length_code + 0x100;
+ }
+
+ // Remove one bit from the input data
+ if(WasteBits(pWork, 1))
+ return 0x306;
+
+ // If the binary compression type, read 8 bits and return them as one byte.
+ if(pWork->ctype == CMP_BINARY)
+ {
+ unsigned int uncompressed_byte = pWork->bit_buff & 0xFF;
+
+ if(WasteBits(pWork, 8))
+ return 0x306;
+ return uncompressed_byte;
+ }
+
+ // When ASCII compression ...
+ if(pWork->bit_buff & 0xFF)
+ {
+ value = pWork->offs2C34[pWork->bit_buff & 0xFF];
+
+ if(value == 0xFF)
+ {
+ if(pWork->bit_buff & 0x3F)
+ {
+ if(WasteBits(pWork, 4))
+ return 0x306;
+
+ value = pWork->offs2D34[pWork->bit_buff & 0xFF];
+ }
+ else
+ {
+ if(WasteBits(pWork, 6))
+ return 0x306;
+
+ value = pWork->offs2E34[pWork->bit_buff & 0x7F];
+ }
+ }
+ }
+ else
+ {
+ if(WasteBits(pWork, 8))
+ return 0x306;
+
+ value = pWork->offs2EB4[pWork->bit_buff & 0xFF];
+ }
+
+ return WasteBits(pWork, pWork->ChBitsAsc[value]) ? 0x306 : value;
+}
+
+//-----------------------------------------------------------------------------
+// Decodes the distance of the repetition, backwards relative to the
+// current output buffer position
+
+static unsigned int DecodeDist(TDcmpStruct * pWork, unsigned int rep_length)
+{
+ unsigned int dist_pos_code; // Distance position code
+ unsigned int dist_pos_bits; // Number of bits of distance position
+ unsigned int distance; // Distance position
+
+ // Next 2-8 bits in the input buffer is the distance position code
+ dist_pos_code = pWork->DistPosCodes[pWork->bit_buff & 0xFF];
+ dist_pos_bits = pWork->DistBits[dist_pos_code];
+ if(WasteBits(pWork, dist_pos_bits))
+ return 0;
+
+ if(rep_length == 2)
+ {
+ // If the repetition is only 2 bytes length,
+ // then take 2 bits from the stream in order to get the distance
+ distance = (dist_pos_code << 2) | (pWork->bit_buff & 0x03);
+ if(WasteBits(pWork, 2))
+ return 0;
+ }
+ else
+ {
+ // If the repetition is more than 2 bytes length,
+ // then take "dsize_bits" bits in order to get the distance
+ distance = (dist_pos_code << pWork->dsize_bits) | (pWork->bit_buff & pWork->dsize_mask);
+ if(WasteBits(pWork, pWork->dsize_bits))
+ return 0;
+ }
+ return distance + 1;
+}
+
+static unsigned int Expand(TDcmpStruct * pWork)
+{
+ unsigned int next_literal; // Literal decoded from the compressed data
+ unsigned int result; // Value to be returned
+ unsigned int copyBytes; // Number of bytes to copy to the output buffer
+
+ pWork->outputPos = 0x1000; // Initialize output buffer position
+
+ // Decode the next literal from the input data.
+ // The returned literal can either be an uncompressed byte (next_literal < 0x100)
+ // or an encoded length of the repeating byte sequence that
+ // is to be copied to the current buffer position
+ while((result = next_literal = DecodeLit(pWork)) < 0x305)
+ {
+ // If the literal is greater than 0x100, it holds length
+ // of repeating byte sequence
+ // literal of 0x100 means repeating sequence of 0x2 bytes
+ // literal of 0x101 means repeating sequence of 0x3 bytes
+ // ...
+ // literal of 0x305 means repeating sequence of 0x207 bytes
+ if(next_literal >= 0x100)
+ {
+ unsigned char * source;
+ unsigned char * target;
+ unsigned int rep_length; // Length of the repetition, in bytes
+ unsigned int minus_dist; // Backward distance to the repetition, relative to the current buffer position
+
+ // Get the length of the repeating sequence.
+ // Note that the repeating block may overlap the current output position,
+ // for example if there was a sequence of equal bytes
+ rep_length = next_literal - 0xFE;
+
+ // Get backward distance to the repetition
+ if((minus_dist = DecodeDist(pWork, rep_length)) == 0)
+ {
+ result = 0x306;
+ break;
+ }
+
+ // Target and source pointer
+ target = &pWork->out_buff[pWork->outputPos];
+ source = target - minus_dist;
+
+ // Update buffer output position
+ pWork->outputPos += rep_length;
+
+ // Copy the repeating sequence
+ while(rep_length-- > 0)
+ *target++ = *source++;
+ }
+ else
+ {
+ pWork->out_buff[pWork->outputPos++] = (unsigned char)next_literal;
+ }
+
+ // Flush the output buffer, if number of extracted bytes has reached the end
+ if(pWork->outputPos >= 0x2000)
+ {
+ // Copy decompressed data into user buffer
+ copyBytes = 0x1000;
+ pWork->write_buf((char *)&pWork->out_buff[0x1000], ©Bytes, pWork->param);
+
+ // Now copy the decompressed data to the first half of the buffer.
+ // This is needed because the decompression might reuse them as repetitions.
+ // Note that if the output buffer overflowed previously, the extra decompressed bytes
+ // are stored in "out_buff_overflow", and they will now be
+ // within decompressed part of the output buffer.
+ memmove(pWork->out_buff, &pWork->out_buff[0x1000], pWork->outputPos - 0x1000);
+ pWork->outputPos -= 0x1000;
+ }
+ }
+
+ // Flush any remaining decompressed bytes
+ copyBytes = pWork->outputPos - 0x1000;
+ pWork->write_buf((char *)&pWork->out_buff[0x1000], ©Bytes, pWork->param);
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+// Main exploding function.
+
+unsigned int explode(
+ unsigned int (*read_buf)(char *buf, unsigned int *size, void *param),
+ void (*write_buf)(char *buf, unsigned int *size, void *param),
+ char *work_buf,
+ void *param)
+{
+ TDcmpStruct * pWork = (TDcmpStruct *)work_buf;
+
+ // Initialize work struct and load compressed data
+ // Note: The caller must zero the "work_buff" before passing it to explode
+ pWork->read_buf = read_buf;
+ pWork->write_buf = write_buf;
+ pWork->param = param;
+ pWork->in_pos = sizeof(pWork->in_buff);
+ pWork->in_bytes = pWork->read_buf((char *)pWork->in_buff, &pWork->in_pos, pWork->param);
+ if(pWork->in_bytes <= 4)
+ return CMP_BAD_DATA;
+
+ pWork->ctype = pWork->in_buff[0]; // Get the compression type (CMP_BINARY or CMP_ASCII)
+ pWork->dsize_bits = pWork->in_buff[1]; // Get the dictionary size
+ pWork->bit_buff = pWork->in_buff[2]; // Initialize 16-bit bit buffer
+ pWork->extra_bits = 0; // Extra (over 8) bits
+ pWork->in_pos = 3; // Position in input buffer
+
+ // Test for the valid dictionary size
+ if(4 > pWork->dsize_bits || pWork->dsize_bits > 6)
+ return CMP_INVALID_DICTSIZE;
+
+ pWork->dsize_mask = 0xFFFF >> (0x10 - pWork->dsize_bits); // Shifted by 'sar' instruction
+
+ if(pWork->ctype != CMP_BINARY)
+ {
+ if(pWork->ctype != CMP_ASCII)
+ return CMP_INVALID_MODE;
+
+ memcpy(pWork->ChBitsAsc, ChBitsAsc, sizeof(pWork->ChBitsAsc));
+ GenAscTabs(pWork);
+ }
+
+ memcpy(pWork->LenBits, LenBits, sizeof(pWork->LenBits));
+ GenDecodeTabs(pWork->LengthCodes, LenCode, pWork->LenBits, sizeof(pWork->LenBits));
+ memcpy(pWork->ExLenBits, ExLenBits, sizeof(pWork->ExLenBits));
+ memcpy(pWork->LenBase, LenBase, sizeof(pWork->LenBase));
+ memcpy(pWork->DistBits, DistBits, sizeof(pWork->DistBits));
+ GenDecodeTabs(pWork->DistPosCodes, DistCode, pWork->DistBits, sizeof(pWork->DistBits));
+ if(Expand(pWork) != 0x306)
+ return CMP_NO_ERROR;
+
+ return CMP_ABORT;
+}
diff --git a/dep/StormLib/src/pklib/implode.c b/dep/StormLib/src/pklib/implode.c
new file mode 100644
index 000000000..f29f54d64
--- /dev/null
+++ b/dep/StormLib/src/pklib/implode.c
@@ -0,0 +1,769 @@
+/*****************************************************************************/
+/* implode.c Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Implode function of PKWARE Data Compression library */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 11.04.03 1.00 Lad First version of implode.c */
+/* 02.05.03 1.00 Lad Stress test done */
+/* 22.04.10 1.01 Lad Documented */
+/*****************************************************************************/
+
+#include
+#include
+
+#include "pklib.h"
+
+#if ((1200 < _MSC_VER) && (_MSC_VER < 1400))
+#pragma optimize("", off) // Fucking Microsoft VS.NET 2003 compiler !!! (_MSC_VER=1310)
+#endif
+
+//-----------------------------------------------------------------------------
+// Defines
+
+#define MAX_REP_LENGTH 0x204 // The longest allowed repetition
+
+//-----------------------------------------------------------------------------
+// Tables
+
+static unsigned char DistBits[] =
+{
+ 0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
+};
+
+static unsigned char DistCode[] =
+{
+ 0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
+ 0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
+ 0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
+ 0xF0, 0x70, 0xB0, 0x30, 0xD0, 0x50, 0x90, 0x10, 0xE0, 0x60, 0xA0, 0x20, 0xC0, 0x40, 0x80, 0x00
+};
+
+static unsigned char ExLenBits[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
+};
+
+static unsigned char LenBits[] =
+{
+ 0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07
+};
+
+static unsigned char LenCode[] =
+{
+ 0x05, 0x03, 0x01, 0x06, 0x0A, 0x02, 0x0C, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00
+};
+
+static unsigned char ChBitsAsc[] =
+{
+ 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x07, 0x0C, 0x0C, 0x07, 0x0C, 0x0C,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x04, 0x0A, 0x08, 0x0C, 0x0A, 0x0C, 0x0A, 0x08, 0x07, 0x07, 0x08, 0x09, 0x07, 0x06, 0x07, 0x08,
+ 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x07, 0x07, 0x08, 0x08, 0x0C, 0x0B, 0x07, 0x09, 0x0B,
+ 0x0C, 0x06, 0x07, 0x06, 0x06, 0x05, 0x07, 0x08, 0x08, 0x06, 0x0B, 0x09, 0x06, 0x07, 0x06, 0x06,
+ 0x07, 0x0B, 0x06, 0x06, 0x06, 0x07, 0x09, 0x08, 0x09, 0x09, 0x0B, 0x08, 0x0B, 0x09, 0x0C, 0x08,
+ 0x0C, 0x05, 0x06, 0x06, 0x06, 0x05, 0x06, 0x06, 0x06, 0x05, 0x0B, 0x07, 0x05, 0x06, 0x05, 0x05,
+ 0x06, 0x0A, 0x05, 0x05, 0x05, 0x05, 0x08, 0x07, 0x08, 0x08, 0x0A, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C,
+ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
+ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
+ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
+ 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0C, 0x0D,
+ 0x0D, 0x0D, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D
+};
+
+static unsigned short ChCodeAsc[] =
+{
+ 0x0490, 0x0FE0, 0x07E0, 0x0BE0, 0x03E0, 0x0DE0, 0x05E0, 0x09E0,
+ 0x01E0, 0x00B8, 0x0062, 0x0EE0, 0x06E0, 0x0022, 0x0AE0, 0x02E0,
+ 0x0CE0, 0x04E0, 0x08E0, 0x00E0, 0x0F60, 0x0760, 0x0B60, 0x0360,
+ 0x0D60, 0x0560, 0x1240, 0x0960, 0x0160, 0x0E60, 0x0660, 0x0A60,
+ 0x000F, 0x0250, 0x0038, 0x0260, 0x0050, 0x0C60, 0x0390, 0x00D8,
+ 0x0042, 0x0002, 0x0058, 0x01B0, 0x007C, 0x0029, 0x003C, 0x0098,
+ 0x005C, 0x0009, 0x001C, 0x006C, 0x002C, 0x004C, 0x0018, 0x000C,
+ 0x0074, 0x00E8, 0x0068, 0x0460, 0x0090, 0x0034, 0x00B0, 0x0710,
+ 0x0860, 0x0031, 0x0054, 0x0011, 0x0021, 0x0017, 0x0014, 0x00A8,
+ 0x0028, 0x0001, 0x0310, 0x0130, 0x003E, 0x0064, 0x001E, 0x002E,
+ 0x0024, 0x0510, 0x000E, 0x0036, 0x0016, 0x0044, 0x0030, 0x00C8,
+ 0x01D0, 0x00D0, 0x0110, 0x0048, 0x0610, 0x0150, 0x0060, 0x0088,
+ 0x0FA0, 0x0007, 0x0026, 0x0006, 0x003A, 0x001B, 0x001A, 0x002A,
+ 0x000A, 0x000B, 0x0210, 0x0004, 0x0013, 0x0032, 0x0003, 0x001D,
+ 0x0012, 0x0190, 0x000D, 0x0015, 0x0005, 0x0019, 0x0008, 0x0078,
+ 0x00F0, 0x0070, 0x0290, 0x0410, 0x0010, 0x07A0, 0x0BA0, 0x03A0,
+ 0x0240, 0x1C40, 0x0C40, 0x1440, 0x0440, 0x1840, 0x0840, 0x1040,
+ 0x0040, 0x1F80, 0x0F80, 0x1780, 0x0780, 0x1B80, 0x0B80, 0x1380,
+ 0x0380, 0x1D80, 0x0D80, 0x1580, 0x0580, 0x1980, 0x0980, 0x1180,
+ 0x0180, 0x1E80, 0x0E80, 0x1680, 0x0680, 0x1A80, 0x0A80, 0x1280,
+ 0x0280, 0x1C80, 0x0C80, 0x1480, 0x0480, 0x1880, 0x0880, 0x1080,
+ 0x0080, 0x1F00, 0x0F00, 0x1700, 0x0700, 0x1B00, 0x0B00, 0x1300,
+ 0x0DA0, 0x05A0, 0x09A0, 0x01A0, 0x0EA0, 0x06A0, 0x0AA0, 0x02A0,
+ 0x0CA0, 0x04A0, 0x08A0, 0x00A0, 0x0F20, 0x0720, 0x0B20, 0x0320,
+ 0x0D20, 0x0520, 0x0920, 0x0120, 0x0E20, 0x0620, 0x0A20, 0x0220,
+ 0x0C20, 0x0420, 0x0820, 0x0020, 0x0FC0, 0x07C0, 0x0BC0, 0x03C0,
+ 0x0DC0, 0x05C0, 0x09C0, 0x01C0, 0x0EC0, 0x06C0, 0x0AC0, 0x02C0,
+ 0x0CC0, 0x04C0, 0x08C0, 0x00C0, 0x0F40, 0x0740, 0x0B40, 0x0340,
+ 0x0300, 0x0D40, 0x1D00, 0x0D00, 0x1500, 0x0540, 0x0500, 0x1900,
+ 0x0900, 0x0940, 0x1100, 0x0100, 0x1E00, 0x0E00, 0x0140, 0x1600,
+ 0x0600, 0x1A00, 0x0E40, 0x0640, 0x0A40, 0x0A00, 0x1200, 0x0200,
+ 0x1C00, 0x0C00, 0x1400, 0x0400, 0x1800, 0x0800, 0x1000, 0x0000
+};
+
+//-----------------------------------------------------------------------------
+// Macros
+
+// Macro for calculating hash of the current byte pair.
+// Note that most exact byte pair hash would be buffer[0] + buffer[1] << 0x08,
+// but even this way gives nice indication of equal byte pairs, with significantly
+// smaller size of the array that holds numbers of those hashes
+#define BYTE_PAIR_HASH(buffer) ((buffer[0] * 4) + (buffer[1] * 5))
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+// Builds the "hash_to_index" table and "pair_hash_offsets" table.
+// Every element of "hash_to_index" will contain lowest index to the
+// "pair_hash_offsets" table, effectively giving offset of the first
+// occurence of the given PAIR_HASH in the input data.
+static void SortBuffer(TCmpStruct * pWork, unsigned char * buffer_begin, unsigned char * buffer_end)
+{
+ unsigned short * phash_to_index;
+ unsigned char * buffer_ptr;
+ unsigned short total_sum = 0;
+ unsigned long byte_pair_hash; // Hash value of the byte pair
+ unsigned short byte_pair_offs; // Offset of the byte pair, relative to "work_buff"
+
+ // Zero the entire "phash_to_index" table
+ memset(pWork->phash_to_index, 0, sizeof(pWork->phash_to_index));
+
+ // Step 1: Count amount of each PAIR_HASH in the input buffer
+ // The table will look like this:
+ // offs 0x000: Number of occurences of PAIR_HASH 0
+ // offs 0x001: Number of occurences of PAIR_HASH 1
+ // ...
+ // offs 0x8F7: Number of occurences of PAIR_HASH 0x8F7 (the highest hash value)
+ for(buffer_ptr = buffer_begin; buffer_ptr < buffer_end; buffer_ptr++)
+ pWork->phash_to_index[BYTE_PAIR_HASH(buffer_ptr)]++;
+
+ // Step 2: Convert the table to the array of PAIR_HASH amounts.
+ // Each element contains count of PAIR_HASHes that is less or equal
+ // to element index
+ // The table will look like this:
+ // offs 0x000: Number of occurences of PAIR_HASH 0 or lower
+ // offs 0x001: Number of occurences of PAIR_HASH 1 or lower
+ // ...
+ // offs 0x8F7: Number of occurences of PAIR_HASH 0x8F7 or lower
+ for(phash_to_index = pWork->phash_to_index; phash_to_index < &pWork->phash_to_index_end; phash_to_index++)
+ {
+ total_sum = total_sum + phash_to_index[0];
+ phash_to_index[0] = total_sum;
+ }
+
+ // Step 3: Convert the table to the array of indexes.
+ // Now, each element contains index to the first occurence of given PAIR_HASH
+ for(buffer_end--; buffer_end >= buffer_begin; buffer_end--)
+ {
+ byte_pair_hash = BYTE_PAIR_HASH(buffer_end);
+ byte_pair_offs = (unsigned short)(buffer_end - pWork->work_buff);
+
+ pWork->phash_to_index[byte_pair_hash]--;
+ pWork->phash_offs[pWork->phash_to_index[byte_pair_hash]] = byte_pair_offs;
+ }
+}
+
+static void FlushBuf(TCmpStruct * pWork)
+{
+ unsigned char save_ch1;
+ unsigned char save_ch2;
+ unsigned int size = 0x800;
+
+ pWork->write_buf(pWork->out_buff, &size, pWork->param);
+
+ save_ch1 = pWork->out_buff[0x800];
+ save_ch2 = pWork->out_buff[pWork->out_bytes];
+ pWork->out_bytes -= 0x800;
+
+ memset(pWork->out_buff, 0, sizeof(pWork->out_buff));
+
+ if(pWork->out_bytes != 0)
+ pWork->out_buff[0] = save_ch1;
+ if(pWork->out_bits != 0)
+ pWork->out_buff[pWork->out_bytes] = save_ch2;
+}
+
+static void OutputBits(TCmpStruct * pWork, unsigned int nbits, unsigned long bit_buff)
+{
+ unsigned int out_bits;
+
+ // If more than 8 bits to output, do recursion
+ if(nbits > 8)
+ {
+ OutputBits(pWork, 8, bit_buff);
+ bit_buff >>= 8;
+ nbits -= 8;
+ }
+
+ // Add bits to the last out byte in out_buff;
+ out_bits = pWork->out_bits;
+ pWork->out_buff[pWork->out_bytes] |= (unsigned char)(bit_buff << out_bits);
+ pWork->out_bits += nbits;
+
+ // If 8 or more bits, increment number of bytes
+ if(pWork->out_bits > 8)
+ {
+ pWork->out_bytes++;
+ bit_buff >>= (8 - out_bits);
+
+ pWork->out_buff[pWork->out_bytes] = (unsigned char)bit_buff;
+ pWork->out_bits &= 7;
+ }
+ else
+ {
+ pWork->out_bits &= 7;
+ if(pWork->out_bits == 0)
+ pWork->out_bytes++;
+ }
+
+ // If there is enough compressed bytes, flush them
+ if(pWork->out_bytes >= 0x800)
+ FlushBuf(pWork);
+}
+
+// This function searches for a repetition
+// (a previous occurence of the current byte sequence)
+// Returns length of the repetition, and stores the backward distance
+// to pWork structure.
+static unsigned int FindRep(TCmpStruct * pWork, unsigned char * input_data)
+{
+ unsigned short * phash_to_index; // Pointer into pWork->phash_to_index table
+ unsigned short * phash_offs; // Pointer to the table containing offsets of each PAIR_HASH
+ unsigned char * repetition_limit; // An eventual repetition must be at position below this pointer
+ unsigned char * prev_repetition; // Pointer to the previous occurence of the current PAIR_HASH
+ unsigned char * prev_rep_end; // End of the previous repetition
+ unsigned char * input_data_ptr;
+ unsigned short phash_offs_index; // Index to the table with PAIR_HASH positions
+ unsigned short min_phash_offs; // The lowest allowed hash offset
+ unsigned short offs_in_rep; // Offset within found repetition
+ unsigned int equal_byte_count; // Number of bytes that are equal to the previous occurence
+ unsigned int rep_length = 1; // Length of the found repetition
+ unsigned int rep_length2; // Secondary repetition
+ unsigned char pre_last_byte; // Last but one byte from a repetion
+ unsigned short di_val;
+
+ // Calculate the previous position of the PAIR_HASH
+ phash_to_index = pWork->phash_to_index + BYTE_PAIR_HASH(input_data);
+ min_phash_offs = (unsigned short)((input_data - pWork->work_buff) - pWork->dsize_bytes + 1);
+ phash_offs_index = phash_to_index[0];
+
+ // If the PAIR_HASH offset is below the limit, find a next one
+ phash_offs = pWork->phash_offs + phash_offs_index;
+ if(*phash_offs < min_phash_offs)
+ {
+ while(*phash_offs < min_phash_offs)
+ {
+ phash_offs_index++;
+ phash_offs++;
+ }
+ *phash_to_index = phash_offs_index;
+ }
+
+ // Get the first location of the PAIR_HASH,
+ // and thus the first eventual location of byte repetition
+ phash_offs = pWork->phash_offs + phash_offs_index;
+ prev_repetition = pWork->work_buff + phash_offs[0];
+ repetition_limit = input_data - 1;
+
+ // If the current PAIR_HASH was not encountered before,
+ // we haven't found a repetition.
+ if(prev_repetition >= repetition_limit)
+ return 0;
+
+ // We have found a match of a PAIR_HASH. Now we have to make sure
+ // that it is also a byte match, because PAIR_HASH is not unique.
+ // We compare the bytes and count the length of the repetition
+ input_data_ptr = input_data;
+ for(;;)
+ {
+ // If the first byte of the repetition and the so-far-last byte
+ // of the repetition are equal, we will compare the blocks.
+ if(*input_data_ptr == *prev_repetition && input_data_ptr[rep_length-1] == prev_repetition[rep_length-1])
+ {
+ // Skip the current byte
+ prev_repetition++;
+ input_data_ptr++;
+ equal_byte_count = 2;
+
+ // Now count how many more bytes are equal
+ while(equal_byte_count < MAX_REP_LENGTH)
+ {
+ prev_repetition++;
+ input_data_ptr++;
+
+ // Are the bytes different ?
+ if(*prev_repetition != *input_data_ptr)
+ break;
+
+ equal_byte_count++;
+ }
+
+ // If we found a repetition of at least the same length, take it.
+ // If there are multiple repetitions in the input buffer, this will
+ // make sure that we find the most recent one, which in turn allows
+ // us to store backward length in less amount of bits
+ input_data_ptr = input_data;
+ if(equal_byte_count >= rep_length)
+ {
+ // Calculate the backward distance of the repetition.
+ // Note that the distance is stored as decremented by 1
+ pWork->distance = (unsigned int)(input_data - prev_repetition + equal_byte_count - 1);
+
+ // Repetitions longer than 10 bytes will be stored in more bits,
+ // so they need a bit different handling
+ if((rep_length = equal_byte_count) > 10)
+ break;
+ }
+ }
+
+ // Move forward in the table of PAIR_HASH repetitions.
+ // There might be a more recent occurence of the same repetition.
+ phash_offs_index++;
+ phash_offs++;
+ prev_repetition = pWork->work_buff + phash_offs[0];
+
+ // If the next repetition is beyond the minimum allowed repetition, we are done.
+ if(prev_repetition >= repetition_limit)
+ {
+ // A repetition must have at least 2 bytes, otherwise it's not worth it
+ return (rep_length >= 2) ? rep_length : 0;
+ }
+ }
+
+ // If the repetition has max length of 0x204 bytes, we can't go any fuhrter
+ if(equal_byte_count == MAX_REP_LENGTH)
+ {
+ pWork->distance--;
+ return equal_byte_count;
+ }
+
+ // Check for possibility of a repetition that occurs at more recent position
+ phash_offs = pWork->phash_offs + phash_offs_index;
+ if(pWork->work_buff + phash_offs[1] >= repetition_limit)
+ return rep_length;
+
+ //
+ // The following part checks if there isn't a longer repetition at
+ // a latter offset, that would lead to better compression.
+ //
+ // Example of data that can trigger this optimization:
+ //
+ // "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEQQQQQQQQQQQQ"
+ // "XYZ"
+ // "EEEEEEEEEEEEEEEEQQQQQQQQQQQQ";
+ //
+ // Description of data in this buffer
+ // [0x00] Single byte "E"
+ // [0x01] Single byte "E"
+ // [0x02] Repeat 0x1E bytes from [0x00]
+ // [0x20] Single byte "X"
+ // [0x21] Single byte "Y"
+ // [0x22] Single byte "Z"
+ // [0x23] 17 possible previous repetitions of length at least 0x10 bytes:
+ // - Repetition of 0x10 bytes from [0x00] "EEEEEEEEEEEEEEEE"
+ // - Repetition of 0x10 bytes from [0x01] "EEEEEEEEEEEEEEEE"
+ // - Repetition of 0x10 bytes from [0x02] "EEEEEEEEEEEEEEEE"
+ // ...
+ // - Repetition of 0x10 bytes from [0x0F] "EEEEEEEEEEEEEEEE"
+ // - Repetition of 0x1C bytes from [0x10] "EEEEEEEEEEEEEEEEQQQQQQQQQQQQ"
+ // The last repetition is the best one.
+ //
+
+ pWork->offs09BC[0] = 0xFFFF;
+ pWork->offs09BC[1] = 0x0000;
+ di_val = 0;
+
+ // Note: I failed to figure out what does the table "offs09BC" mean.
+ // If anyone has an idea, let me know to zezula_at_volny_dot_cz
+ for(offs_in_rep = 1; offs_in_rep < rep_length; )
+ {
+ if(input_data[offs_in_rep] != input_data[di_val])
+ {
+ di_val = pWork->offs09BC[di_val];
+ if(di_val != 0xFFFF)
+ continue;
+ }
+ pWork->offs09BC[++offs_in_rep] = ++di_val;
+ }
+
+ //
+ // Now go through all the repetitions from the first found one
+ // to the current input data, and check if any of them migh be
+ // a start of a greater sequence match.
+ //
+
+ prev_repetition = pWork->work_buff + phash_offs[0];
+ prev_rep_end = prev_repetition + rep_length;
+ rep_length2 = rep_length;
+
+ for(;;)
+ {
+ rep_length2 = pWork->offs09BC[rep_length2];
+ if(rep_length2 == 0xFFFF)
+ rep_length2 = 0;
+
+ // Get the pointer to the previous repetition
+ phash_offs = pWork->phash_offs + phash_offs_index;
+
+ // Skip those repetitions that don't reach the end
+ // of the first found repetition
+ do
+ {
+ phash_offs++;
+ phash_offs_index++;
+ prev_repetition = pWork->work_buff + *phash_offs;
+ if(prev_repetition >= repetition_limit)
+ return rep_length;
+ }
+ while(prev_repetition + rep_length2 < prev_rep_end);
+
+ // Verify if the last but one byte from the repetition matches
+ // the last but one byte from the input data.
+ // If not, find a next repetition
+ pre_last_byte = input_data[rep_length - 2];
+ if(pre_last_byte == prev_repetition[rep_length - 2])
+ {
+ // If the new repetition reaches beyond the end
+ // of previously found repetition, reset the repetition length to zero.
+ if(prev_repetition + rep_length2 != prev_rep_end)
+ {
+ prev_rep_end = prev_repetition;
+ rep_length2 = 0;
+ }
+ }
+ else
+ {
+ phash_offs = pWork->phash_offs + phash_offs_index;
+ do
+ {
+ phash_offs++;
+ phash_offs_index++;
+ prev_repetition = pWork->work_buff + *phash_offs;
+ if(prev_repetition >= repetition_limit)
+ return rep_length;
+ }
+ while(prev_repetition[rep_length - 2] != pre_last_byte || prev_repetition[0] != input_data[0]);
+
+ // Reset the length of the repetition to 2 bytes only
+ prev_rep_end = prev_repetition + 2;
+ rep_length2 = 2;
+ }
+
+ // Find out how many more characters are equal to the first repetition.
+ while(*prev_rep_end == input_data[rep_length2])
+ {
+ if(++rep_length2 >= 0x204)
+ break;
+ prev_rep_end++;
+ }
+
+ // Is the newly found repetion at least as long as the previous one ?
+ if(rep_length2 >= rep_length)
+ {
+ // Calculate the distance of the new repetition
+ pWork->distance = (unsigned int)(input_data - prev_repetition - 1);
+ if((rep_length = rep_length2) == 0x204)
+ return rep_length;
+
+ // Update the additional elements in the "offs09BC" table
+ // to reflect new rep length
+ while(offs_in_rep < rep_length2)
+ {
+ if(input_data[offs_in_rep] != input_data[di_val])
+ {
+ di_val = pWork->offs09BC[di_val];
+ if(di_val != 0xFFFF)
+ continue;
+ }
+ pWork->offs09BC[++offs_in_rep] = ++di_val;
+ }
+ }
+ }
+}
+
+static void WriteCmpData(TCmpStruct * pWork)
+{
+ unsigned char * input_data_end; // Pointer to the end of the input data
+ unsigned char * input_data = pWork->work_buff + pWork->dsize_bytes + 0x204;
+ unsigned int input_data_ended = 0; // If 1, then all data from the input stream have been already loaded
+ unsigned int save_rep_length; // Saved length of current repetition
+ unsigned int save_distance = 0; // Saved distance of current repetition
+ unsigned int rep_length; // Length of the found repetition
+ unsigned int phase = 0; //
+
+ // Store the compression type and dictionary size
+ pWork->out_buff[0] = (char)pWork->ctype;
+ pWork->out_buff[1] = (char)pWork->dsize_bits;
+ pWork->out_bytes = 2;
+
+ // Reset output buffer to zero
+ memset(&pWork->out_buff[2], 0, sizeof(pWork->out_buff) - 2);
+ pWork->out_bits = 0;
+
+ while(input_data_ended == 0)
+ {
+ unsigned int bytes_to_load = 0x1000;
+ int total_loaded = 0;
+ int bytes_loaded;
+
+ // Load the bytes from the input stream, up to 0x1000 bytes
+ while(bytes_to_load != 0)
+ {
+ bytes_loaded = pWork->read_buf((char *)pWork->work_buff + pWork->dsize_bytes + 0x204 + total_loaded,
+ &bytes_to_load,
+ pWork->param);
+ if(bytes_loaded == 0)
+ {
+ if(total_loaded == 0 && phase == 0)
+ goto __Exit;
+ input_data_ended = 1;
+ break;
+ }
+ else
+ {
+ bytes_to_load -= bytes_loaded;
+ total_loaded += bytes_loaded;
+ }
+ }
+
+ input_data_end = pWork->work_buff + pWork->dsize_bytes + total_loaded;
+ if(input_data_ended)
+ input_data_end += 0x204;
+
+ //
+ // Warning: The end of the buffer passed to "SortBuffer" is actually 2 bytes beyond
+ // valid data. It is questionable if this is actually a bug or not,
+ // but it might cause the compressed data output to be dependent on random bytes
+ // that are in the buffer.
+ // To prevent that, the calling application must always zero the compression
+ // buffer before passing it to "implode"
+ //
+
+ // Search the PAIR_HASHes of the loaded blocks. Also, include
+ // previously compressed data, if any.
+ switch(phase)
+ {
+ case 0:
+ SortBuffer(pWork, input_data, input_data_end + 1);
+ phase++;
+ if(pWork->dsize_bytes != 0x1000)
+ phase++;
+ break;
+
+ case 1:
+ SortBuffer(pWork, input_data - pWork->dsize_bytes + 0x204, input_data_end + 1);
+ phase++;
+ break;
+
+ default:
+ SortBuffer(pWork, input_data - pWork->dsize_bytes, input_data_end + 1);
+ break;
+ }
+
+ // Perform the compression of the current block
+ while(input_data < input_data_end)
+ {
+ // Find if the current byte sequence wasn't there before.
+ rep_length = FindRep(pWork, input_data);
+ while(rep_length != 0)
+ {
+ // If we found repetition of 2 bytes, that is 0x100 or fuhrter back,
+ // don't bother. Storing the distance of 0x100 bytes would actually
+ // take more space than storing the 2 bytes as-is.
+ if(rep_length == 2 && pWork->distance >= 0x100)
+ break;
+
+ // When we are at the end of the input data, we cannot allow
+ // the repetition to go past the end of the input data.
+ if(input_data_ended && input_data + rep_length > input_data_end)
+ {
+ // Shorten the repetition length so that it only covers valid data
+ rep_length = (unsigned long)(input_data_end - input_data);
+ if(rep_length < 2)
+ break;
+
+ // If we got repetition of 2 bytes, that is 0x100 or more backward, don't bother
+ if(rep_length == 2 && pWork->distance >= 0x100)
+ break;
+ goto __FlushRepetition;
+ }
+
+ if(rep_length >= 8 || input_data + 1 >= input_data_end)
+ goto __FlushRepetition;
+
+ // Try to find better repetition 1 byte later.
+ // Example: "ARROCKFORT" "AROCKFORT"
+ // When "input_data" points to the second string, FindRep
+ // returns the occurence of "AR". But there is longer repetition "ROCKFORT",
+ // beginning 1 byte after.
+ save_rep_length = rep_length;
+ save_distance = pWork->distance;
+ rep_length = FindRep(pWork, input_data + 1);
+
+ // Only use the new repetition if it's length is greater than the previous one
+ if(rep_length > save_rep_length)
+ {
+ // If the new repetition if only 1 byte better
+ // and the previous distance is less than 0x80 bytes, use the previous repetition
+ if(rep_length > save_rep_length + 1 || save_distance > 0x80)
+ {
+ // Flush one byte, so that input_data will point to the secondary repetition
+ OutputBits(pWork, pWork->nChBits[*input_data], pWork->nChCodes[*input_data]);
+ input_data++;
+ continue;
+ }
+ }
+
+ // Revert to the previous repetition
+ rep_length = save_rep_length;
+ pWork->distance = save_distance;
+
+ __FlushRepetition:
+
+ OutputBits(pWork, pWork->nChBits[rep_length + 0xFE], pWork->nChCodes[rep_length + 0xFE]);
+ if(rep_length == 2)
+ {
+ OutputBits(pWork, pWork->dist_bits[pWork->distance >> 2],
+ pWork->dist_codes[pWork->distance >> 2]);
+ OutputBits(pWork, 2, pWork->distance & 3);
+ }
+ else
+ {
+ OutputBits(pWork, pWork->dist_bits[pWork->distance >> pWork->dsize_bits],
+ pWork->dist_codes[pWork->distance >> pWork->dsize_bits]);
+ OutputBits(pWork, pWork->dsize_bits, pWork->dsize_mask & pWork->distance);
+ }
+
+ // Move the begin of the input data by the length of the repetition
+ input_data += rep_length;
+ goto _00402252;
+ }
+
+ // If there was no previous repetition for the current position in the input data,
+ // just output the 9-bit literal for the one character
+ OutputBits(pWork, pWork->nChBits[*input_data], pWork->nChCodes[*input_data]);
+ input_data++;
+_00402252:;
+ }
+
+ if(input_data_ended == 0)
+ {
+ input_data -= 0x1000;
+ memmove(pWork->work_buff, pWork->work_buff + 0x1000, pWork->dsize_bytes + 0x204);
+ }
+ }
+
+__Exit:
+
+ // Write the termination literal
+ OutputBits(pWork, pWork->nChBits[0x305], pWork->nChCodes[0x305]);
+ if(pWork->out_bits != 0)
+ pWork->out_bytes++;
+ pWork->write_buf(pWork->out_buff, &pWork->out_bytes, pWork->param);
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Main imploding function
+
+unsigned int PKEXPORT implode(
+ unsigned int (*read_buf)(char *buf, unsigned int *size, void *param),
+ void (*write_buf)(char *buf, unsigned int *size, void *param),
+ char *work_buf,
+ void *param,
+ unsigned int *type,
+ unsigned int *dsize)
+{
+ TCmpStruct * pWork = (TCmpStruct *)work_buf;
+ unsigned int nChCode;
+ unsigned int nCount;
+ unsigned int i;
+ int nCount2;
+
+ // Fill the work buffer information
+ // Note: The caller must zero the "work_buff" before passing it to implode
+ pWork->read_buf = read_buf;
+ pWork->write_buf = write_buf;
+ pWork->dsize_bytes = *dsize;
+ pWork->ctype = *type;
+ pWork->param = param;
+ pWork->dsize_bits = 4;
+ pWork->dsize_mask = 0x0F;
+
+ // Test dictionary size
+ switch(*dsize)
+ {
+ case CMP_IMPLODE_DICT_SIZE3: // 0x1000 bytes
+ pWork->dsize_bits++;
+ pWork->dsize_mask |= 0x20;
+ // No break here !!!
+
+ case CMP_IMPLODE_DICT_SIZE2: // 0x800 bytes
+ pWork->dsize_bits++;
+ pWork->dsize_mask |= 0x10;
+ // No break here !!!
+
+ case CMP_IMPLODE_DICT_SIZE1: // 0x400
+ break;
+
+ default:
+ return CMP_INVALID_DICTSIZE;
+ }
+
+ // Test the compression type
+ switch(*type)
+ {
+ case CMP_BINARY: // We will compress data with binary compression type
+ for(nChCode = 0, nCount = 0; nCount < 0x100; nCount++)
+ {
+ pWork->nChBits[nCount] = 9;
+ pWork->nChCodes[nCount] = (unsigned short)nChCode;
+ nChCode = (nChCode & 0x0000FFFF) + 2;
+ }
+ break;
+
+
+ case CMP_ASCII: // We will compress data with ASCII compression type
+ for(nCount = 0; nCount < 0x100; nCount++)
+ {
+ pWork->nChBits[nCount] = (unsigned char )(ChBitsAsc[nCount] + 1);
+ pWork->nChCodes[nCount] = (unsigned short)(ChCodeAsc[nCount] * 2);
+ }
+ break;
+
+ default:
+ return CMP_INVALID_MODE;
+ }
+
+ for(i = 0; i < 0x10; i++)
+ {
+ if(1 << ExLenBits[i])
+ {
+ for(nCount2 = 0; nCount2 < (1 << ExLenBits[i]); nCount2++)
+ {
+ pWork->nChBits[nCount] = (unsigned char)(ExLenBits[i] + LenBits[i] + 1);
+ pWork->nChCodes[nCount] = (unsigned short)((nCount2 << (LenBits[i] + 1)) | ((LenCode[i] & 0xFFFF00FF) * 2) | 1);
+ nCount++;
+ }
+ }
+ }
+
+ // Copy the distance codes and distance bits and perform the compression
+ memcpy(&pWork->dist_codes, DistCode, sizeof(DistCode));
+ memcpy(&pWork->dist_bits, DistBits, sizeof(DistBits));
+ WriteCmpData(pWork);
+ return CMP_NO_ERROR;
+}
diff --git a/dep/StormLib/src/pklib/pklib.h b/dep/StormLib/src/pklib/pklib.h
new file mode 100644
index 000000000..9eb2915b7
--- /dev/null
+++ b/dep/StormLib/src/pklib/pklib.h
@@ -0,0 +1,146 @@
+/*****************************************************************************/
+/* pklib.h Copyright (c) Ladislav Zezula 2003 */
+/*---------------------------------------------------------------------------*/
+/* Header file for PKWARE Data Compression Library */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 31.03.03 1.00 Lad The first version of pkware.h */
+/*****************************************************************************/
+
+#ifndef __PKLIB_H__
+#define __PKLIB_H__
+
+//-----------------------------------------------------------------------------
+// Defines
+
+#define CMP_BINARY 0 // Binary compression
+#define CMP_ASCII 1 // Ascii compression
+
+#define CMP_NO_ERROR 0
+#define CMP_INVALID_DICTSIZE 1
+#define CMP_INVALID_MODE 2
+#define CMP_BAD_DATA 3
+#define CMP_ABORT 4
+
+#define CMP_IMPLODE_DICT_SIZE1 1024 // Dictionary size of 1024
+#define CMP_IMPLODE_DICT_SIZE2 2048 // Dictionary size of 2048
+#define CMP_IMPLODE_DICT_SIZE3 4096 // Dictionary size of 4096
+
+//-----------------------------------------------------------------------------
+// Define calling convention
+
+#ifndef PKEXPORT
+#ifdef WIN32
+#define PKEXPORT __cdecl // Use for normal __cdecl calling
+#else
+#define PKEXPORT
+#endif
+#endif
+
+//-----------------------------------------------------------------------------
+// Internal structures
+
+// Compression structure
+typedef struct
+{
+ unsigned int distance; // 0000: Backward distance of the currently found repetition, decreased by 1
+ unsigned int out_bytes; // 0004: # bytes available in out_buff
+ unsigned int out_bits; // 0008: # of bits available in the last out byte
+ unsigned int dsize_bits; // 000C: Number of bits needed for dictionary size. 4 = 0x400, 5 = 0x800, 6 = 0x1000
+ unsigned int dsize_mask; // 0010: Bit mask for dictionary. 0x0F = 0x400, 0x1F = 0x800, 0x3F = 0x1000
+ unsigned int ctype; // 0014: Compression type (CMP_ASCII or CMP_BINARY)
+ unsigned int dsize_bytes; // 0018: Dictionary size in bytes
+ unsigned char dist_bits[0x40]; // 001C: Distance bits
+ unsigned char dist_codes[0x40]; // 005C: Distance codes
+ unsigned char nChBits[0x306]; // 009C: Table of literal bit lengths to be put to the output stream
+ unsigned short nChCodes[0x306]; // 03A2: Table of literal codes to be put to the output stream
+ unsigned short offs09AE; // 09AE:
+
+ void * param; // 09B0: User parameter
+ unsigned int (*read_buf)(char *buf, unsigned int *size, void *param); // 9B4
+ void (*write_buf)(char *buf, unsigned int *size, void *param); // 9B8
+
+ unsigned short offs09BC[0x204]; // 09BC:
+ unsigned long offs0DC4; // 0DC4:
+ unsigned short phash_to_index[0x900]; // 0DC8: Array of indexes (one for each PAIR_HASH) to the "pair_hash_offsets" table
+ unsigned short phash_to_index_end; // 1FC8: End marker for "phash_to_index" table
+ char out_buff[0x802]; // 1FCA: Compressed data
+ unsigned char work_buff[0x2204]; // 27CC: Work buffer
+ // + DICT_OFFSET => Dictionary
+ // + UNCMP_OFFSET => Uncompressed data
+ unsigned short phash_offs[0x2204]; // 49D0: Table of offsets for each PAIR_HASH
+} TCmpStruct;
+
+#define CMP_BUFFER_SIZE sizeof(TCmpStruct) // Size of compression structure.
+ // Defined as 36312 in pkware header file
+
+
+// Decompression structure
+typedef struct
+{
+ unsigned long offs0000; // 0000
+ unsigned long ctype; // 0004: Compression type (CMP_BINARY or CMP_ASCII)
+ unsigned long outputPos; // 0008: Position in output buffer
+ unsigned long dsize_bits; // 000C: Dict size (4, 5, 6 for 0x400, 0x800, 0x1000)
+ unsigned long dsize_mask; // 0010: Dict size bitmask (0x0F, 0x1F, 0x3F for 0x400, 0x800, 0x1000)
+ unsigned long bit_buff; // 0014: 16-bit buffer for processing input data
+ unsigned long extra_bits; // 0018: Number of extra (above 8) bits in bit buffer
+ unsigned int in_pos; // 001C: Position in in_buff
+ unsigned long in_bytes; // 0020: Number of bytes in input buffer
+ void * param; // 0024: Custom parameter
+ unsigned int (*read_buf)(char *buf, unsigned int *size, void *param); // Pointer to function that reads data from the input stream
+ void (*write_buf)(char *buf, unsigned int *size, void *param);// Pointer to function that writes data to the output stream
+
+ unsigned char out_buff[0x2204]; // 0030: Output circle buffer.
+ // 0x0000 - 0x0FFF: Previous uncompressed data, kept for repetitions
+ // 0x1000 - 0x1FFF: Currently decompressed data
+ // 0x2000 - 0x2203: Reserve space for the longest possible repetition
+ unsigned char in_buff[0x800]; // 2234: Buffer for data to be decompressed
+ unsigned char DistPosCodes[0x100]; // 2A34: Table of distance position codes
+ unsigned char LengthCodes[0x100]; // 2B34: Table of length codes
+ unsigned char offs2C34[0x100]; // 2C34: Buffer for
+ unsigned char offs2D34[0x100]; // 2D34: Buffer for
+ unsigned char offs2E34[0x80]; // 2EB4: Buffer for
+ unsigned char offs2EB4[0x100]; // 2EB4: Buffer for
+ unsigned char ChBitsAsc[0x100]; // 2FB4: Buffer for
+ unsigned char DistBits[0x40]; // 30B4: Numbers of bytes to skip copied block length
+ unsigned char LenBits[0x10]; // 30F4: Numbers of bits for skip copied block length
+ unsigned char ExLenBits[0x10]; // 3104: Number of valid bits for copied block
+ unsigned short LenBase[0x10]; // 3114: Buffer for
+} TDcmpStruct;
+
+#define EXP_BUFFER_SIZE sizeof(TDcmpStruct) // Size of decompression structure
+ // Defined as 12596 in pkware headers
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+unsigned int PKEXPORT implode(
+ unsigned int (*read_buf)(char *buf, unsigned int *size, void *param),
+ void (*write_buf)(char *buf, unsigned int *size, void *param),
+ char *work_buf,
+ void *param,
+ unsigned int *type,
+ unsigned int *dsize);
+
+
+unsigned int PKEXPORT explode(
+ unsigned int (*read_buf)(char *buf, unsigned int *size, void *param),
+ void (*write_buf)(char *buf, unsigned int *size, void *param),
+ char *work_buf,
+ void *param);
+
+// The original name "crc32" was changed to "crc32pk" due
+// to compatibility with zlib
+unsigned long PKEXPORT crc32_pklib(char *buffer, unsigned int *size, unsigned long *old_crc);
+
+#ifdef __cplusplus
+ } // End of 'extern "C"' declaration
+#endif
+
+#endif // __PKLIB_H__
diff --git a/dep/StormLib/src/rsa/rsa_verify_simple.c b/dep/StormLib/src/rsa/rsa_verify_simple.c
new file mode 100644
index 000000000..74dd792ee
--- /dev/null
+++ b/dep/StormLib/src/rsa/rsa_verify_simple.c
@@ -0,0 +1,87 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis
+ *
+ * LibTomCrypt is a library that provides various cryptographic
+ * algorithms in a highly modular and flexible manner.
+ *
+ * The library is free for all purposes without any express
+ * guarantee it works.
+ *
+ * Tom St Denis, tomstdenis@gmail.com, http://libtom.org
+ */
+#include "rsa_verify_simple.h"
+
+/**
+ @file rsa_verify_simple.c
+ Created by Ladislav Zezula (zezula@volny.cz) as modification
+ for Blizzard strong signature verification
+*/
+
+#ifdef LTC_MRSA
+
+/**
+ Simple RSA decryption
+ @param sig The signature data
+ @param siglen The length of the signature data (octets)
+ @param hash The hash of the message that was signed
+ @param hashlen The length of the hash of the message that was signed (octets)
+ @param stat [out] The result of the signature comparison, 1==valid, 0==invalid
+ @param key The public RSA key corresponding
+ @return Error code
+*/
+int rsa_verify_simple(const unsigned char *sig, unsigned long siglen,
+ const unsigned char *hash, unsigned long hashlen,
+ int *stat,
+ rsa_key *key)
+{
+ unsigned long modulus_bitlen, modulus_bytelen, x;
+ unsigned char *tmpbuf;
+ int err;
+
+ LTC_ARGCHK(sig != NULL);
+ LTC_ARGCHK(hash != NULL);
+ LTC_ARGCHK(stat != NULL);
+ LTC_ARGCHK(key != NULL);
+
+ /* default to invalid */
+ *stat = 0;
+
+ /* get modulus len in bits */
+ modulus_bitlen = mp_count_bits( (key->N));
+
+ /* outlen must be at least the size of the modulus */
+ modulus_bytelen = mp_unsigned_bin_size( (key->N));
+ if (modulus_bytelen != siglen) {
+ return CRYPT_INVALID_PACKET;
+ }
+
+ /* allocate temp buffer for decoded sig */
+ tmpbuf = XMALLOC(siglen);
+ if (tmpbuf == NULL) {
+ return CRYPT_MEM;
+ }
+
+ /* RSA decode it */
+ x = siglen;
+ if ((err = ltc_mp.rsa_me(sig, siglen, tmpbuf, &x, PK_PUBLIC, key)) != CRYPT_OK) {
+ XFREE(tmpbuf);
+ return err;
+ }
+
+ /* make sure the output is the right size */
+ if (x != siglen) {
+ XFREE(tmpbuf);
+ return CRYPT_INVALID_PACKET;
+ }
+
+ /* compare the decrypted signature with the given hash */
+ if(x == hashlen && XMEMCMP(tmpbuf, hash, hashlen) == 0)
+ *stat = 1;
+
+#ifdef LTC_CLEAN_STACK
+ zeromem(tmpbuf, siglen);
+#endif
+ XFREE(tmpbuf);
+ return CRYPT_OK;
+}
+
+#endif /* LTC_MRSA */
diff --git a/dep/StormLib/src/rsa/rsa_verify_simple.h b/dep/StormLib/src/rsa/rsa_verify_simple.h
new file mode 100644
index 000000000..a87b98026
--- /dev/null
+++ b/dep/StormLib/src/rsa/rsa_verify_simple.h
@@ -0,0 +1,40 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis
+ *
+ * LibTomCrypt is a library that provides various cryptographic
+ * algorithms in a highly modular and flexible manner.
+ *
+ * The library is free for all purposes without any express
+ * guarantee it works.
+ *
+ * Tom St Denis, tomstdenis@gmail.com, http://libtom.org
+ */
+#ifndef __RSA_VERIFY_SIMPLE_H
+#define __RSA_VERIFY_SIMPLE_H
+
+#include "tomcrypt.h"
+
+/**
+ @file rsa_verify_simple.c
+ Created by Ladislav Zezula (zezula@volny.cz) as modification
+ for Blizzard strong signature verification
+*/
+
+#ifdef LTC_MRSA
+
+/**
+ Simple RSA decryption
+ @param sig The signature data
+ @param siglen The length of the signature data (octets)
+ @param hash The hash of the message that was signed
+ @param hashlen The length of the hash of the message that was signed (octets)
+ @param stat [out] The result of the signature comparison, 1==valid, 0==invalid
+ @param key The public RSA key corresponding
+ @return Error code
+*/
+int rsa_verify_simple(const unsigned char *sig, unsigned long siglen,
+ const unsigned char *hash, unsigned long hashlen,
+ int *stat,
+ rsa_key *key);
+#endif /* LTC_MRSA */
+
+#endif
\ No newline at end of file
diff --git a/dep/StormLib/src/sparse/sparse.cpp b/dep/StormLib/src/sparse/sparse.cpp
new file mode 100644
index 000000000..4fe09810e
--- /dev/null
+++ b/dep/StormLib/src/sparse/sparse.cpp
@@ -0,0 +1,282 @@
+/*****************************************************************************/
+/* huffman.cpp Copyright (c) Ladislav Zezula 1998-2003 */
+/*---------------------------------------------------------------------------*/
+/* This module contains Huffmann (de)compression methods */
+/* */
+/* Authors : Ladislav Zezula (ladik.zezula.net) */
+/* ShadowFlare (BlakFlare@hotmail.com) */
+/* */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* xx.xx.xx 1.00 Lad The first version of dcmp.cpp */
+/* 03.05.03 1.00 Lad Added compression methods */
+/* 19.11.03 1.01 Dan Big endian handling */
+/* 08.12.03 2.01 Dan High-memory handling (> 0x80000000) */
+/*****************************************************************************/
+
+#include
+#include
+
+#include "sparse.h"
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+void CompressSparse(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ unsigned char * pbOutBufferEnd = (unsigned char *)pvOutBuffer + *pcbOutBuffer;
+ unsigned char * pbInBufferEnd = (unsigned char *)pvInBuffer + cbInBuffer;
+ unsigned char * pbLastNonZero = (unsigned char *)pvInBuffer;
+ unsigned char * pbOutBuffer0 = (unsigned char *)pvOutBuffer;
+ unsigned char * pbInBuffPtr = (unsigned char *)pvInBuffer;
+ unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer;
+ unsigned char * pbInBuffer = (unsigned char *)pvInBuffer;
+ size_t NumberOfNonZeros;
+ size_t NumberOfZeros;
+
+ // There must be at least 4 bytes of free space in the output buffer now
+ if((pbInBuffer + 4) >= pbInBufferEnd)
+ return;
+
+ // Put the original data length (in little endian)
+ *pbOutBuffer++ = (unsigned char)(cbInBuffer >> 0x18);
+ *pbOutBuffer++ = (unsigned char)(cbInBuffer >> 0x10);
+ *pbOutBuffer++ = (unsigned char)(cbInBuffer >> 0x08);
+ *pbOutBuffer++ = (unsigned char)(cbInBuffer >> 0x00);
+
+ // If there is at least 3 bytes in the input buffer, do this loop
+ while(pbInBuffer < (pbInBufferEnd - 3))
+ {
+ // Reset the zero count and frontal pointer
+ pbLastNonZero = pbInBuffer;
+ pbInBuffPtr = pbInBuffer;
+ NumberOfZeros = 0;
+
+ if(pbInBuffPtr < pbInBufferEnd)
+ {
+ do
+ {
+ // Count number of zeros
+ if(*pbInBuffPtr == 0)
+ {
+ NumberOfZeros++;
+ }
+ else
+ {
+ // Were there at least 3 zeros before? If yes, we need to flush the data
+ if(NumberOfZeros >= 3)
+ break;
+ pbLastNonZero = pbInBuffPtr + 1;
+ NumberOfZeros = 0;
+ }
+ }
+ while(++pbInBuffPtr < pbInBufferEnd);
+ }
+
+ // Get number of nonzeros that we found so far and flush them
+ NumberOfNonZeros = pbLastNonZero - pbInBuffer;
+ if(NumberOfNonZeros != 0)
+ {
+ // Process blocks that are longer than 0x81 nonzero bytes
+ while(NumberOfNonZeros > 0x81)
+ {
+ // Verify if we still have enough space in output buffer
+ if((pbOutBuffer + 0x81) >= pbOutBufferEnd)
+ return;
+
+ // Put marker that means "0x80 of nonzeros"
+ *pbOutBuffer++ = 0xFF;
+ memcpy(pbOutBuffer, pbInBuffer, 0x80);
+
+ // Adjust counter of nonzeros and both pointers
+ NumberOfNonZeros -= 0x80;
+ pbOutBuffer += 0x80;
+ pbInBuffer += 0x80;
+ }
+
+ // BUGBUG: The following code will be triggered if the NumberOfNonZeros
+ // was 0x81 before. It will copy just one byte. This seems like a bug to me,
+ // but since I want StormLib to be exact like Blizzard code is, I will keep
+ // it that way here
+ if(NumberOfNonZeros > 0x80)
+ {
+ // Verify if we still have enough space in output buffer
+ if((pbOutBuffer + 2) >= pbOutBufferEnd)
+ return;
+
+ // Put marker that means "1 nonzero byte"
+ *pbOutBuffer++ = 0x80;
+ memcpy(pbOutBuffer, pbInBuffer, 1);
+
+ // Adjust counter of nonzeros and both pointers
+ NumberOfNonZeros--;
+ pbOutBuffer++;
+ pbInBuffer++;
+ }
+
+ // If there is 1 nonzero or more, put the block
+ if(NumberOfNonZeros >= 0x01)
+ {
+ // Verify if we still have enough space in output buffer
+ if((pbOutBuffer + NumberOfNonZeros + 1) >= pbOutBufferEnd)
+ return;
+
+ // Put marker that means "Several nonzero bytes"
+ *pbOutBuffer++ = (unsigned char)(0x80 | (NumberOfNonZeros - 1));
+ memcpy(pbOutBuffer, pbInBuffer, NumberOfNonZeros);
+
+ // Adjust pointers
+ pbOutBuffer += NumberOfNonZeros;
+ pbInBuffer += NumberOfNonZeros;
+ }
+ }
+
+ // Now flush all zero bytes
+ while(NumberOfZeros > 0x85)
+ {
+ // Do we have at least 2 bytes in the output buffer ?
+ if((pbOutBuffer + 1) >= pbOutBufferEnd)
+ return;
+
+ // Put marker that means "0x82 zeros"
+ *pbOutBuffer++ = 0x7F;
+
+ // Adjust zero counter and input pointer
+ NumberOfZeros -= 0x82;
+ pbInBuffer += 0x82;
+ }
+
+ // If we got more than 0x82 zeros, flush 3 of them now
+ if(NumberOfZeros > 0x82)
+ {
+ // Do we have at least 2 bytes in the output buffer ?
+ if((pbOutBuffer + 1) >= pbOutBufferEnd)
+ return;
+
+ // Put marker that means "0x03 zeros"
+ *pbOutBuffer++ = 0;
+
+ // Adjust zero counter and input pointer
+ NumberOfZeros -= 0x03;
+ pbInBuffer += 0x03;
+ }
+
+ // Is there at least three zeros ?
+ if(NumberOfZeros >= 3)
+ {
+ // Do we have at least 2 bytes in the output buffer ?
+ if((pbOutBuffer + 1) >= pbOutBufferEnd)
+ return;
+
+ // Put marker that means "Several zeros"
+ *pbOutBuffer++ = (unsigned char)(NumberOfZeros - 3);
+
+ // Adjust pointer
+ pbInBuffer += NumberOfZeros;
+ }
+ }
+
+ // Flush last three bytes
+ if(pbInBuffer < pbInBufferEnd)
+ {
+ pbInBuffPtr = pbInBuffer;
+
+ for(;;)
+ {
+ if(*pbInBuffPtr++ != 0)
+ {
+ // Get number of bytes remaining
+ NumberOfNonZeros = (pbInBufferEnd - pbInBuffer);
+
+ // Not enough space in the output buffer ==> exit
+ if((pbOutBuffer + NumberOfNonZeros + 1) >= pbOutBufferEnd)
+ return;
+
+ // Terminate with a marker that means "0x80 of nonzeros"
+ *pbOutBuffer++ = 0xFF;
+ memcpy(pbOutBuffer, pbInBuffer, NumberOfNonZeros);
+
+ // Adjust pointer
+ pbOutBuffer += NumberOfNonZeros;
+ break;
+ }
+ else
+ {
+ // Is there are more chars in the input buffer
+ if(pbInBuffPtr < pbInBufferEnd)
+ continue;
+
+ // If the compression will not compress it by even 1 byte, do nothing
+ if((pbOutBuffer + 1) >= pbOutBufferEnd)
+ return;
+
+ // Terminate with a chunk that means "0x82 of zeros"
+ *pbOutBuffer++ = 0x7F;
+ break;
+ }
+ }
+ }
+
+ // Out the length of the output buffer
+ *pcbOutBuffer = (int)(pbOutBuffer - pbOutBuffer0);
+}
+
+int DecompressSparse(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer)
+{
+ unsigned char * pbInBufferEnd = (unsigned char *)pvInBuffer + cbInBuffer;
+ unsigned char * pbOutBuffer = (unsigned char *)pvOutBuffer;
+ unsigned char * pbInBuffer = (unsigned char *)pvInBuffer;
+ unsigned int cbChunkSize;
+ unsigned int cbOutBuffer = 0;
+ unsigned int OneByte;
+
+ // Don't decompress anything that is shorter than 5 bytes
+ if(cbInBuffer < 5)
+ return 0;
+
+ // Get the 32-bits from the input stream
+ OneByte = *pbInBuffer++;
+ cbOutBuffer |= (OneByte << 0x18);
+ OneByte = *pbInBuffer++;
+ cbOutBuffer |= (OneByte << 0x10);
+ OneByte = *pbInBuffer++;
+ cbOutBuffer |= (OneByte << 0x08);
+ OneByte = *pbInBuffer++;
+ cbOutBuffer |= (OneByte << 0x00);
+
+ // Verify the size of the stream against the output buffer size
+ if(cbOutBuffer > (unsigned int)*pcbOutBuffer)
+ return 0;
+
+ // Put the output size to the buffer
+ *pcbOutBuffer = cbOutBuffer;
+
+ // Process the input buffer
+ while(pbInBuffer < pbInBufferEnd)
+ {
+ // Get (next) byte from the stream
+ OneByte = *pbInBuffer++;
+
+ // If highest bit, it means that that normal data follow
+ if(OneByte & 0x80)
+ {
+ cbChunkSize = (OneByte & 0x7F) + 1;
+ cbChunkSize = (cbChunkSize < cbOutBuffer) ? cbChunkSize : cbOutBuffer;
+ memcpy(pbOutBuffer, pbInBuffer, cbChunkSize);
+ pbInBuffer += cbChunkSize;
+ }
+ else
+ {
+ cbChunkSize = (OneByte & 0x7F) + 3;
+ cbChunkSize = (cbChunkSize < cbOutBuffer) ? cbChunkSize : cbOutBuffer;
+ memset(pbOutBuffer, 0, cbChunkSize);
+ }
+
+ // Increment output buffer pointer
+ pbOutBuffer += cbChunkSize;
+ cbOutBuffer -= cbChunkSize;
+ }
+
+ return 1;
+}
diff --git a/dep/StormLib/src/sparse/sparse.h b/dep/StormLib/src/sparse/sparse.h
new file mode 100644
index 000000000..12f62b526
--- /dev/null
+++ b/dep/StormLib/src/sparse/sparse.h
@@ -0,0 +1,17 @@
+/*****************************************************************************/
+/* sparse.h Copyright (c) Ladislav Zezula 2010 */
+/*---------------------------------------------------------------------------*/
+/* implementation of Sparse compression, used in Starcraft II */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 05.03.10 1.00 Lad The first version of sparse.h */
+/*****************************************************************************/
+
+#ifndef __SPARSE_H__
+#define __SPARSE_H__
+
+void CompressSparse(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+int DecompressSparse(void * pvOutBuffer, int * pcbOutBuffer, void * pvInBuffer, int cbInBuffer);
+
+#endif // __SPARSE_H__
diff --git a/dep/acelite/AUTHORS b/dep/acelite/AUTHORS
new file mode 100644
index 000000000..3e474e06c
--- /dev/null
+++ b/dep/acelite/AUTHORS
@@ -0,0 +1,13 @@
+Douglas C. Schmidt
+d.schmidt@vanderbilt.edu
+
+Professor of Computer Science
+Associate Chair of Computer Science and Engineering
+Department of Electrical Engineering and Computer Science
+Senior Researcher at the Institute for Software Integrated Systems (ISIS)
+Vanderbilt University
+Nashville, TN 37203
+
+www.dre.vanderbilt.edu/~schmidt
+TEL: (615) 343-8197
+FAX: (615) 343-7440
diff --git a/dep/acelite/CMakeLists.txt b/dep/acelite/CMakeLists.txt
new file mode 100644
index 000000000..7cc577c21
--- /dev/null
+++ b/dep/acelite/CMakeLists.txt
@@ -0,0 +1,355 @@
+#
+# This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+project(ACE VERSION 6.5.5)
+
+set(ACE_SOURCES
+ ace/ACE.cpp
+ ace/ACE_crc32.cpp
+ ace/ACE_crc_ccitt.cpp
+ ace/ATM_Acceptor.cpp
+ ace/ATM_Addr.cpp
+ ace/ATM_Connector.cpp
+ ace/ATM_Params.cpp
+ ace/ATM_QoS.cpp
+ ace/ATM_Stream.cpp
+ ace/Activation_Queue.cpp
+ ace/Active_Map_Manager.cpp
+ ace/Addr.cpp
+ ace/Argv_Type_Converter.cpp
+ ace/Assert.cpp
+ ace/Asynch_IO.cpp
+ ace/Asynch_IO_Impl.cpp
+ ace/Asynch_Pseudo_Task.cpp
+ ace/Atomic_Op.cpp
+ ace/Barrier.cpp
+ ace/Base_Thread_Adapter.cpp
+ ace/Based_Pointer_Repository.cpp
+ ace/Basic_Stats.cpp
+ ace/Basic_Types.cpp
+ ace/CDR_Base.cpp
+ ace/CDR_Size.cpp
+ ace/CDR_Stream.cpp
+ ace/Capabilities.cpp
+ ace/Cleanup.cpp
+ ace/Codecs.cpp
+ ace/Codeset_IBM1047.cpp
+ ace/Codeset_Registry.cpp
+ ace/Codeset_Registry_db.cpp
+ ace/Condition_Attributes.cpp
+ ace/Condition_Recursive_Thread_Mutex.cpp
+ ace/Condition_Thread_Mutex.cpp
+ ace/Configuration.cpp
+ ace/Configuration_Import_Export.cpp
+ ace/Connection_Recycling_Strategy.cpp
+ ace/Containers.cpp
+ ace/Copy_Disabled.cpp
+ ace/DEV.cpp
+ ace/DEV_Addr.cpp
+ ace/DEV_Connector.cpp
+ ace/DEV_IO.cpp
+ ace/DLL.cpp
+ ace/DLL_Manager.cpp
+ ace/Date_Time.cpp
+ ace/Dev_Poll_Reactor.cpp
+ ace/Dirent.cpp
+ ace/Dirent_Selector.cpp
+ ace/Dump.cpp
+ ace/Dynamic.cpp
+ ace/Dynamic_Message_Strategy.cpp
+ ace/Dynamic_Service_Base.cpp
+ ace/Dynamic_Service_Dependency.cpp
+ ace/Encoding_Converter.cpp
+ ace/Encoding_Converter_Factory.cpp
+ ace/Event_Base.cpp
+ ace/Event_Handler.cpp
+ ace/Event_Handler_Handle_Timeout_Upcall.cpp
+ ace/FIFO.cpp
+ ace/FIFO_Recv.cpp
+ ace/FIFO_Recv_Msg.cpp
+ ace/FIFO_Send.cpp
+ ace/FIFO_Send_Msg.cpp
+ ace/FILE.cpp
+ ace/FILE_Addr.cpp
+ ace/FILE_Connector.cpp
+ ace/FILE_IO.cpp
+ ace/File_Lock.cpp
+ ace/Filecache.cpp
+ ace/Flag_Manip.cpp
+ ace/Framework_Component.cpp
+ ace/Functor.cpp
+ ace/Functor_String.cpp
+ ace/Get_Opt.cpp
+ ace/Handle_Ops.cpp
+ ace/Handle_Set.cpp
+ ace/Hashable.cpp
+ ace/High_Res_Timer.cpp
+ ace/ICMP_Socket.cpp
+ ace/INET_Addr.cpp
+ ace/IOStream.cpp
+ ace/IO_Cntl_Msg.cpp
+ ace/IO_SAP.cpp
+ ace/IPC_SAP.cpp
+ ace/Init_ACE.cpp
+ ace/LSOCK.cpp
+ ace/LSOCK_Acceptor.cpp
+ ace/LSOCK_CODgram.cpp
+ ace/LSOCK_Connector.cpp
+ ace/LSOCK_Dgram.cpp
+ ace/LSOCK_Stream.cpp
+ ace/Lib_Find.cpp
+ ace/Local_Memory_Pool.cpp
+ ace/Local_Name_Space.cpp
+ ace/Local_Tokens.cpp
+ ace/Lock.cpp
+ ace/Log_Category.cpp
+ ace/Log_Msg.cpp
+ ace/Log_Msg_Android_Logcat.cpp
+ ace/Log_Msg_Backend.cpp
+ ace/Log_Msg_Callback.cpp
+ ace/Log_Msg_IPC.cpp
+ ace/Log_Msg_NT_Event_Log.cpp
+ ace/Log_Msg_UNIX_Syslog.cpp
+ ace/Log_Record.cpp
+ ace/Logging_Strategy.cpp
+ ace/MEM_Acceptor.cpp
+ ace/MEM_Addr.cpp
+ ace/MEM_Connector.cpp
+ ace/MEM_IO.cpp
+ ace/MEM_SAP.cpp
+ ace/MEM_Stream.cpp
+ ace/MMAP_Memory_Pool.cpp
+ ace/Malloc.cpp
+ ace/Malloc_Allocator.cpp
+ ace/Mem_Map.cpp
+ ace/Message_Block.cpp
+ ace/Message_Queue.cpp
+ ace/Message_Queue_NT.cpp
+ ace/Message_Queue_Vx.cpp
+ ace/Method_Request.cpp
+ ace/Monitor_Admin.cpp
+ ace/Monitor_Admin_Manager.cpp
+ ace/Monitor_Base.cpp
+ ace/Monitor_Control_Action.cpp
+ ace/Monitor_Control_Types.cpp
+ ace/Monitor_Point_Registry.cpp
+ ace/Monitor_Size.cpp
+ ace/Monotonic_Time_Policy.cpp
+ ace/Msg_WFMO_Reactor.cpp
+ ace/Multihomed_INET_Addr.cpp
+ ace/Mutex.cpp
+ ace/NT_Service.cpp
+ ace/Name_Proxy.cpp
+ ace/Name_Request_Reply.cpp
+ ace/Name_Space.cpp
+ ace/Naming_Context.cpp
+ ace/Netlink_Addr.cpp
+ ace/Notification_Queue.cpp
+ ace/Notification_Strategy.cpp
+ ace/Null_Mutex.cpp
+ ace/OS_Errno.cpp
+ ace/OS_Log_Msg_Attributes.cpp
+ ace/OS_NS_Thread.cpp
+ ace/OS_NS_arpa_inet.cpp
+ ace/OS_NS_ctype.cpp
+ ace/OS_NS_devctl.cpp
+ ace/OS_NS_dirent.cpp
+ ace/OS_NS_dlfcn.cpp
+ ace/OS_NS_errno.cpp
+ ace/OS_NS_fcntl.cpp
+ ace/OS_NS_math.cpp
+ ace/OS_NS_netdb.cpp
+ ace/OS_NS_poll.cpp
+ ace/OS_NS_pwd.cpp
+ ace/OS_NS_regex.cpp
+ ace/OS_NS_signal.cpp
+ ace/OS_NS_stdio.cpp
+ ace/OS_NS_stdlib.cpp
+ ace/OS_NS_string.cpp
+ ace/OS_NS_strings.cpp
+ ace/OS_NS_stropts.cpp
+ ace/OS_NS_sys_mman.cpp
+ ace/OS_NS_sys_msg.cpp
+ ace/OS_NS_sys_resource.cpp
+ ace/OS_NS_sys_select.cpp
+ ace/OS_NS_sys_sendfile.cpp
+ ace/OS_NS_sys_shm.cpp
+ ace/OS_NS_sys_socket.cpp
+ ace/OS_NS_sys_stat.cpp
+ ace/OS_NS_sys_time.cpp
+ ace/OS_NS_sys_uio.cpp
+ ace/OS_NS_sys_utsname.cpp
+ ace/OS_NS_sys_wait.cpp
+ ace/OS_NS_time.cpp
+ ace/OS_NS_unistd.cpp
+ ace/OS_NS_wchar.cpp
+ ace/OS_NS_wctype.cpp
+ ace/OS_QoS.cpp
+ ace/OS_TLI.cpp
+ ace/OS_Thread_Adapter.cpp
+ ace/OS_main.cpp
+ ace/Obchunk.cpp
+ ace/Object_Manager.cpp
+ ace/Object_Manager_Base.cpp
+ ace/Obstack.cpp
+ ace/PI_Malloc.cpp
+ ace/POSIX_Asynch_IO.cpp
+ ace/POSIX_CB_Proactor.cpp
+ ace/POSIX_Proactor.cpp
+ ace/Pagefile_Memory_Pool.cpp
+ ace/Parse_Node.cpp
+ ace/Ping_Socket.cpp
+ ace/Pipe.cpp
+ ace/Priority_Reactor.cpp
+ ace/Proactor.cpp
+ ace/Proactor_Impl.cpp
+ ace/Process.cpp
+ ace/Process_Manager.cpp
+ ace/Process_Mutex.cpp
+ ace/Process_Semaphore.cpp
+ ace/Profile_Timer.cpp
+ ace/RW_Mutex.cpp
+ ace/RW_Process_Mutex.cpp
+ ace/RW_Thread_Mutex.cpp
+ ace/Reactor.cpp
+ ace/Reactor_Impl.cpp
+ ace/Reactor_Notification_Strategy.cpp
+ ace/Reactor_Timer_Interface.cpp
+ ace/Read_Buffer.cpp
+ ace/Recursive_Thread_Mutex.cpp
+ ace/Recyclable.cpp
+ ace/Registry.cpp
+ ace/Registry_Name_Space.cpp
+ ace/Remote_Name_Space.cpp
+ ace/Remote_Tokens.cpp
+ ace/SOCK.cpp
+ ace/SOCK_Acceptor.cpp
+ ace/SOCK_CODgram.cpp
+ ace/SOCK_Connector.cpp
+ ace/SOCK_Dgram.cpp
+ ace/SOCK_Dgram_Bcast.cpp
+ ace/SOCK_Dgram_Mcast.cpp
+ ace/SOCK_IO.cpp
+ ace/SOCK_Netlink.cpp
+ ace/SOCK_SEQPACK_Acceptor.cpp
+ ace/SOCK_SEQPACK_Association.cpp
+ ace/SOCK_SEQPACK_Connector.cpp
+ ace/SOCK_Stream.cpp
+ ace/SPIPE.cpp
+ ace/SPIPE_Acceptor.cpp
+ ace/SPIPE_Addr.cpp
+ ace/SPIPE_Connector.cpp
+ ace/SPIPE_Stream.cpp
+ ace/SString.cpp
+ ace/SUN_Proactor.cpp
+ ace/SV_Message.cpp
+ ace/SV_Message_Queue.cpp
+ ace/SV_Semaphore_Complex.cpp
+ ace/SV_Semaphore_Simple.cpp
+ ace/SV_Shared_Memory.cpp
+ ace/Sample_History.cpp
+ ace/Sbrk_Memory_Pool.cpp
+ ace/Sched_Params.cpp
+ ace/Select_Reactor_Base.cpp
+ ace/Semaphore.cpp
+ ace/Service_Config.cpp
+ ace/Service_Gestalt.cpp
+ ace/Service_Manager.cpp
+ ace/Service_Object.cpp
+ ace/Service_Repository.cpp
+ ace/Service_Types.cpp
+ ace/Shared_Memory.cpp
+ ace/Shared_Memory_MM.cpp
+ ace/Shared_Memory_Pool.cpp
+ ace/Shared_Memory_SV.cpp
+ ace/Shared_Object.cpp
+ ace/Sig_Adapter.cpp
+ ace/Sig_Handler.cpp
+ ace/Signal.cpp
+ ace/Sock_Connect.cpp
+ ace/Stack_Trace.cpp
+ ace/Stats.cpp
+ ace/String_Base_Const.cpp
+ ace/Svc_Conf_Lexer.cpp
+ ace/Svc_Conf_y.cpp
+ ace/Synch_Options.cpp
+ ace/System_Time.cpp
+ ace/TLI.cpp
+ ace/TLI_Acceptor.cpp
+ ace/TLI_Connector.cpp
+ ace/TLI_Stream.cpp
+ ace/TP_Reactor.cpp
+ ace/TSS_Adapter.cpp
+ ace/TTY_IO.cpp
+ ace/Task.cpp
+ ace/Thread.cpp
+ ace/Thread_Adapter.cpp
+ ace/Thread_Control.cpp
+ ace/Thread_Exit.cpp
+ ace/Thread_Hook.cpp
+ ace/Thread_Manager.cpp
+ ace/Thread_Mutex.cpp
+ ace/Thread_Semaphore.cpp
+ ace/Throughput_Stats.cpp
+ ace/Time_Policy.cpp
+ ace/Time_Value.cpp
+ ace/Timeprobe.cpp
+ ace/Token.cpp
+ ace/Token_Collection.cpp
+ ace/Token_Invariants.cpp
+ ace/Token_Manager.cpp
+ ace/Token_Request_Reply.cpp
+ ace/Trace.cpp
+ ace/UNIX_Addr.cpp
+ ace/UPIPE_Acceptor.cpp
+ ace/UPIPE_Connector.cpp
+ ace/UPIPE_Stream.cpp
+ ace/UTF16_Encoding_Converter.cpp
+ ace/UTF32_Encoding_Converter.cpp
+ ace/UTF8_Encoding_Converter.cpp
+ ace/UUID.cpp
+ ace/WFMO_Reactor.cpp
+ ace/WIN32_Asynch_IO.cpp
+ ace/WIN32_Proactor.cpp
+ ace/XML_Svc_Conf.cpp
+ ace/XTI_ATM_Mcast.cpp
+ ace/ace_wchar.cpp
+ $<$:
+ pch.cpp
+ pch.h
+ >
+)
+
+add_library(ace STATIC
+ ${ACE_SOURCES}
+)
+
+target_include_directories(ace
+ PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+target_compile_definitions(ace
+ PUBLIC
+ ACE_AS_STATIC_LIBS
+ $<$:_WANT_SEMUN>
+)
+
+if(PCH)
+ add_cxx_pch(ace ${CMAKE_CURRENT_SOURCE_DIR}/pch.h ${CMAKE_CURRENT_SOURCE_DIR}/pch.cpp)
+endif()
\ No newline at end of file
diff --git a/dep/acelite/COPYING b/dep/acelite/COPYING
new file mode 100644
index 000000000..a8a5eda0c
--- /dev/null
+++ b/dep/acelite/COPYING
@@ -0,0 +1,111 @@
+
+ _________________________________________________________________
+
+ Copyright and Licensing Information for ACE(TM), TAO(TM), CIAO(TM),
+ DAnCE(TM), and CoSMIC(TM)
+
+ [1]ACE(TM), [2]TAO(TM), [3]CIAO(TM), DAnCE(TM), and [4]CoSMIC(TM)
+ (henceforth referred to as "DOC software") are copyrighted by
+ [5]Douglas C. Schmidt and his [6]research group at [7]Washington
+ University, [8]University of California, Irvine, and [9]Vanderbilt
+ University, Copyright (c) 1993-2014, all rights reserved. Since DOC
+ software is open-source, freely available software, you are free to
+ use, modify, copy, and distribute--perpetually and irrevocably--the
+ DOC software source code and object code produced from the source, as
+ well as copy and distribute modified versions of this software. You
+ must, however, include this copyright statement along with any code
+ built using DOC software that you release. No copyright statement
+ needs to be provided if you just ship binary executables of your
+ software products.
+
+ You can use DOC software in commercial and/or binary software releases
+ and are under no obligation to redistribute any of your source code
+ that is built using DOC software. Note, however, that you may not
+ misappropriate the DOC software code, such as copyrighting it yourself
+ or claiming authorship of the DOC software code, in a way that will
+ prevent DOC software from being distributed freely using an
+ open-source development model. You needn't inform anyone that you're
+ using DOC software in your software, though we encourage you to let
+ [10]us know so we can promote your project in the [11]DOC software
+ success stories.
+
+ The [12]ACE, [13]TAO, [14]CIAO, [15]DAnCE, and [16]CoSMIC web sites
+ are maintained by the [17]DOC Group at the [18]Institute for Software
+ Integrated Systems (ISIS) and the [19]Center for Distributed Object
+ Computing of Washington University, St. Louis for the development of
+ open-source software as part of the open-source software community.
+ Submissions are provided by the submitter ``as is'' with no warranties
+ whatsoever, including any warranty of merchantability, noninfringement
+ of third party intellectual property, or fitness for any particular
+ purpose. In no event shall the submitter be liable for any direct,
+ indirect, special, exemplary, punitive, or consequential damages,
+ including without limitation, lost profits, even if advised of the
+ possibility of such damages. Likewise, DOC software is provided as is
+ with no warranties of any kind, including the warranties of design,
+ merchantability, and fitness for a particular purpose,
+ noninfringement, or arising from a course of dealing, usage or trade
+ practice. Washington University, UC Irvine, Vanderbilt University,
+ their employees, and students shall have no liability with respect to
+ the infringement of copyrights, trade secrets or any patents by DOC
+ software or any part thereof. Moreover, in no event will Washington
+ University, UC Irvine, or Vanderbilt University, their employees, or
+ students be liable for any lost revenue or profits or other special,
+ indirect and consequential damages.
+
+ DOC software is provided with no support and without any obligation on
+ the part of Washington University, UC Irvine, Vanderbilt University,
+ their employees, or students to assist in its use, correction,
+ modification, or enhancement. A [20]number of companies around the
+ world provide commercial support for DOC software, however. DOC
+ software is Y2K-compliant, as long as the underlying OS platform is
+ Y2K-compliant. Likewise, DOC software is compliant with the new US
+ daylight savings rule passed by Congress as "The Energy Policy Act of
+ 2005," which established new daylight savings times (DST) rules for
+ the United States that expand DST as of March 2007. Since DOC software
+ obtains time/date and calendaring information from operating systems
+ users will not be affected by the new DST rules as long as they
+ upgrade their operating systems accordingly.
+
+ The names ACE(TM), TAO(TM), CIAO(TM), DAnCE(TM), CoSMIC(TM),
+ Washington University, UC Irvine, and Vanderbilt University, may not
+ be used to endorse or promote products or services derived from this
+ source without express written permission from Washington University,
+ UC Irvine, or Vanderbilt University. This license grants no permission
+ to call products or services derived from this source ACE(TM),
+ TAO(TM), CIAO(TM), DAnCE(TM), or CoSMIC(TM), nor does it grant
+ permission for the name Washington University, UC Irvine, or
+ Vanderbilt University to appear in their names.
+
+ If you have any suggestions, additions, comments, or questions, please
+ let [21]me know.
+
+ [22]Douglas C. Schmidt
+ _________________________________________________________________
+
+ Back to the [23]ACE home page.
+
+References
+
+ 1. http://www.cs.wustl.edu/~schmidt/ACE.html
+ 2. http://www.cs.wustl.edu/~schmidt/TAO.html
+ 3. http://www.dre.vanderbilt.edu/CIAO/
+ 4. http://www.dre.vanderbilt.edu/cosmic/
+ 5. http://www.dre.vanderbilt.edu/~schmidt/
+ 6. http://www.cs.wustl.edu/~schmidt/ACE-members.html
+ 7. http://www.wustl.edu/
+ 8. http://www.uci.edu/
+ 9. http://www.vanderbilt.edu/
+ 10. mailto:doc_group@cs.wustl.edu
+ 11. http://www.cs.wustl.edu/~schmidt/ACE-users.html
+ 12. http://www.cs.wustl.edu/~schmidt/ACE.html
+ 13. http://www.cs.wustl.edu/~schmidt/TAO.html
+ 14. http://www.dre.vanderbilt.edu/CIAO/
+ 15. http://www.dre.vanderbilt.edu/~schmidt/DOC_ROOT/DAnCE/
+ 16. http://www.dre.vanderbilt.edu/cosmic/
+ 17. http://www.dre.vanderbilt.edu/
+ 18. http://www.isis.vanderbilt.edu/
+ 19. http://www.cs.wustl.edu/~schmidt/doc-center.html
+ 20. http://www.cs.wustl.edu/~schmidt/commercial-support.html
+ 21. mailto:d.schmidt@vanderbilt.edu
+ 22. http://www.dre.vanderbilt.edu/~schmidt/
+ 23. http://www.cs.wustl.edu/ACE.html
diff --git a/dep/acelite/ChangeLog b/dep/acelite/ChangeLog
new file mode 100644
index 000000000..28f4fc60d
--- /dev/null
+++ b/dep/acelite/ChangeLog
@@ -0,0 +1,256 @@
+Fri Nov 14 08:38:16 CET 2014 Johnny Willemsen
+
+ * ACE version 6.3.0 released.
+
+Thu Nov 13 11:31:37 UTC 2014 Steve Huston
+
+ * ace/config-lite.h: Adjust ACE_DECLARE_STL_REVERSE_ITERATORS to
+ work with Solaris Studio 12.4.
+
+ * NEWS: Note support for Solaris Studio 12.4.
+
+Mon Nov 10 15:45:42 UTC 2014 Johnny Willemsen
+
+ * ace/CDR_Stream.h:
+ Doxygen fixes
+
+Mon Nov 10 12:39:18 UTC 2014 Martin Corino
+
+ * NEWS:
+ Updated.
+
+Thu Nov 6 19:45:38 UTC 2014 Johnny Willemsen
+
+ * bin/MakeProjectCreator/config/vc_warnings.mpb:
+ Visual Studio 2013 Update 3 gives a lot of warnings on deprecated
+ ascii winsock calls but they just work, so no need to change our
+ code. Added new feature vc_avoid_winsock_warnings which is enabled
+ by default, this adds _WINSOCK_DEPRECATED_NO_WARNINGS to the
+ compiler falgs. If you want to see all warnings, set
+ vc_avoid_winsock_warnings to 0 in your default.features file before
+ generatin the project files
+
+Tue Nov 4 14:37:56 UTC 2014 Johnny Willemsen
+
+ * etc/ace_inet.doxygen:
+ * etc/ace_qos.doxygen:
+ * etc/ace_rmcast.doxygen:
+ * etc/ace_ssl.doxygen:
+ * etc/acexml.doxygen:
+ Set UML_LOOK to NO, with UML look all members/methods are shown
+ in the diagrams making them unusable
+
+Mon Nov 3 12:50:28 UTC 2014 Martin Corino
+
+ * ace/WIN32_Asynch_IO.cpp:
+ Do not pass holder for bytes written when using OVERLAPPED IO.
+ Pass NULL instead. Recommended by MSDN.
+
+ * tests/Proactor_File_Test.cpp:
+ Circumvent problems with unreliable Async IO on older Win32
+ (WinXP, Win2003/2008).
+
+Fri Oct 31 14:10:52 UTC 2014 Johnny Willemsen
+
+ * rpmbuild/ace-tao.spec:
+ No need for lsb
+
+Fri Oct 31 13:49:11 UTC 2014 Martin Corino
+
+ * ace/WIN32_Asynch_IO.h:
+ * ace/WIN32_Asynch_IO.cpp:
+ Changed ACE_WIN32_Asynch_Write classes to not use shared_write() method
+ for Stream and File IO but use specific write() implementations instead.
+ This fixes Asynch File IO support (specifically writing) for Windows.
+ Fixes Bugzilla #3762 and #3992.
+
+ * tests/Proactor_File_Test.cpp:
+ * tests/run_test.lst:
+ * tests/tests.mpc:
+ Added new example/regression test for Asynch File IO using Proactor.
+
+Wed Oct 29 20:22:38 UTC 2014 Steve Huston
+
+ * bin/MakeProjectCreator/templates/gnu.mpd: Change LDFLAGS for HP-UX to do install_rpath correctly.
+ Fixes Bugzilla #4170.
+
+Tue Oct 28 09:51:14 UTC 2014 Martin Corino
+
+ * bin/MakeProjectCreator/docs/templates/gnu.txt:
+ * bin/MakeProjectCreator/templates/gnu.mpd:
+ * include/makeinclude/wrapper_macros.GNU:
+ Added (optional) support for per project output directories for
+ object files. Fixes Bugzilla #3868.
+
+Tue Oct 28 08:00:15 UTC 2014 Johnny Willemsen
+
+ * tests/Bug_4189_Regression_Test.cpp:
+ Use PF_INET, that is the default argument of ACE_SOCK_Dgram_Bcast
+
+Mon Oct 27 18:06:17 UTC 2014 Johnny Willemsen
+
+ * ace/config-win32-common.h:
+ Don't define SO_REUSEPORT when it is not defined, the code
+ that uses this as argument to call ACE_OS::setsockopt only works
+ when this has been defined. Defining it here to a dummy value
+ causes us to call the Windows API with an invalid argument
+
+Mon Oct 27 12:32:17 UTC 2014 Johnny Willemsen
+
+ * ace/config-win32-common.h:
+ * ace/os_include/sys/os_types.h:
+ Introduce new ACE_LACKS_PID_T which is set on Windows, but
+ not with MinGW
+
+Mon Oct 27 07:57:57 UTC 2014 Johnny Willemsen
+
+ * ace/OS_NS_sys_socket.inl:
+ Removed the ignore of SO_REUSEADDR on windows, this is needed
+ for ACE_SOCK_Dgram_Bcast to work, see bugzilla 4189. Also removed
+ the Windows specific change of SO_REUSEPORT to SO_REUSEADDR, this
+ are different flags that we should not merge at this level.
+
+Fri Oct 24 18:41:42 UTC 2014 Johnny Willemsen
+
+ * tests/Bug_4189_Regression_Test.cpp:
+ * tests/run_test.lst:
+ * tests/tests.mpc:
+ New test for bugzilla 4189, on Windows it is not possible
+ at this moment to open a ACE_SOCK_Dgram_Bcast multiple times
+ on the same port, even when reuse_addr has been set to 1
+
+Thu Oct 23 19:41:00 UTC 2014 Steve Huston
+
+ * tests/Naming_Test.cpp: Fixed data type warning for conversion on
+ %d formatting for Mingw.
+
+Tue Oct 21 15:45:59 UTC 2014 Phil Mesnier
+
+ * ace/ETCL/ETCL_l.cpp:
+ Scoreboard cleanup.
+
+Mon Oct 20 15:37:35 UTC 2014 Phil Mesnier
+
+ * ACEXML/common/XMLFilterImpl.h:
+ * ACEXML/common/XMLFilterImpl.cpp:
+ * ACEXML/parser/parser/Entity_Manager.h:
+ * ace/ETCL/ETCL_l.cpp:
+ * protocols/ace/RMCast/Flow.h:
+ * protocols/ace/RMCast/Flow.cpp:
+ * protocols/ace/RMCast/Reassemble.h:
+ * protocols/ace/RMCast/Reassemble.cpp:
+ * protocols/ace/TMCast/TransactionController.hpp:
+ Clean up unused member warnings identified by clang 6.0.
+
+ * bin/PerlACE/Process_Unix.pm:
+ increase the valgrind wait factor to accommodate slower machines.
+
+Mon Oct 13 18:59:00 UTC 2014 Steve Huston
+
+ * apps/mkcsregdb/mkcsregdb.cpp: Fix data type error on Windows.
+
+Mon Oct 13 17:24:21 UTC 2014 Johnny Willemsen
+
+ * docs/bczar/bczar.html:
+ Added package for killall
+
+Sat Oct 11 21:09:07 UTC 2014 Steve Huston
+
+ * ace/Process.cpp (ACE_Process_Options::setenv (const ACE_TCHAR *, ...):
+ Avoid the secret, magic number buried in ACE_OS::vsprintf() for
+ platforms that support ACE_OS::vsnprintf. Allows full use of
+ DEFAULT_COMMAND_LINE_BUF_LEN as well as customizing that value
+ to a higher size. Thanks to John Lilley for this improvement.
+
+Sat Oct 11 20:45:03 UTC 2014 Steve Huston
+
+ * apps/drwho/File_Manager.cpp: Fixed const-ness compile warning.
+
+Thu Oct 9 16:21:38 UTC 2014 Steve Huston
+
+ * NEWS: Add description of the below changes.
+
+ * ace/OS_NS_stdlib.{h inl cpp}:
+ * ace/OS_NS_stdio.{h inl}:
+ * ace/README:
+ Added new feature config macros ACE_DISABLE_MKTEMP and
+ ACE_DISABLE_TEMPNAM. These disable the ACE wrappers for
+ mktemp() and tempnam(), respectively. Those functions are
+ considered insecure and better replaced by the use of mkstemp().
+
+ * ace/FILE_Addr.cpp (set): Call to set(const ACE_FILE_Addr&) using
+ the default "any" address will now fail if ACE_DISABLE_MKTEMP is
+ set.
+
+ * ace/FILE_Connector.cpp (connect): Specifying 'any' for the
+ address now uses ACE_OS::mkstemp(). Contrast this with setting a
+ ACE_FILE_Addr with 'any', above.
+
+ * ace/MMAP_Memory_Pool.cpp (constructor): Using the 'unique' option
+ will no longer work if ACE_DISABLE_MKTEMP is set.
+
+ * tests/MM_Shared_Memory_Test.cpp:
+ Will not work if ACE_DISABLE_MKTEMP is set.
+
+ * tests/Naming_Test.cpp:
+ Changed to avoid use of ACE_OS::tempnam.
+
+ * tests/Svc_Handler_Test.cpp: Modified to work with or without
+ ACE_DISABLE_MKTEMP.
+
+ * apps/mkcsregdb/mkcsregdb.cpp:
+ * apps/drwho/File_Manager.cpp:
+ Use ACE_OS::mkstemp.
+
+ * examples/OS/Process/imore.cpp:
+ * examples/Service_Configurator/IPC-tests/client/local_dgram_client.cpp:
+ Won't work if ACE_DISABLE_TEMPNAM is set.
+
+Tue Oct 7 21:55:51 UTC 2014 Steve Huston
+
+ Set of changes applicable to VxWorks, particularly 6.9. These changes
+ were contributed by Jeff Fowler and Gordon Hulpieu at NetApp. Feel
+ free to contact me with any issues.
+
+ * ace/OS_NS_Thread.{h cpp inl}: Change from using the VxWorks TCB
+ spare4 value to a new static member of ACE_TSS_Emulation. This
+ mechanism covers both VxWorks multicore (SMP) using the __thread
+ attribute and single core using Task Var. This change gets ACE out
+ of the way of the applications that use spare4 for their own
+ purposes.
+ Also fixes for various compiler warnings.
+
+ * ace/OS_NS_sys_socket.inl (accept): Add IPv6 case to the
+ ACE_HAS_BROKEN_ACCEPT_ADDR case.
+
+ * ace/config-vxworks6.9.h: Correct the ACE_HAS_BROKEN_ACCEPT_ADDR
+ setting for VxWorks 6.9+.
+
+ * ace/OS_NS_dlfcn.inl (dlsym): For VxWorks, use the symFind() call
+ instead of the deprecated symFindByName().
+
+ * ace/Process.cpp: Fix a weird compile diagnostic by adding what should
+ be a needless space.
+
+ * include/makeinclude/platform_vxworks6.9.GNU: Update MUNCH_FLAGS
+ for this version.
+
+Tue Sep 30 13:34:34 UTC 2014 Johnny Willemsen
+
+ * include/makeinclude/platform_g++_common.GNU:
+ Add -Wnon-virtual-dtor to the default CCFLAGS
+
+Fri Sep 26 09:58:57 UTC 2014 Olli Savia
+
+ * tests/Message_Block_Large_Copy_Test.cpp:
+ Fixed compile error on LynxOS.
+
+Wed Sep 24 19:51:44 CEST 2014 Johnny Willemsen
+
+ * ACE version 6.2.8 released.
+
+Local Variables:
+mode: change-log
+add-log-time-format: (lambda () (progn (setq tz (getenv "TZ")) (set-time-zone-rule "UTC") (setq time (format-time-string "%a %b %e %H:%M:%S %Z %Y" (current-time))) (set-time-zone-rule tz) time))
+indent-tabs-mode: nil
+End:
diff --git a/dep/acelite/NEWS b/dep/acelite/NEWS
new file mode 100644
index 000000000..e6d4d3dbb
--- /dev/null
+++ b/dep/acelite/NEWS
@@ -0,0 +1,2048 @@
+USER VISIBLE CHANGES BETWEEN ACE-6.2.8 and ACE-6.3.0
+====================================================
+
+. ACE now supports Oracle Solaris Studio 12.4 on Solaris.
+
+. New config macros were added:
+ ACE_DISABLE_MKTEMP: Disables the availability of ACE_OS::mktemp().
+ ACE_DISABLE_TEMPNAM: Disables the availability of ACE_OS::tempnam().
+ These can be added to your $ACE_ROOT/ace/config.h to disable these
+ wrappers which are considered to be a potential security risk. Disabling
+ one or both will also disable the following:
+ - ACE_FILE_Addr::set () with the 'any' address specified.
+ - ACE_MMAP_Memory_Pool use of the 'unique' option.
+
+. Reduced size of all doxygen documentation by changing the
+ type of diagrams shown
+
+. Removed Windows specific workarounds from ACE_OS::setsockopt, as a
+ result SO_REUSEPORT is not defined anymore on Windows and SO_REUSEADDR
+ is passed directly to the OS
+
+. By adding a 'specific' section to a MPC (base) project it is now possible
+ to have object file output directories per project for GNUACE projects.
+ The following should be added to MPC projects (bugzilla #3868):
+ specific(gnuace) {
+ build_dir_per_project=1
+ }
+
+. ACE_Asynch_Write_File will now correctly accept non-socket (file, TTY ..)
+ handles (bugzilla #3762 and #3992)
+
+. Fixes for VxWorks 6.9
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.7 and ACE-6.2.8
+====================================================
+
+. Add new ACE::make_event_handler() which is similar
+ to std::make_shared but than for allocation of ACE
+ event handlers. This template method is only enabled
+ when ACE_HAS_CPP11 has been defined, which is set
+ automatically when a C++ compiler with adequate
+ C++11 support is used. Also ACE_Event_Handler_var is
+ extended with some C++11 specific operations
+
+. For all reactor types calling cancel_timer with a
+ nullptr is now allowed, will result in a return of 0
+
+. ACE_DLL and ACE_DLL_Manager have been extended with
+ the support to retrieve any dynamic loader errors
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.6 and ACE-6.2.7
+====================================================
+
+. Added configuration files for Microsoft Visual Studio 2014
+
+. Added configuration files for MacOSX Yosemite
+
+. Added configuration files for IBM AIX XL C++ 12.1
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.5 and ACE-6.2.6
+====================================================
+
+. Resolved several data races reported by Intel Inspector XE
+
+. Added optional socket connection optimization for Windows
+
+. Improve functionality and stability of running tests on
+ Android Virtual Device (AVD).
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.4 and ACE-6.2.5
+====================================================
+
+. Added the ability to build RPMs for just ACE, using an ACE-src tarball.
+ To do this add "--without tao" to the rpmbuild command line.
+
+. Added support for Embarcadero C++Builder XE5 using
+ bcc32 in debug and release mode
+
+. Added support for Embarcadero C++Builder XE6 using
+ bcc32 in debug and release mode
+
+. When Intel C++ 2013 SP1 Update 2 is used with C++11 enabled
+ as compiler feature now also ACE_HAS_CPP11 will be defined,
+ this compiler is now able to compile all our C++11 feature
+ tests
+
+. Fixed several boundary bugs in the ACE RLE Compressor
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.3 and ACE-6.2.4
+====================================================
+
+. Added support for FC20 and ended support for FC19
+
+. Extended C++11 feature test suite
+
+. Improved support for MingW64
+
+. Improvements to IPv6 support on Windows
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.2 and ACE-6.2.3
+====================================================
+
+. The ACE_OS::thr_join() method will detect if the thread to be waited on is
+ the calling thread and avert that deadlock. The support needed for this
+ method is available at Vista/Windows Server 2003 and higher; to enable
+ the deadlock prevention, compile ACE with _WIN32_WINNT=0x0502 or higher.
+
+. Ended maintenance and support for FC12 CEEL
+
+. Further improvements of the Android port: Added new define
+ ACE_HAS_EXPLICIT_TEMPLATE_CLASS_INSTANTIATION and related macros
+ ACE_SINGLETON_TEMPLATE_INSTANTIATION and ACE_SINGLETON_TEMPLATE_INSTANTIATE
+ providing a cleaner way to support explicit template (static member or class)
+ instantiation.
+
+' Improvements of the test framework for running tests in a mixed environment
+ where different targets run on different physiscal devices (possibly having
+ different OS).
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.1 and ACE-6.2.2
+====================================================
+
+. The max_len argument to ACE_Process::command_line_buf changed from int*
+ to size_t*. This corrects a mismatch between the argument type and the
+ data member in ACE_Process from which the value comes.
+
+. Removed some include files from ACE.h. These were not required for ACE.
+ The removed includes are OS_NS_math, Flag_Manip, Handle_Ops, Lib_Find,
+ Init_ACE, Sock_Connect.h. You may have to explicitly add one of these
+ in your own code to restore compiling.
+
+. Further improvements of the Android port, still work in progress.
+
+USER VISIBLE CHANGES BETWEEN ACE-6.2.0 and ACE-6.2.1
+====================================================
+
+. Added support for Fedora 19, ended daily maintenance
+ for Fedora 17 and 18
+
+. Added support for Embarcadero C++BuilderXE4 using
+ bcc32 in debug and release mode
+
+. Improved support for Android
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.9 and ACE-6.2.0
+====================================================
+
+. None
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.8 and ACE-6.1.9
+====================================================
+
+. Added MinGW64 as supported platform
+
+. Added support for GCC 4.8.0
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.7 and ACE-6.1.8
+====================================================
+
+. Small bug fixes
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.6 and ACE-6.1.7
+====================================================
+
+. Integrated several patches to simplify Debian/Ubuntu
+ packaging
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.5 and ACE-6.1.6
+====================================================
+
+. Added new event and sema initialization methods to OS_NS_Thread
+ to allow passing pre-initialized condition attributes providing
+ basic support for using time policies in ACE Event classes.
+
+. Added TIME_POLICY support to ACE_Event classes to allow for
+ monotonic timer support for ACE Events.
+
+. Added new regression test:
+ Monotonic_Manual_Event_Test
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.4 and ACE-6.1.5
+====================================================
+
+. When a ACE_Event_Handler registered for signals is unregistered,
+ whether by unregistering, returning -1 from handle_signal(), or by
+ the reactor closing, the ACE_Event_Handler::handle_close() hook will
+ be called. The close_mask passed will be ACE_Event_Handler::SIGNAL_MASK.
+ In previous versions, handle_close() would only be called when the
+ handle_signal() callback returned -1. This resolves Bugzilla #2368.
+
+. Some initial ACE unit tests to validate the C++11 support of various
+ compilers
+
+. Added support for OpenSuSE 12.2
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.3 and ACE-6.1.4
+====================================================
+
+. Added a new ACE_Time_Value derived template class (Time_Value_T.h):
+
+ template class ACE_Time_Value_T
+
+ This template class overloads 4 new virtual methods from
+ the ACE_Time_Value base class to provide time policy aware
+ time values:
+ to_relative_time ()
+ to_absolute_time ()
+ now ()
+ duplicate ()
+
+. Updated time policy classes to return ACE_Time_Value_T<> instantiations
+ for the corresponding time policy instead of 'common' time values.
+
+. Added new ACE_Monotonic_Time_Policy (Monotonic_Time_Policy.h).
+ This class provides a monotonic time source for supported
+ platforms (Windows and POSIX platforms providing the required
+ clock_gettime() time source; currently verified for Windows and
+ Linux)
+
+. Updated OS_NS_Thread to use the new time policy support in ACE_Time_Value
+ for (relative) time calculations and added new ACE_OS::condattr_setclock ()
+ method.
+
+. Added TIME_POLICY support to ACE_Condition_Attributes to allow for
+ monotonic timer support for ACE_Condition.
+
+. Added TIME_POLICY support to ACE_Message_Queue-s, ACE_Task-s and
+ related classes to enable support for monotonic timers in the timed
+ wait methods (ACE_Condition based). See docs/ACE-monotonic-timer.html
+ for how to use this.
+
+. Added two new regression tests:
+ Monotonic_Task_Test
+ Monotonic_Message_Queue_Test
+ and updated the Bug_4055_Regression_Test to a fixed state.
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.2 and ACE-6.1.3
+====================================================
+
+. Added support for Oracle Solaris Studio 12 Update 3 (SunCC 5.12)
+
+. Added new XML_Utils library which comes from DAnCE but is now also used
+ by OpenDDS
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.1 and ACE-6.1.2
+====================================================
+
+. Added compile time support for Windows CE 7, no runtime testing has
+ been performed
+
+. The High Res Timer global scale factor on Windows is now 64bit, see bugzilla
+ 3703 for the background of this. If you use the gsf in your code, use the
+ new ACE_High_Res_Timer::global_scale_factor_type type trait to not get
+ any conversion warnings
+
+. Removed Tandem NSK v2/v3 support which resulted in cleanup throughout all
+ code. The emulations for ACE_INT64/ACE_UINT64 have been removed because no
+ platform is using them anymore
+
+USER VISIBLE CHANGES BETWEEN ACE-6.1.0 and ACE-6.1.1
+====================================================
+
+. Minor bug fixes
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.8 and ACE-6.1.0
+====================================================
+
+. Added compilation support for VxWorks 6.9, no runtime
+ testing has been performed
+
+. Added ACE Run-length encoding compressor
+
+. Fixed several Coverity reported issues
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.7 and ACE-6.0.8
+====================================================
+
+. Added support for MPC's new feature that creates dependency files for IDL
+ files when generating '-type gnuace' projects. Turned off by default, it
+ can be enabled in a features file or on the command line with
+ '-features ace_idl_dependencies=1'.
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.6 and ACE-6.0.7
+====================================================
+
+. Added a new method to ACE_Atomic_Op, TYPE exchange (TYPE newval)
+ which does an atomic exchange of the new value with ACE_Atomic_Op's value
+ and returns the old value. The tests/Atomic_Op_Test.cpp test program has a
+ test case that exemplifies its usage; see the Exchange_Tester class.
+
+. Added a new feature to timer queue templates classes: TIME_POLICY.
+ This feature is specified through a new template argument and provides the
+ timer queue with a policy for a timer (time of day) value. This feature is
+ intended to replace (in time) the gettimeofday setter method which has been
+ marked @deprecated. For now backwards compatibility is guaranteed.
+ The TIME_POLICY feature provides flexibility with regards to providing a timer
+ source to the timer queues as well as the possibility for a fully optimized
+ calling path.
+ A number of standard time policies are provided in ace/Time_Policy.h.
+ The tests/Timer_Queue_Test.cpp has been updated to reflect and exemplify these
+ changes.
+
+. Added the TIME_POLICY feature also to countdown time class which has now
+ become a template (ace/Countdown_Time_T.h)
+
+. Initial support for Microsoft Visual Studio 11
+
+. Increased overall code quality by using Coverity and Klocwork
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.5 and ACE-6.0.6
+====================================================
+
+. Removed autoconf support, only traditional way of
+ compilation is shipped from now
+
+. Add support for RHEL 6.1 64bit
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.4 and ACE-6.0.5
+====================================================
+
+. Improved support for Android and added the ability to run all ACE/TAO tests
+ automatically using the Android emulator
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.3 and ACE-6.0.4
+====================================================
+
+. Removed support for C++ Builder
+
+. Added support for building with the Android NDK, at least r5c. This
+ is currently available for linux host platforms.
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.2 and ACE-6.0.3
+====================================================
+
+. Added support for GCC 4.6
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.1 and ACE-6.0.2
+====================================================
+
+. The ACE_wrappers/ace/OS.h file has been restored in order to ensure
+ build-time compatibility with older ACE versions. Its use will still
+ cause your build to incur more processing time than using the needed
+ ace/OS_NS_*.h files; however, you should be able to build OS.h-including
+ code without needing to replace it with OS_NS_* includes.
+
+. Improved and simplified QNX support
+
+. Changed rand_r() and getpwnam_r() to conform Single UNIX Specification.
+
+. Fixed performance of send_v on windows when individual iovec elements
+ are particularly large.
+
+USER VISIBLE CHANGES BETWEEN ACE-6.0.0 and ACE-6.0.1
+====================================================
+
+. Added support for MinGW with GCC 4.5
+
+USER VISIBLE CHANGES BETWEEN ACE-5.8.3 and ACE-6.0.0
+====================================================
+
+. Changed the string format produced by ACE::timestamp() from the ctime
+ format "Day Mon dd hh:mm:ss yyyy" to ISO-8601 yyyy-mm-dd hh:mm:ss.mmmmmm.
+ This makes the time easier to collate and removes any dependence on locale.
+ The change affects the output from ACE_Log_Msg's %D format and both VERBOSE
+ and VERBOSE_LIGHT timestamps in addition to application-made direct calls
+ to ACE::timestamp().
+
+. Removed GCC < 3 support
+
+. A new build system hook was added for users to include site-private rules
+ in a build. If a file named "rules.private.GNU" in located in any build
+ directory it will get included from
+ $ACE_ROOT/include/makeinclude/rules.local.GNU. The "private_rules_file"
+ make variable can be set to override the name and/or location of the file.
+ If no such rules file exists, its absence is silently ignored. This
+ facility can be used, for example, to integrate a specialized code checker
+ into the build process.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.8.2 and ACE-5.8.3
+====================================================
+
+. Two new methods were added to ACE_Pipe: close_read() and close_write().
+ These methods can be used to close individual pipe handles.
+
+. The ACE::handle_ready() family of methods was changed to prefer using
+ poll() over select() on platforms where poll() is available. This
+ preference was previously only used if ACE_HAS_LIMITED_SELECT was set.
+ The ACE_HAS_LIMITED_SELECT choice is removed, making ACE_HAS_POLL the
+ setting that switches this preference. The driving reason for this
+ is that if select() is called to detect changes on a handle whose
+ values falls outside that which can safely be stored in an fdset,
+ the handle-setting macros/functions will set/clear bits outside
+ of the fdset. This results in very weird memory changes, often in
+ the stack, which are very hard to diagnose. poll()'s operation
+ does not suffer from this affect. With the growing use of large
+ numbers of handles and use of ACE_Dev_Poll_Reactor on Linux,
+ the rate at which this problem was cropping up was increasing.
+
+. Added a simple helper ACE::is_equal() which compares equality of two
+ objects without using operator==. This is useful for comparing floating
+ point values.
+
+. Removed all deprecated methods, arguments, files, classes, macros and
+ anything else we kept for years.
+
+. Removed Irix/Tru64/SCO/Uniware/Cray support
+
+. ACE_Pair has been removed. Users should now use std::pair.
+
+. This is the last micro release that will work with GCC < 3, after x.8.3
+ support for GCC < 3 will be removed
+
+USER VISIBLE CHANGES BETWEEN ACE-5.8.1 and ACE-5.8.2
+====================================================
+
+. Added support for the Microsoft Visual Studio 2010 IDE (vc10)
+
+. Removed complete support for emulated C++ exceptions
+
+USER VISIBLE CHANGES BETWEEN ACE-5.8.0 and ACE-5.8.1
+====================================================
+
+. Added support for Microsoft Visual Studio 2010 using nmake
+
+. Reduced the amount of doxygen pages generated, the original settings caused
+ a doxygen generated html package of 1.4GB which was way too large
+
+. Extended ACE INet addon library with:
+ * HTTP Basic Authentication
+ * SSL/HTTPS support.
+ * Proxy CONNECT tunneling.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.9 and ACE-5.8.0
+====================================================
+
+. There are two new ACE_Time_Value methods for getting and setting millisecond
+ values to/from ACE_UINT64 values:
+
+ ACE_UINT64 ACE_Time_Value::get_msec () const
+ void ACE_Time_Value::set_msec (const ACE_UINT64 &ms)
+
+ The former is a replacement for the existing msec(ACE_UINT64&) methods that
+ are "getter" methods whose signatures look confusingly like "setters". See
+ Bugzilla #3336 for the history behind this change.
+
+ The latter is for consistency and clarity.
+
+. Added ACE INet addon library for Inet protocol clients (and possibly
+ servers at some point) like http://, ftp:// etc.
+ The library implements standard C++ iostream wrapper classes for
+ ACE Svc_Handler and Reactor based input/output handling, URL classes
+ and protocol handler classes.
+ NOTE: This is work in progress! There is no guarentee that the API
+ won't change in the next few releases.
+ Protocol handling is currently restricted to client side download
+ requests for HTTP and FTP.
+ Handling for upload requests should be added in the near future as well
+ as HTTP Basic Authentication.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.8 and ACE-5.7.9
+====================================================
+
+. ACE's default makefiles (traditional ACE/GNU, not autoconf/automake)
+ now support installation with "make install".
+ Please see the ACE-INSTALL.html file for instructions.
+
+. Support for the ARCH make variable has been enhanced to apply to executables
+ (in addition to libraries and object files), and the ARCH feature has been
+ integrated into the MPC-generated makefiles (to work with MPC's requires
+ and avoids features).
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.7 and ACE-5.7.8
+====================================================
+
+. ACE now uses GCC builtin Atomic instructions for short,
+ unsigned short, long, unsigned long, int, unsigned int,
+ and bool. This makes our Atomic_Op around 7 times faster
+
+. ACE Service Configuration Framework now process first service
+ configuration files and then command-line directives. Thus if
+ application uses both service configuration files and command-line
+ directives then the command-line directives may override results of
+ directives in the configuration files. At the same time if the
+ application uses only the default svc.conf file and command-line
+ directives then the directives from svc.conf can not override
+ results of the user provided command-line directives.
+
+. ACE_Dev_Poll_Reactor now dispatches notifications in only one thread at
+ a time. This brings notification handling more in line with behavior in
+ other Reactor implementations.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.6 and ACE-5.7.7
+====================================================
+
+. Integrated fix for bug 3104 and regression test for
+ interval timers.
+
+. Added support for GCC builtin Atomic instructions which
+ are enabled with GCC >= 4.1 for PPC32/PPC64/IA64
+
+. Improved autoconf support for debian
+
+. Added support for -mcpu and -mtune. Add TCPU=.. to your
+ environment/platform_macros.GNU to specify you cpu and
+ than add cpumodelflag=1 and/or tunemodelflag=1. Using
+ this with IBM Cell increased the performance significantly
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.5 and ACE-5.7.6
+====================================================
+
+. Added support for iPhone/iPod Touch/iPad. The following
+ environment variables are needed:
+
+ IPHONE_TARGET, should be set to either SIMULATOR or
+ HARDWARE. Set to HARDWARE if you want to deploy
+ on the iPhone/iPod Touch/iPad device.
+
+ IPHONE_VERSION, should be set to 3.1.2 or 3.2. One can
+ set the version to any future or past versions, but
+ only 3.1.2 and 3.2 have been tried.
+
+ Note that one has to compile ACE/TAO statically as
+ it is believed that the iPhone OS does not support
+ dynamic loading of external libraries. The usual
+ procedure of cross compiling ACE/TAO applies
+ (such as setting HOST_ROOT environment variable).
+
+. Added support for Embarcadero C++ Builder 2010
+
+. Added option to print a given ACE_Time_Value in the log
+ message instead of system supplied timestamp as in %T
+ and %D.
+ The option is implemented as a variant of the %D/%T
+ options by using the '#' flag character like '%#D' or
+ '%#T'. When using this flag an ACE_Time_Value pointer is
+ expected in the argument list supplied with the log message.
+ This fixed Bugzilla #3221.
+
+. Fixed problems with ACE_INET_Addr::is_multicast() on
+ little endian platforms. This fixed bugzilla #3729.
+
+. Added compilation support for VxWorks 6.8, no runtime
+ testing has been performed
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.4 and ACE-5.7.5
+====================================================
+
+. Added MacOSX Snow Leopard support
+
+. Added strsignal() wrapper
+
+. Improved LynxOS support
+
+. Updated Interix port
+
+. Fixed MinGW compilation problems
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.3 and ACE-5.7.4
+====================================================
+
+. ACE_CDR::consolidate now returns an int, 0 is ok, -1 is failure
+
+. Fixed a bug in the realclean feature of the GNU makefiles
+
+. Improved Sun Studio for Linux support
+
+. Improved OpenBSD support
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.2 and ACE-5.7.3
+====================================================
+
+. C++ Builder 2009 Update 3 is the only C++Builder that is supported, older
+ and newer compilers are not supported anymore
+
+. Made final changes for the CEGCC port
+
+. Added a set of tests to validate C++ compiler and the stl implementation
+ they ship.
+
+. HP-UX PARISC aCC < 3.80 are deprecated and can't be used anymore. Upgrade
+ to aCC 3.80 or newer
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.1 and ACE-5.7.2
+====================================================
+
+. Borland C++ makefiles aren't shipped anymore as part of the release
+ but have to be generated by the user
+
+. Refactored gperf to have its own shared library so that we can reuse
+ that in TAO
+
+. Added support for SuSE Enterprise 10
+
+. ACE_Configuration_Heap::open() now returns -1 with errno EBUSY if it is
+ called multiple times. Previous versions would allow multiple calls to
+ open() but leak resources.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.7.0 and ACE-5.7.1
+====================================================
+
+. Added support for Sun Studio 12 Update Pack 1
+
+. Fixed compile problems when using Windows CE x86 release mode
+
+. Fixed compile problems for FreeBSD
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.9 and ACE-5.7.0
+====================================================
+
+. Added support for the VxWorks vxAtomicLib which is available with VxWorks 6.6
+ and newer. If you don't want to use this library undef ACE_HAS_VXATOMICLIB
+ in your config.h file
+
+. Added support for C++ Builder 2009 Update 3
+
+. Added support for ACE/TAO using the CEGCC project
+
+. Added support for upcoming Fedora 11 and OpenSuSE Factory
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.8 and ACE-5.6.9
+====================================================
+
+. Removed Borland/CodeGear C++ Builder 2007 support. If you'd like to
+ fund this support please let us know.
+
+. Removed VxWorks 5.5.x, 6.2, and 6.3 support. If you'd like to fund
+ this support please let us know.
+
+. Improved Unicode support.
+
+. Added support for the native Windows Vista and Windows Server 2008
+ condition variables. These has to be enabled at compile time, and when
+ enabled the application can only run on Vista or Server 2008. Add
+ ACE_HAS_WTHREADS_CONDITION_VARIABLE to your config.h file to enable
+ these
+
+. Fixed a bug when trying to read a file of 1 byte when unicode is
+ enabled
+
+. Improved the Windows CE port
+
+. Fixed several Klocwork reported issues
+
+. Added support for MinGW 3.15
+
+. Added support for Incredibuild, which is an MSVC++ feature that
+ optimizes compiles via distributing builds.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.7 and ACE-5.6.8
+====================================================
+
+. Added a new function ACE::isdotdir() which determines if a specified
+ pathname is "dot dir" (ie. "." or ".."). ACE::isdotdir() is significantly
+ faster than pair of strcmp() calls.
+
+. Last micro release that is maintained for Borland/CodeGear C++
+ Builder 2007 and Intel C++ on Windows.
+
+. Fixed crash when ACE thread tries to inherit the logging attributes
+ from non ACE threads.
+
+. Fixed many small compile and test errors that occur on some platforms.
+
+. Fixed log output formatting on some platforms.
+
+. Bugs fixed: 2748, 3164, 3480, 3494, 3502, 3541, 3542, 3544, 3557.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.6 and ACE-5.6.7
+====================================================
+
+. Changed the automake build's feature test for a "usable" config
+ to warn on failure instead of exiting with an error. This should
+ make it easier to diagnose configure failures, as the script will
+ now generate a config.h file even when the test fails.
+
+. Removed borland MPC template, use the bmake template from now
+
+. Added Windows Mobile 6 support and improved the WinCE port
+
+. Removed BCB6 and BCB2006 support
+
+. Added BCB2009 MPC template
+
+. Updated stat struct on Windows CE to match the stat struct on other
+ platforms so that application code can be written portable
+
+. Added new ACE_OS wrappers: raise, atof, atol, isblank, isascii,
+ isctype, and iswctype
+
+. Added ACE_OS wrapper for narrow-char version of strtoll.
+
+. ACE_OS wrappers for wide-char versions of strtol, strtoul,
+ strtoll, and strtoll.
+
+. Added Visual Studio 2010 (vc10) support
+
+. Added a new feature for the "Traditional Make" build facility to allow
+ building for multiple architectures out of a single source directory.
+ To use this facility, set the ARCH make variable. The ARCH value will be
+ used to add a subdirectory layer below the source directory where the
+ traditional .shobj, .obj, etc. directories will be placed.
+
+. Added support for HP-UX 11iv3 on Integrity using aC++
+
+. ACE (and TAO) can now be built using GNU make and the Microsoft Visual C++
+ compiler and linker. See include/makeinclude/platform_win32_msvc.GNU for
+ more details.
+
+. Added support for FC10
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.5 and ACE-5.6.6
+====================================================
+
+. Added an option to the ACE_Process_Options class to use a wchar_t
+ environment buffer on Windows.
+
+. A new configure option, --enable-rcsid, was added to the autoconf build.
+ This is used to embed RCS IDs in object files.
+
+. A new method was added: void ACE_Time_Value::msec (ACE_UINT64&)
+ This method, like the existing msec(ACE_UINT64&)const method, obtains the
+ time value in milliseconds and stores it in the passed ACE_UINT64 object.
+ This method was added so that msec(ACE_UINT64&) can be called on both
+ const and non-const ACE_Time_Value objects without triggering compile errors.
+ Fixes Bugzilla #3336.
+
+. Added ACE_Stack_Trace class to allow users to obtain a stack trace
+ within their application on supported platforms. A new conversion
+ character, the question mark, was added to ACE_Log_Msg for stack
+ trace logging.
+
+. Added iterator support to ACE_Message_Queue_Ex class. The resulted in
+ the addition of ACE_Message_Queue_Ex_Iterator class and
+ ACE_Message_Queue_Ex_Reverse_Iterator class.
+
+. Renamed gperf to ace_gperf to prevent clashes with the regular gperf
+ tool that is available in linux distributions
+
+. Added support for FC9
+
+. Added support for OpenSuSE 11.0
+
+. Improved support for GCC 4.2 and 4.3
+
+. Added support for CodeGear C++ Builder 2009
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.4 and ACE-5.6.5
+====================================================
+
+. Added new Monitoring lib that can be used to store and retrieve
+ counters. This is disabled by default because it is not 100%
+ finished yet, with the next release it will be enabled by default
+
+. Fixed bug in ACE_Service_Config when it was used from a thread
+ not spawned by ACE
+
+. Add VxWorks 6.x kernel mode with shared library support to ACE
+
+. Extended the implementation of Unbounded_Set, which has been
+ renamed Unbounded_Set_Ex, to accept a second parameter which is
+ a comparator that implements operator() which returns true if
+ the items are equivalent. Unbounded_Set has been reimplemented
+ in terms of Unbounded_Set_Ex using a comparator that uses operator==,
+ which captures the previous behavior.
+
+. Added support for Intel C++ on MacOSX
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.3 and ACE-5.6.4
+====================================================
+
+. Reworked the relationship between ACE_Service_Config and
+ ACE_Service_Gestalt
+
+. Improved autoconf support
+
+. Improved AIX with gcc support
+
+. Improved OpenVMS support
+
+. Improved VxWorks support
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.2 and ACE-5.6.3
+====================================================
+
+. Deprecated Visual Age 5 and older
+
+. Closed a rare race condition hole whereby ACE_Atomic_Op<> function
+ pointers would not be fully initialized prior to use. See bugzilla
+ 3185 for details.
+
+. Tweaks to support MacOS X Leopard (10.5 and 10.5.1) on Intel
+
+. Fixed compile problems with MinGW with GCC 4.2. Do note that we do see
+ much more test failures then when using GCC 3.4.
+
+. Changed to use synchronous exception handling with msvc 8/9 which is the
+ default. Asynchrous exception handling does catch access violations but
+ it leads to lower performance and other problems. See also bugzilla 3169
+
+. Make ace_main extern C with VxWorks so that it doesn't get mangled
+
+. Fixed compile errors and warnings for VxWorks 6.6
+
+. Added an MPC generator for the WindRiver Workbench 2.6 which is shipped
+ with VxWorks 6.4
+
+. Added support for CodeGear C++ Builder 2007 with December 2007 update
+ installed
+
+. Added support for VxWorks 5.5.1
+
+. Implemented the const reverse iterator for ACE_Hash_Map_Manager_Ex
+
+. Increased support for using ACE_Hash_Map_Manager_Ex with STL
+ functions based on latest standard C++ draft
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6.1 and ACE-5.6.2
+====================================================
+
+. ACE-ified the UUID class, which will change user applications slightly.
+
+. Added support for Sun Studio 12
+
+. Added support for Intel C++ 10.1
+
+. Fixed runtime problems with VxWorks 6.x in kernel mode, several improvements
+ have been made to ACE, but also some problems in the VxWorks kernel have
+ been found for which WindRiver has made patches.
+
+. Added support for VxWorks 6.5 kernel mode
+
+. Added support for MacOS 10.5
+
+. Support for MacOS 10.4 is now deprecated.
+
+. Added support for OpenSuSE 10.3
+
+. Added support for RedHat 5.1
+
+. Added support for Microsoft Visual Studio 2008
+
+. Added support for Fedora Core 8
+
+. Added support for Ubuntu 7.10
+
+. With Ubuntu 7.04 and 7.10 we can't use visibility, that results in
+ unresolved externals when building some tests. With lsb_release we
+ now detect Ubuntu 7.04 and 7.10 automatically and then we disable
+ visibility
+
+. Removed deprecated (un)subscribe methods from ACE_SOCK_Dgram_Mcast
+
+. Added an additional replace() method to ACE_OuptutCDR for replacing a
+ ACE_CDR::Short value. Also added write_long_placeholder() and
+ write_short_placeholder() to properly align the stream's write pointer,
+ write a placeholder value and return the placeholder's pointer. The pointer
+ can later be used in a call to replace() to replace the placeholder with a
+ different value.
+
+. Initial support for VxWorks 6.6
+
+. Removed support for pthread draft 4, 6, & 7. This makes the ACE threading
+ code much cleaner
+
+. Improved autoconf support
+
+. Fixed TSS emulation problems
+
+. Changed ACE_thread_t and ACE_hthread_t to int for VxWorks kernel mode. All
+ thread creation methods do have an additional const char* argument to
+ specify the task name, this now also works with pthread support enabled
+
+. Use bool in much more interfaces where this is possible
+
+. Added support for Debian Etch
+
+. Fixed ACE CDR LongDouble support on VxWorks 6.x
+
+. Added Microsoft Visual Studio 2008 project files to the release packages
+
+. Fixed a few bugs in the ACE_Vector template
+
+USER VISIBLE CHANGES BETWEEN ACE-5.6 and ACE-5.6.1
+====================================================
+
+. Added support for CodeGear RAD Studio 2007
+
+. Added support for CodeGear C++ Builder 2007 Update 3
+
+. Modified the definiton of ACE_DEFAULT_THREAD_KEYS on Windows so it
+ is based on the version of the OS as defined by Microsoft in this web
+ page: http://tinyurl.com/2jqcmd
+ This fixes bugzilla #2753
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.10 and ACE-5.6
+====================================================
+
+. OpenVMS 8.3 on IA64 port
+
+. Added autoconf support for Intel C++ 10.0
+
+. Improved autoconf support on Linux, Solaris, NetBSD and HPUX
+
+. CodeGear C++ Builder 2007 Update 2 support
+
+. The netsvcs's client logging daemon has a new configuration option,
+ -llocal-ip[:local-port], which can be used to specify the local IP
+ address and port number for the client logging daemon's connection to
+ the server logging daemon. If the -l option is specified with an IP
+ address but not a port number, an unused port number is selected.
+
+. A new ACE+TAO port to LabVIEW RT 8.2 with Pharlap ETS. The host build
+ environment is Windows with Microsoft Visual Studio .NET 2003 (VC7.1).
+ Please see the ACE-INSTALL.html file for build instructions.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.9 and ACE-5.5.10
+====================================================
+
+. The ACE_utsname struct, used in the ACE_OS::uname() function when the
+ platform doesn't provide the standard utsname struct, was changed. It
+ defines a number of text fields and their types were changed from
+ ACE_TCHAR[] to char[] in order to be consistent with all other platforms.
+ This removes the need to write different code for platforms where
+ ACE_LACKS_UTSNAME_T is set and that have wide characters (most probably
+ Windows). Fixes Bugzilla #2665.
+
+. The ACE::daemonize() "close_all_handles" parameter was changed from
+ an "int" to a "bool" to better reflect how it is used.
+
+. VxWorks 6.5 support. Compilation of the core libraries has been validated
+ but no runtime testing has been performed.
+
+. CodeGear C++ Builder 2007 support.
+
+. The FaCE utility was moved from the ACE_wrappers/apps directory to
+ ACE_wrappers/contrib. It is used for testing ACE+TAO apps on WinCE.
+ See the ACE_wrappers/contrib/FaCE/README file for more information.
+
+. ACE_INET_Addr::set (u_short port, char *host_name, ...) now favors IPv6
+ addresses when compiled with ACE_HAS_IPV6 defined and the supplied address
+ family is AF_UNSPEC. This means that if host_name has an IPv6 address in
+ DNS or /etc/hosts, that will be used over an IPv4 address. If no IPv6
+ address exists for host_name, then its IPv4 address will be used.
+
+. Intel C++ 10.0 support
+
+. Support for the version of vc8 for 64-bit (AMD64) shipped with the Microsoft
+ Platform SDK.
+
+. Fixed ACE_Vector::swap() (bugzilla #2951).
+
+. Make use of the Atomic_Op optimizations on Intel EM64T processors. The
+ Atomic_Op is now several times faster on EM64T then with previous versions
+ of ACE
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.8 and ACE-5.5.9
+====================================================
+
+. Use Intel C++ specific optimizations for Linux on IA64
+
+. Improved support for ACE_OS::fgetc. Added support for ACE_OS::fputc,
+ ACE_OS::getc, ACE_OS::putc and ACE_OS::ungetc.
+
+. Added support for ACE_OS::log2(double) and improved support for
+ ACE::log2(u_long).
+
+. Shared library builds on AIX now produce a libxxx.so file instead of the
+ previous practice of producing libxxx.a(shr.o).
+
+. GCC 4.1.2 that comes with Fedora 7 seems to have a fix for the visibility
+ attribute we use for the singletons. F7 users will therefore need to
+ define the following in your config.h file.
+ ACE_GCC_HAS_TEMPLATE_INSTANTIATION_VISIBILITY_ATTRS 1
+
+. Fixed (rare) problem in TP_Reactor where incorrect event handler was
+ resumed.
+
+. Reduced footprint on some platforms, particularly those that use
+ g++ >= 3.3.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.7 and ACE-5.5.8
+====================================================
+
+. Extended ACE_Event constructor with optional LPSECURITY_ATTRIBUTES
+ argument
+
+. Added support for QT4
+
+. Added support to integrate with the FOX Toolkit (www.fox-toolkit.org)
+
+. Added support for Microsoft Visual Studio Code Name "Orcas", which is
+ the msvc9 beta
+
+. Added ability to provide an optional priority when calling
+ ACE_Message_Queue_Ex::enqueue_prio(). There was previously no way
+ to specify a priority for queueing.
+
+. Removed support for Visual Age on Windows.
+
+. ACE will compile once again with ACE_LACKS_CDR_ALIGNMENT #defined.
+
+. ACE_Process_Manager::terminate() no longer removes the process from the
+ process descriptor table; the pid remains available in order to call
+ ACE_Process_Manager::wait().
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.6 and ACE-5.5.7
+====================================================
+
+. ACE 5.5 contained a set of pragmas which prevented Visual Studio 2005 (VC8)
+ from issuing warnings where C run-time functions are used but a more
+ secure alternative is available. For more information on the C run-time
+ issues and Microsoft's response, please see the following MSDN page:
+ http://msdn2.microsoft.com/en-us/library/8ef0s5kh(VS.80).aspx.
+ In this beta, the pragmas which prevented the warnings have been removed.
+ The ACE library has been reviewed and most of the use of "unsafe" functions
+ has been fixed where possible. Since not all of the warnings emanating from
+ ACE are situations that can or should be fixed, the ACE VC8 projects will
+ prevent the warnings while building the ACE kit and its contained examples,
+ tests, etc. The warnings are disabled by adding Microsoft-specified macros
+ to the compile line via MPC. If desired, the warnings can be re-enabled by
+ regenerating the project files with different MPC features. Note, however,
+ that while ACE without warnings caused by the new C run-time functions, your
+ application builds may trigger these warnings either by use of the "unsafe"
+ C run-time functions or via use of an inlined ACE_OS method which uses it.
+ If the warning is caused by an ACE_OS method, there is a more safe alternate
+ available, probably located by appending _r to the method name (e.g.,
+ instead of using ACE_OS::ctime(), use ACE_OS::ctime_r()).
+ There are other cases where the compiler may have issued warnings and ACE
+ prevented this via a #pragma. These #pragmas have been removed as well.
+ This may cause your application builds to trigger more warnings from VC8
+ than past ACE versions. You should review your code and either correct
+ the code or disable the warnings locally, as appropriate.
+
+. The "release" argument to a number of ACE_String_Base<> methods was changed
+ from int to bool to more accurately reflect its purpose. The following
+ methods were changed:
+
+ ACE_String_Base (const CHAR *s,
+ ACE_Allocator *the_allocator = 0,
+ int release = 1);
+ to
+ ACE_String_Base (const CHAR *s,
+ ACE_Allocator *the_allocator = 0,
+ bool release = true);
+
+ ACE_String_Base (const CHAR *s,
+ size_type len,
+ ACE_Allocator *the_allocator = 0,
+ int release = 1);
+ to
+ ACE_String_Base (const CHAR *s,
+ size_type len,
+ ACE_Allocator *the_allocator = 0,
+ bool release = true);
+
+ void set (const CHAR * s, int release = 1);
+ to
+ void set (const CHAR * s, bool release = true);
+
+ void set (const CHAR * s, size_type len, int release);
+ to
+ void set (const CHAR * s, size_type len, bool release);
+
+ void clear (int release = 0);
+ to
+ void clear (bool release = false);
+
+ Since ACE_String_Base forms the basis of the ACE_CString and ACE_TString
+ classes, this may ripple out to user application code. If you encounter
+ errors in this area while building your applications, replace the
+ int argument you are passing to the method now with either true or false.
+
+. Solutions for the eVC3/4 platform have been removed from this
+ release. Note that we package WinCE projects/workspaces for use
+ with VC8.
+
+. There were 3 new ACE_Log_Msg logging format specifiers added to make logging
+ easier for types that may change sizes across platforms. These all take one
+ argument, and the new formats are:
+ %b - format a ssize_t value
+ %B - format a size_t value
+ %: - format a time_t value
+
+. The ace/Time_Request_Reply.h and ace/Time_Request_Reply.cpp files were
+ moved from $ACE_ROOT/ace to $ACE_ROOT/netsvcs/lib. The time arguments in
+ the public API to ACE_Time_Request were changed from ACE_UINT32 to time_t
+ and the portions of the on-wire protocol that contains time was changed from
+ ACE_UINT32 to ACE_UINT64. Thus, code that uses the ACE_Time_Request class
+ to transfer time information will not interoperate properly with prior
+ ACE versions. This will affect uses of the netsvcs time clerk/server.
+
+. The portion of the ACE_Name_Request class that carries the on-wire seconds
+ portion of a timeout value was changed from ACE_UINT32 to ACE_UINT64. This
+ means that Name server/clients at ACE 5.5.7 and higher will not interoperate
+ properly with previous ACE versions' name servers/clients.
+
+. In the ACE_Log_Record (ACE_Log_Priority, long, long) constructor, the
+ second argument, long time_stamp, was changed to be of type time_t. This
+ aligns the type with the expected value, a time stamp such as that returned
+ from ACE_OS::time().
+
+. Added support for VxWorks 6.x cross compilation using a Windows host
+ system
+
+. Added support for VxWorks 6.x using the diab compiler
+
+. The destructor of ACE_Event_Handler no longer calls
+ purge_pending_notifications(). Please see bugzilla #2845 for the full
+ rationale.
+ (http://deuce.doc.wustl.edu/bugzilla/show_bug.cgi?id=2845)
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.5 and ACE-5.5.6
+====================================================
+
+. The ACE_TYPENAME macro has been added to those that are not
+ available when the ACE_LACKS_DEPRECATED_MACROS config option is set
+ (it is not set by default). You are encouraged to replace the use of
+ ACE_TYPENAME with the C++ typename keyword before the ACE_TYPENAME
+ macros is removed from ACE in the future.
+
+. A new script, rm_exception_macros.pl, has been added to help users
+ remove the use of the ACE exception macros from their own code.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.4 and ACE-5.5.5
+====================================================
+
+. The prebuild MPC keyword is now supported by the gnuace project type.
+ This fixes Bugzilla #2713.
+
+. Support for Windows earlier than NT 4 SP2 was removed. ACE will not build
+ for Windows 95, 98, Me, etc. out of the box any longer.
+
+. Reformat stringified IPv6 addresses to use [addr]:port when printing
+ addresses that contain ':' such as "::1".
+
+. Added method to ACE_INET_Addr to determine if address is IPv6 or
+ IPv4 multicast.
+
+. Fixed a bug in ACE_Async_Timer_Adapter_Timer_Queue_Adapter where the
+ gettimeofday function of the timer queue was ignored when setting the alarm.
+
+. Fixed a problem where, on Solaris 9 onwards, calling
+ ACE_OS::thr_create(THR_NEW_LWP) more than 2^15 (65535) times in a
+ process will fail. See changelog entry from "Wed Jan 3 22:31:05 UTC
+ 2007 Chris Cleeland " for more information.
+
+. Fixed a bug in ACE_QtReactor where the two select() calls in that function
+ might select on different handler sets.
+
+. ACE_SOCK_IO::recvv(iovec[], size_t, const ACE_Time_Value* = 0) and
+ ACE_SOCK_IO::sendv (const iovec[], size_t, const ACE_Time_Value* = 0) methods
+ were changed to specify the iovec count argument as int instead of size_t
+ since it gets reduced to int in the underlying OS calls (usually).
+
+. The following deprecated methods were removed:
+
+ ssize_t ACE_SOCK_IO::recv (iovec iov[],
+ size_t n,
+ const ACE_Time_Value *timeout = 0) const;
+
+ ssize_t ACE_SOCK_IO::recv (iovec *io_vec,
+ const ACE_Time_Value *timeout = 0) const;
+
+ ssize_t ACE_SOCK_IO::send (const iovec iov[],
+ size_t n,
+ const ACE_Time_Value *timeout = 0) const;
+
+ These were previously replaced with more specific recvv() and sendv()
+ methods.
+
+. The ACE_Service_Repository::find(const ACE_TCHAR name[],
+ const ACE_Service_Type **srp = 0,
+ int ignore_suspended = true) const
+ method's 'ignore_suspended' parameter was changed from int to bool to
+ reflect it's purpose as a yes/no indicator.
+
+. Added --enable-ace-reactor-notification-queue configure script
+ option to the autoconf build for enabling the Reactor's userspace
+ notification queue (defines ACE_HAS_REACTOR_NOTIFICATION_QUEUE in
+ config.h).
+
+. The int ACE_OutputCDR::consolidate(void) method was contributed by
+ Howard Finer at Sonus Networks. This method consolidates any continuation
+ blocks used by an ACE_OutputCDR object into a single block. It's useful for
+ situations which require access to a single memory area containing the
+ encoded stream, regardless of its length, when the length cannot be known
+ in advance.
+
+. There are a number of new methods defined on ACE_String_Base:
+
+ size_t capacity (void) const: This method returns the number
+ of allocated CHAR units in the string object.
+
+ void fast_resize (size_t): This method manage the sizing/reallocating
+ of the string, but doesn't do the memory setting of resize().
+
+ bool operator!= (const CHAR *) const
+ bool operator== (const CHAR *) const: These methods compare the
+ string with a nul-terminated CHAR* string.
+
+ nonmember functions operator== and operator!= where also added
+ that compare const ACE_String_Base and const CHAR*; these make
+ it possible to switch ACE_String and CHAR* on either side of
+ the operator.
+
+ Thank you to Kelly Hickel for these additions.
+
+. There are 2 new build options on the traditional make command:
+ dmalloc and mtrace. When specified at build time (e.g. make mtrace=1)
+ the PLATFORM_DMALLOC_CPPFLAGS and/or PLATFORM_MTRACE_CPPFLAGS values
+ are added to CPPFLAGS. For dmalloc, the PLATFORM_DMALLOC_LDFLAGS and
+ PLATFORM_DMALLOC_LIBS are added to LDFLAGS and LIBS, respectively.
+ Thank you to Howard Finer for supplying these additions.
+
+. Added the ability to specify additional purify and quantify command-line
+ options by setting PLATFORM_PURIFY_OPTIONS and PLATFORM_QUANTIFY_OPTIONS,
+ respectively. Thank you to Howard Finer for supplying these additions.
+
+. Added the ability to use trio (http://sourceforge.net/projects/ctrio/)
+ if platform lacks decent support for vsnprintf. trio support is
+ enabled by defining trio=1 in plaform_macros.GNU
+
+. Removed Irix 5, DGUX, and m88k support
+
+. Improved LynxOS 4.2 support
+
+. VxWorks 6.4 support
+
+. Added support for FC6. Because the GCC 4.1.1 version that gets shipped
+ has a fix for the visibility attribute we use for the singletons
+ you will need to define the following in your config.h file. This can't be
+ done automatically because SuSE 10.2 gets shipped with GCC 4.1.2 but
+ doesn't have the same fix
+ ACE_GCC_HAS_TEMPLATE_INSTANTIATION_VISIBILITY_ATTRS 1
+
+. RTEMS port
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.3 and ACE-5.5.4
+====================================================
+
+. Added appropriate intptr_t and uintptr_t typedefs on platforms that
+ don't provide them (i.e. when ACE_LACKS_INTPTR_T is defined).
+
+. Added ability to explicitly choose support for 32 bit or 64 bit file
+ offsets on all platforms. Define the _FILE_OFFSET_BITS preprocessor
+ symbol to either 32 or 64 to choose the desired number of file
+ offset bits. This preprocessor symbol is supported natively by most
+ UNIX and UNIX-like operating systems, and supported by ACE on
+ Windows. Use the new ACE_OFF_T typedef to refer to file offsets
+ across UNIX and Windows portably.
+
+. 64-bit file offsets are now enabled by default in Win64
+ configurations.
+
+. Improved support for 64 bit platforms (64 bit addresses, etc).
+
+. Added STL-style traits, iterators and a swap() method to the
+ ACE_Array_Base<> class template.
+
+. Added STL-style traits and iterator accessors to the
+ ACE_Hash_Map_Manager_Ex<> class template, as well as new find() and
+ unbind() methods that return (as an "out" parameter) and accept
+ iterators, respectively.
+
+. Greatly improved event handler dispatch performance in
+ select()-based reactors (e.g. ACE_Select_Reactor and ACE_TP_Reactor)
+ for large handle sets on Windows. Previous event handler search
+ were linear, and are now constant on average.
+
+. Addressed a number of Coverity errors (CHECKED_RETURN, DEADCODE,
+ LOCK, USE_AFTER_FREE, RESOURCE_LEAK, FORWARD_NULL).
+
+. Added STL-style "element_type" trait to all ACE auto_ptr class
+ templates.
+
+. Removed support for LynxOS 3.x.
+
+. Resolved Bugzilla #2701 to ensure fini() is called for all
+ Service Objects upon calling ACE_Service_Config::close()
+
+. VxWorks 5.5.2 has been tested, for ACE the support is exactly
+ the same as for VxWorks 5.5.1. No specific defines or flags have
+ to be used.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.2 and ACE-5.5.3
+====================================================
+
+. Added the base projects for executionmanager_stub and plan_generator.
+
+. Added the ACE_Hash_MultiMap_Manager class and its test file.
+
+. Changed the ACE_Synch_Options::operator[] method to return bool rather than
+ int. The value returned is a yes/no indication of whether or not the
+ specified option(s) are set in the object.
+
+. Changed the prototype(s) for ACE::debug () to return (and take) a
+ bool. This is consistent with the original intent for this
+ feature. If you have been using it like 'ACE::debug () > 0' or
+ 'ACE::debug (1)', you may have to rebuild ACE. The value of the
+ ACE_DEBUG environment variable can be used to specify the initial
+ value for ACE::debug(), at the process start up.
+
+. An assembler (within a C source file) based implementation for SPARC
+ of atomic operations suitable for use with the
+ ACE_Atomic_Op and
+ ACE_Atomic_Op specializations has
+ been added. Currently, it can only be enabled by setting the
+ atomic_ops_sparc make macro to 1 when using the GNUACE build system with
+ the Solaris SunCC compiler. It should be noted that this requires the
+ -xarch=v8plus (or higher) be added to the CFLAGS make macro or the
+ assembler code will not compile.
+
+. The ACE_Message_Queue_Ex_N class
+ is new, contributed by Guy Peleg .
+ ACE_Message_Queue_Ex_N is
+ similar to ACE_Message_Queue_Ex in that the object queued is a
+ template parameter. However, ACE_Message_Queue_Ex_N allows the
+ enqueueing and dequeueing of multiple chained objects at once. This
+ wasn't added to ACE_Message_Queue_Ex because the chained object
+ functionality requires the ACE_MESSAGE_TYPE class to have a
+ ACE_MESSAGE_TYPE *next (void) const method, analogous to
+ ACE_Message_Block::next(), to follow the chain and this would
+ probably break existing applications using ACE_Message_Queue_Ex.
+ The ACE_wrappers/tests/Message_Queue_Test_Ex.cpp test has an example of
+ how to use the new class.
+
+. The selector and comparator function pointer arguments to ACE_OS::scandir()
+ and ACE_Dirent_Selector are now marked as extern "C" to enforce their
+ use with a C RTL function. User code that defines functions which are
+ passed as the selector or comparator arguments which are not declared
+ extern "C" may generate compile warnings. To resolve this, add extern "C"
+ to the function's signature. See ACE_wrappers/tests/Dirent_Test.cpp for
+ an example.
+
+. To address a problem in the ACE string interface that prevented
+ substring or character searches in very large strings (e.g. greater
+ than the maximum value of an ssize_t type) from being correctly
+ reported to the caller, the find(), rfind() and strstr() methods now
+ return an unsigned integer (size_t) instead of a signed one
+ (ssize_t). Affected classes include:
+
+ * ACE_CString
+ * ACE_WString
+ * ACE_TString
+ * ACE_NS_WString
+
+ Unless you have been explicitly using -1 instead of npos when
+ comparing the return value of find(), rfind() and strstr(), and/or
+ assigning the return value to ssize_t you should not see any
+ difference. A new size_type typedef has been added to the ACE string
+ class to aid developers. This typedef is analogous to the standard
+ C++ string::size_type typedef.
+
+ The ACE_String_Base<>::strstr() documentation and the default
+ rfind() argument erroneously referred to -1 instead of npos. Those
+ instances have been corrected.
+
+ To summarize, a "no position" condition is denoted using the npos
+ constant, not -1. It can be referred directly by scoping it with the
+ appropriate string class (e.g. ACE_CString::npos, ACE_WString::npos,
+ etc).
+
+. Changing the shared library extension for hpux ia64 to ".so". On
+ HP-UX 11i Version 1.5 the naming scheme is lib*.sl for PA and
+ lib*.so on IPF.
+
+. The ACE_Refcounted_Auto_Ptr reset() and release() methods were changed
+ per Bugzilla #1925. They will both now detach from the underlying
+ ACE_Refcounted_Auto_Ptr_Rep object; reset() will create a new one for
+ the new pointer specified as its argument. This change may cause referenced
+ objects to be deleted in cases where previous ACE versions would not have.
+
+. The return type of "ACE_Refcounted_Auto_Ptr::null (void) const" changed
+ from int to bool. It's possible values, true and false, have not changed.
+
+. TTY_IO now accepts "none" as a valid parity value. Due to this change
+ 'parityenb' member is now deprecated and will be removed in the future.
+ The users of TTY_IO class should change their code to use only 'paritymode'
+ member for parity control and leave 'parityenb' unchanged (it is
+ enabled by default in class constructor).
+
+. Support for Intel C++ 9.1 on Windows and Linux
+
+. VxWorks 6.3 support
+
+. Fixed Bugzilla #2648 to make sure ACE_Service_Object::fini()
+ is called iff ACE_Service_Object::init() succeeded, as per
+ C++NPv2.
+
+. Added preliminary support for Mac OS X 10.4 on Intel CPU's.
+
+. Fixed Bugzilla #2602 to re-enable XML Service Configurator
+ file support.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5.1 and ACE-5.5.2
+====================================================
+
+. Added support for:
+ - VxWorks 6.2 for the rtp model using pthread support
+ - OpenVMS 8.2 for Alpha
+
+. Removed code and configurations that provided support for:
+ - Visual C++ 6.0 and 7.0
+ - Chorus
+ - pSOS
+ - KAI C++ on all platforms
+
+. Explicit template instantiation support has been removed. This effectively
+ removes support for Sun Forte 6 and 7 which required explicit template
+ instantiation to build ACE reliably.
+
+. Added support for multiple independent Service Repositories through
+ configuration contexts called "Gestalt". Full backwards compatibility
+ is maintained through the existing ACE_Service_Config static methods,
+ while direct individual repository access is enabled through instances
+ of the new ACE_Service_Gestalt class. ACE_Service_Config has changed to
+ a specialization of ACE_Service_Gestalt and is only responsible for the
+ process-wide configuration.
+
+. To support dynamically-sized ACE_Log_Record messages, the netsvcs
+ logging components now use ACE CDR encoding and transfer mechanisms
+ inspired by the examples in Chapter 4 of the C++NPv1 book.
+ The client and server logging daemons in ACE 5.5.2 and forward will
+ not interoperate with those in previous ACE versions.
+
+. Added a wrapper for the sendfile API (ACE_OS::sendfile()).
+
+. Added support for netlink sockets on Linux.
+
+. Added a new method, ACE_Task::last_thread(). This method returns the thread
+ ID (ACE_thread_t) of the last thread to exit from the ACE_Task object.
+ Users checking to see if a thread is the last one out (for example, to know
+ when to perform cleanup operations) should compare the current thread ID to
+ the return value from last_thread(). This is a change from the previously
+ recommended practice (C++NPv2, page 189) of comparing the return value of
+ thr_count() with 0.
+
+. Changed the first argument to ACE_OS::strptime() to be 'const' which
+ matches its usual usage in POSIX strptime(). This change allows users to
+ pass const strings in - a common use case.
+
+. Made part of the file support in ACE 64bit but we have some places where
+ 32bit types are used, this could lead to some conversion warnings which
+ will be addressed in the near future, but getting everything 64bit
+ compliant is a lot of work.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.5 and ACE-5.5.1
+====================================================
+
+. Added support for the --enable-symbol-visibility configure option
+ to the autoconf build infrastructure instead of solely relying on
+ feature tests to enable/disable symbol visibility support. This
+ avoids build problems with icc, etc.
+
+. Added support for the --enable-fl-reactor configure option to the
+ autoconf build infrastructure to build the ACE_FlReactor library.
+
+. Added support for the --enable-qt-reactor configure option to the
+ autoconf build infrastructure to build the ACE_QtReactor library.
+
+. Added support for the --enable-xt-reactor configure option to the
+ autoconf build infrastructure to build the ACE_XtReactor library.
+
+. Fixed a bug that would cause timer IDs from ACE_Timer_Heap to be
+ improperly duplicated under certain conditions (Bugzilla #2447).
+
+. Fixed ACE_SSL_Context::private_key(), context(), and dh_params() methods
+ to allow retrying a file load after a failed call.
+
+. Fixed ACE_SSL_Asynch_Stream so it can be instantiated; also moved the
+ declarations for ACE_SSL_Asynch_Read_Stream_Result,
+ ACE_SSL_Asynch_Write_Stream_Result, and ACE_SSL_Asynch_Result classes
+ to the ace/SSL/SSL_Asynch_Stream.h file so applications can see them.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.10 and ACE-5.5
+====================================================
+
+. Added a platform macros option "templates=manual", currently only
+ applies to AIX 5.3 with XL 7 compiler. It allows the user to tell the
+ compiler to set -qnotempinc and -qnotemplateregistry and works well
+ in static builds.
+
+. ACE and its tests compile error free with GCC 4.1 pre release.
+
+. ACE_Recursive_Thread_Mutex::get_nesting_level() fixed for 64-bit Windows
+ XP on amd64/EM64T hardware.
+
+. Many build-time fixes for Windows Mobile 5 and Windows PocketPC 2003 using
+ Visual Studio .NET 2005 (VC8).
+
+. Added support for the --enable-tk-reactor configure option to the
+ autoconf build infrastructure to build the ACE_TkReactor library.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.9 and ACE-5.4.10
+====================================================
+
+. Fixed a bug in ACE_Timer_Heap_T::cancel().
+
+. Improved ACE_Time_Value support for boundary conditions.
+
+. Fixed problems with operator placement delete on certain C++ compilers.
+
+. Fixed a bug with the ACE_SPIPE_Acceptor on Windows.
+
+. Correctly set sockaddr_in.sin_len and sockaddr_in6.sin6_len on
+ platforms that have these fields.
+
+. Avoided problems with namespace pollution for max() macros.
+
+. Many fixes for ACE_LACKS* and ACE_HAS* macros for autoconfig.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.8 and ACE-5.4.9
+====================================================
+
+. Added dozens of new ACE_LACKS and ACE_HAS defines which are used to
+ simplify the ACE_OS layer
+
+. Constructors of ACE_Time_Value have been made explicit to prevent
+ implicit conversions.
+
+. Added a shutdown() method to ACE_Barrier. The new method aborts the
+ wait by all threads.
+
+. Changed the behavior of ACE_Message_Queue::enqueue_head() and
+ enqueue_tail(). If the enqueued message block has other blocks
+ chained to it via its next() pointer, the entire chain of blocks
+ will be enqueued at once.
+
+. Improved the support for high-resolution timers with
+ ACE_Timer_Queue_Adapter.
+
+. Make it possible to disable file caching in JAWS.
+
+. Improved ACE_Pipe implementation so that it uses localhost to avoid
+ firewall problems.
+
+. Added Unicode support to the Service Configurator.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.7 and ACE-5.4.8
+====================================================
+
+. Improved IPv6 support
+
+. Improved 64bit portability
+
+. TTY_IO overhaul
+ - Improved documentation.
+ - It is now possible to request infinite timeout in portable manner.
+ This can be achieved by setting negative value to readtimeoutmsec.
+ - Various bugs fixed and portability issues resolved.
+
+. Subset ACE for TAO and TAO Services
+
+. Support for Intel C++ 9.0 on Windows and Linux
+
+. Support for Microsoft Visual Studio 2005 (aka VC8) for Win32 as well
+ as the Windows CE platforms Pocket PC 2003 and Windows Mobile 5.
+ Solution/project files are generated with an appended "_vc8" for
+ Win32 and "_WinCE" for the CE platforms. See
+ ACE_wrappers/docs/CE-status.txt for more information.
+
+. Completed implementation of ACE_Dev_Poll_Reactor using the Linux epoll
+ facility; tested on Red Hat Enterprise Linux 4.
+
+. The in-memory size of an ACE_RB_Tree will be smaller due to rearranged
+ placement of pointers.
+
+. Added an optimization to CDR stream to ignores alignment when marshaling
+ data. Use this new ACE_LACKS_CDR_ALIGNMENT compile-time option only
+ when the ACE_DISABLE_SWAP_ON_READ macro is enabled. This new option
+ requires ACE CDR engine to do both marshaling and demarshaling, and
+ when this option is enabled the encoded streams are no longer
+ compliant with the CORBA CDR specification.
+
+. Developed Feature Oriented Customizer (FOCUS) tool to enable
+ specialization of middleware frameworks such as Reactor and Protocol
+ framework. FOCUS provides an XML based transformation engine, where
+ the transformations to specialize the components are captured in XML
+ file and a weaver specializes the code.
+
+. Added support for unrolling ACE_OS::memcpy copy loop where
+ applicable to improve performance. Autoconf tests empirically
+ determine whether loop unrolling is at least 10% better than default
+ version.
+
+. Added support for an ACE "versioned" namespace. When enabled, ACE
+ library sources will be placed within a namespace of the user's
+ choice or a namespace of the form ACE_5_4_7 by default, where
+ "5_4_7" is the ACE major, minor and beta versions. The default may
+ be overridden by defining the ACE_VERSIONED_NAMESPACE_NAME
+ preprocessor symbol. Enable overall versioned namespace support by
+ adding "versioned_namespace=1" to your MPC default.features file.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.6 and ACE-5.4.7
+====================================================
+
+. Support for shared libraries with VxWorks
+
+. Support for Solaris 10 on x86 with Sun Studio 10 (C++ 5.7).
+
+. Extended ACE_OS::event_xxx implementation to support platforms
+ having either PThread support with Process Shared condition
+ variables or POSIX semaphores with named (process shared)
+ semaphore support or using the new FIFO based semaphores.
+
+. ACE_OS::closesocket() no longer calls ACE_OS::shutdown() on any platform
+ while closing the socket. It previously called ACE_OS::shutdown() on
+ HP-UX. Removing this call fixes the fork-and-close programming paradigm
+ that's common to many networked applications.
+
+. RMCast
+ - Support for message fragmentation. This will allow
+ for messages larger than 64K.
+ - Support for flow control.
+ - Timed recv() in RMCast::Socket.
+ - Per-instance configurable protocol parameters (e.g., message
+ retention time, NAK timeout, etc).
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.5 and ACE-5.4.6
+====================================================
+
+. Updated RMCast to include
+ - Reactor-compatible interface.
+ - Message unavailability reporting.
+ - Protocol documentation.
+
+. Added support for 64bit Visual Age on AIX
+
+. Improved g++ 4.0 support. A number of RTTI related problems have been
+ fixed.
+
+. Smaller footprint.
+
+. Fixed memory leaks ACE_DLL and ACE_Log_Msg classes.
+
+. The ACE::ICMP_Socket and ACE::Ping_Socket classes were moved out of
+ the ACE namespace and "flattened" to ACE_ICMP_Socket and
+ ACE_Ping_Socket to be consistent with the rest of ACE.
+
+. ACE_INET_Addr::set_address() - fixed a possible struct member
+ alignment issue when building an IPv4-mapped IPv6 address.
+
+. Added a new ACE::wild_match() function to match a string based on
+ wildcards.
+
+. Added efficient overloads for string concatenation to the
+ ACE_String_Base class.
+
+. Added support for the use of pthread_getschedparam on MacOS X.
+
+. Fixed an issue with static initialization of TSS related classes on
+ static builds for Windows.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.4 and ACE-5.4.5
+====================================================
+
+. Remove special handling in the Thread Specific Storage(TSS) code
+ that released the TSS key for ACE_TSS. ACE_TSS has
+ been changed to explicitly free the TSS key when necessary.
+
+. On Win32 systems: detect thread termination via a hook in DLLMain
+ for ACE.dll. This allows cleanup of TSS objects for non-ACE threads
+ that use ACE functions. The most common case was threads that used
+ ACE logging. Formerly any TSS objects created by these threads would
+ be leaked.
+
+. Added support for GNU G++ 4.0. The x.4.5 beta takes advantage of
+ g++ 4.0's symbol visibility. This feature is conceptually similar
+ to MS Windows "__declspec(dllexport)" DLL functionality. Using this
+ new g++ feature results in substantially improved ACE/TAO/CIAO
+ shared library binaries. A subset of the improvements include the
+ following:
+
+ * The number of unnecessarily exported DSO/DLL symbols is
+ greatly reduced, resulting in faster program start times.
+ * Smaller footprint.
+ * Improved performance since run-time indirection of internal
+ symbols is no longer needed.
+
+ No changes to the ACE/TAO sources were necessary to support this
+ feature since the required visibility attributes were hidden behind
+ the various "*_Export" macros (formerly only useful for MS Windows
+ DLLs) used throughout ACE/TAO.
+
+. The ACE_Reactor destructor will now call close() on the referenced reactor
+ implementation. This assures that all handlers are notified before the
+ ACE_Reactor object that's most likely referenced in these handlers is
+ invalid. Although this should not be a user-visible change, it did catch
+ some ACE tests off guard destroying reactor implementations and ACE_Reactor
+ interfaces in the wrong order, so it may come up in the field as well.
+ When using dynamically allocated reactor implementations, do not destroy
+ the implementation object before the ACE_Reactor interface object. Use of
+ the ACE_Reactor constructor's delete_implementation argument (with a value
+ of 1) is recommended when dynamically allocating reactor implementations.
+
+. Improved performance of HTBP by not requiring a lookup of peer hostname.
+
+. Added new ACE_SizeCDR stream which allows one to calculate size of the
+ representation without writing anything.
+
+. Number of improvements in RMCast, reliable multicast implementation.
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.3 and ACE-5.4.4
+====================================================
+
+. The ace-config script has been replaced by pkg-config metadata files
+ which are installed in ${prefix}/lib/pkgconfig by the automake build.
+
+. Remove ACE_OS::gets() implementation. While this ACE implementation
+ of gets() did not contain the security holes that all standard
+ gets() implementations have, keeping it around only serves to foster
+ confusion since (1) some may incorrectly assume that this
+ ACE-specific gets() implementation has the same holes as standard
+ ones, and (2) invoking it with a default size argument so that it
+ looks like a standard gets() call results in behavior that is
+ different from the standard. Use ACE_OS::fgets() instead.
+
+. Removed ACE_Unbounded_Set_Ex, this gave the false idea that it had
+ thread safe iterators. Use ACE_Unbounded_Set instead
+
+. Improved VxWorks support for static libraries. Shared libraries do cause
+ several known problems which will be fixed in the x.4.5 release.
+
+. Removed the usage of the ACE_x_cast macros, we are using the C++ casts
+ from now on. The ACE_x_cast macros are deprecated and will be removed
+ after the x.5.1 release
+
+. Some improvements in autoconf support; better detection of available
+ OS and compiler features.
+
+. Fixed bugs in ACE TSS emulation
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.2 and ACE-5.4.3
+====================================================
+
+. Improved Cygwin 1.5.12 support, 90% of the tests now succeed
+
+. Improved OpenVMS support.
+
+. Added ability to use fltk with Cygwin/MinGW
+
+. Added ACE_INT64 that defines a native 64 bit type.
+
+. Added 'q' as usable specifier for ACE_Log_Msg to print out int64 bit number.
+
+. Added better support for Intel C++ compilers.
+
+. Improved HPUX support.
+
+. Added a new directory ("ACE_wrappers/protocols/ace") for new protocols
+ that are not directly components of ACE, but are relate to ACE and
+ defined a new protocol, HTBP (Hypertext Tunneling, Bidirectional
+ Protocol) providing ACE_Acceptor/Connector/Stream semantics over a
+ connection owned by an HTTP proxy. Test cases in
+ ACE_wrappers/tests/HTBP provide examples of use.
+
+. Performace enhancement in TP_Reactor's handle_timer_events method [Bug
+ 1971].
+
+. Various changes to permit ACE to execute on HP NonStop platform (e.g
+ support for its pthreads version).
+
+. Updated HP NonStop configuration files (config-tandem-nsk).
+
+. The "ACE" pseudo-namespace is now a true C++ namespace. Transitional
+ pseudo-namespaces that were only meant to be used internally by ACE,
+ such as "ACE_Sock_Connect", no longer exist.
+
+. ACE_CDR::Boolean type is now a true C++ "bool" on all platforms except
+ MSVC++ 6. We plan to deprecate MSVC++ 6 support sometime after the
+ x.5 release of ACE+TAO+CIAO, so we recommend you start migrating to a
+ later version of MSVC++.
+
+. More GNU g++ 3.4.x fixes.
+
+. Added ICMP and "ping" socket support.
+
+. Added mkstemp() emulation.
+
+. Fixed problem on Linux < 2.5.47 platforms where equality comparison of
+ two logically equal sockaddr_in structure instances would incorrectly
+ fail.
+
+. Support for wide characters has been improved on non-Windows
+ platforms.
+
+. A number of Windows CE problems have been fixed.
+
+. ACE's loading of DLLs (for example, as a result of loading synamic
+ services) has been changed to use the native OS's facilities for
+ locating the DLL instead of searching LD_LIBRARY_PATH (or its
+ equivalent) then loading the DLL using a full pathname. This restores
+ enforcement of a platform's loading and security policy. To use the
+ old DLL locating method, add ACE_MUST_HELP_DLOPEN_SEARCH_PATH to your
+ config.h file before building ACE.
+
+. A number of errors in the APG example programs have been corrected.
+
+. Select_Reactor and Priority_Reactor performance improved. [Bug 1890]
+
+. Wide-char functionality on POSIX (Linux, etc.)
+
+. TSS memory leak fixes [Bug 1542]
+
+. Ported to HPUX 11i v2 on Itanium
+
+. Added code to ACE for platform RedHat AS 3.0 on Opteron.
+
+. Changed ACE::crc32() family of functions to NOT fold in the length of
+ the string/buffer/iovec into the CRC.
+
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4.1 and ACE-5.4.2
+====================================================
+
+. Support for g++ 3.4.1.
+
+. All ACE Makefiles, project files, etc, are now generated by OCI's
+ "MakeProjectCreator" (MPC) tool. Makefiles and project files for
+ commonly used configurations have been pre-generated and distributed
+ with the beta(s). Please see:
+
+ $ACE_ROOT/ACE-INSTALL.html
+
+ for information on how to use MPC with ACE.
+
+. Improved Doxygen documentation.
+
+. Reduced header file dependencies, which should speedup compilation
+ and help minimize static footprint.
+
+. ACE now requires support for the following standard C++ features:
+
+ - "bool" keyword
+
+ - "mutable" keyword
+
+ - "explicit" keyword
+
+ - C++ casts (e.g. static_cast<>, reinterpret_cast<>, dynamic_cast<>
+ and const_cast<>)
+
+ If you're using a compiler that does NOT support these features
+ please contact Steve Huston for support.
+
+. Changed the select()-based reactor implementations to scan for
+ broken handles to remove based on the registered handles, not on
+ event handlers. This allows for bad handles to be removed from the
+ reactor even if the event handler doesn't implement get_handle() the
+ way we expect.
+
+. Support for Pthreads native recursive mutexes was added. This
+ capability is specified to ACE_OS::mutex_init() as an optional
+ argument, lock_type. To fix confusion from an earlier attempt to add
+ this functionality, the meaning of the old 'type' argument to
+ ACE_OS::thread_mutex_init() is changed. It previously combined the
+ scope and type. Now it is just the type (e.g. recursive), as the
+ scope is inherent in the method used. For clarification on
+ ACE_HAS_RECURSIVE_MUTEXES, it means that the platform is capable of
+ them, not that they always are, as one would expect. However, before
+ Pthreads had recursion added, it was never optional. Now it is.
+
+. Initial support for new Linux sys_epoll() interface in
+ Dev_Poll_Reactor. The obsolete Linux /dev/epoll interface is no
+ longer supported.
+
+. Improved Cygwin support.
+ - Threading works without problems.
+ - Problems with shared memory, process shared mutexes, multicast and
+ some other small things still exist.
+
+. New OpenVMS port.
+ - This is for the latest version of OpenVMS with all available ECOs
+ applied. Basic stuff works without problems. Advanced features
+ still need some work.
+
+. Usage of ASYS_INLINE is deprecated in ACE. Use ACE_INLINE instead.
+
+. All inline source files now end in ".inl". The previous ".i"
+ extension is generally used for preprocessed C sources.
+
+. Autoconf support has been improved and fixed on a number of
+ platforms, including the BSD variants (e.g. FreeBSD). It is still
+ not the preferred way to configure most platforms, but it is ready
+ for wider testing. Please report any problems found to
+ ace-bugs@cs.wustl.edu.
+
+. A number of fixes were made to quiet compile errors and warnings on
+ 64-bit Windows.
+
+. For builds on AIX using Visual Age C++, the make rtti option default
+ was changed to 1, enabling RTTI by default.
+
+. ACE_Service_Repository::remove() has a new, optional argument that
+ can receive the service record pointer for the removed service. If
+ the pointer is returned to the caller, it is not deleted. If the
+ pointer is not returned to the caller (the default) it is deleted
+ (this is the historic behavior).
+
+. The tutorials in ACE_wrappers/docs have been removed. They were not
+ being maintained and caused confusion in a number of cases. Now that
+ there are complete examples that match the printed books (C++NPv1,
+ C++NPv2, APG), the older tutorials are no longer useful. Please see
+
+ $ACE_ROOT/examples/C++NPv1/
+ $ACE_ROOT/examples/C++NPv2/
+ $ACE_ROOT/examples/APG/
+
+ for the source code of the examples in those books.
+
+. ACE_String_Base::fast_clear() is a new method which sets the string
+ length to 0. Doesn't release string-allocated memory, but if the
+ memory was externally supplied, it is no longer referenced from the
+ string object.
+
+. A true C++ "bool" is now used as the CDR stream boolean type, if
+ supported by the compiler.
+
+. Renamed AIX 5L configuration header from config-aix5.1.h to
+ config-aix-5.x.h.
+
+. All C++ equality, relational and logical operators now return bool
+ instead of int, as is the norm for modern C++.
+
+. Added new ACE_OS::realpath() implementation. Contributed by Olli
+ Savia
+
+
+USER VISIBLE CHANGES BETWEEN ACE-5.4 and ACE-5.4.1
+====================================================
+
+ACE
+---
+
+. Fixed "make install" support in ACE+autoconf configurations.
+
+. Fixed autoconf support on Solaris.
+
+. Corrected invalid `aux' directory (on MS Windows) found in ACE
+ distribution.
+
+. ACE/TAO build now without problems with MinGW and all ACE tests run
+ now without problems
+
+. Added some more support for the new CBuilderX Preview compiler, this
+ is not 100% ready yet because the compiler is still a preview and
+ has its own problems.
+
+. Added Visual SlickEdit 8.1 MPC template
+
+. Added workaround for compile problems in Borland Release builds
+
+. Cygwin 1.5.9 is now supported
+
+. Tests for IPV6 have been added
+
+. Implement lstat() so that it'll use stat() on platforms that don't
+ support lstat().
+
+. Problems related to ACE_Event_Handler usage in WFMO_Reactor was
+ fixed.
+
+. A wrapper for rmdir () has been added.
+
+. Threads spawned in thread-per-connection mode never inherited the
+ priority. This problem was fixed and this fix is consistent with the
+ C++ NPV* books.
+
+. Fixed memory leaks with ACE_String_Base::resize ()
+
+. Enable the usage of native recursive mutexes for the implementation
+ of ACE recursive mutexes on Linux.
+
+. The ACE Proactor framework can now be enabled for AIX 5.2. Since AIO
+ functionality is not run-time enabled by default on AIX 5.2, the ACE
+ Proactor code is not built by default on AIX. To enable it, the
+ config.h file must contain #define ACE_HAS_AIO_CALLS before
+ including the config-aix-5.1.h file.
+
+. The ACE_POSIX_CB_Proactor implementation is now built on all
+ platforms except LynxOS.
+
+
+USER VISIBLE CHANGES BETWEEN ACE-5.3.6 and ACE-5.4
+==================================================
+
+ACE:
+---
+. Added a new makefile commandline flag, static_link, that can be
+ used to force static linking when static_libs_only is turned on. It
+ uses the new STATIC_LINK_FLAG variable and is currently only
+ implemented for for GNU ld, i.e., it adds the "-static" option to
+ LDFLAGS. It's turned off by default since using it causes the
+ footprint to go up by almost 1 MB on Linux, since it links all the
+ system and compiler .a files, but can be turned on if users
+ want/need to use it, by enabling both static_libs_only and static_link.
+
+
+. Added macros ACE_USES_GPROF which enables users to use gprof in a
+ multithreaded environment with ACE libs.
+
+. Added a new functor template class, ACE_Malloc_Lock_Adapter_T,
+ that's used by ACE_Malloc_T as a factory for the ACE_LOCK template
+ parameter, and allows the use of locking strategy classes, like
+ ACE_Process_Semaphore and ACE_Thread_Semaphore that don't have a
+ satisfactory ctor taking a single required ACE_TCHAR* parameter, to
+ be adapted to work with ACE_Malloc_T.
+
+. The source code examples from "The ACE Programmer's Guide" book by
+ Huston, Syyid, and Johnston, are now located in
+ $ACE_ROOT/examples/APG.
+
+. Support for GNU autoconf is now in ACE. Please see ACE-INSTALL.html
+ for details.
+
+. Fixed problems that prevented ACE from being compiled on LynxOS
+ 4.0.0.
+
+. Fixed compilation error which prevented ACE from being compiled when
+ ACE_COMPILE_TIMEPROBES was set to 1.
+
+. Preliminary support for Tandem NSK has been added.
+
+. Lots of bug fixes with TLI and XPG5. Please see $ACE_ROOT/ChangeLog
+ for details.
+
+. Fixed ACE_OS::event_timedwait() and ACE_OS::event_wait() so that
+ they use a while loop around the ACE_OS::cond_[timed]wait() calls to
+ avoid problems with spurious wakeups, etc.
+
+. ACE's wrapper around getipnodebyname() and getipnodebyaddr () has
+ been made go through the IPv4-only case on ACE_WIN32. Since Windows
+ IPv6 implementation doesn't offer support (at thistime) for
+ getipnodebyname() the code has been changed to use the IPV4 part of
+ the code.
+
+. Install with Borland C++ of ACE library fixed
+
+ACEXML:
+-------
+
+. Fixed memory leak in ACEXML parser.
+
+. Fixed implementations of rewind() in all the CharStreams. They were
+ broken previously.
+
+. Fixed bugs in the parser associated with incorrect handling of PE
+ References for keywords.
diff --git a/dep/acelite/PROBLEM-REPORT-FORM b/dep/acelite/PROBLEM-REPORT-FORM
new file mode 100644
index 000000000..139fa83ad
--- /dev/null
+++ b/dep/acelite/PROBLEM-REPORT-FORM
@@ -0,0 +1,87 @@
+[Please use the PRF form below to submit bug reports, problem reports,
+ etc., to the ACE developers and interested users. Send it to
+ ace-bugs@list.isis.vanderbilt.edu, you must be subscribed to the list
+ in order to be able to post to it. If you are using OCI, PrismTech, or
+ Riverace's versions of ACE do not send bugs to this mailing list, but
+ instead contact those companies for support. Please also send your
+ PRF as plain ASCII text, _not_ uuencoded or as an attachment.
+
+ We prefer that all bug reports be submitted through our bug tracking
+ system. See $ACE_ROOT/docs/usage-bugzilla.html for more information
+ about how to do this. If you are unsure as to whether your problem
+ is a real bug or not then please submit your question to the mailing
+ list using the following form. Not using the problem report form
+ will make it harder or impossible to identify the problem, and in
+ many cases we will be unable to help at all. Also please try to
+ browse bugzilla and the ChangeLog files to find out if your problem
+ has been solved in a more recent version of ACE.
+
+ To subscribe to the list see
+ http://www.dre.vanderbilt.edu/~schmidt/ACE-mail.html
+
+ Replace/remove all the explanatory text in brackets before mailing.
+
+ Please send this form as ASCII text only. Do _not_ send it as an
+ attachment, or as tar'ed, compressed and/or uuencoded text. And
+ limit line lengths to less than 80 characters.
+
+ PLEASE make your Subject: line as descriptive as possible.
+ Subjects like "ACE bug" or "bug report" are not helpful!
+ Also, do _not_ include the word "help" in the Subject!]
+
+ When including your config.h and platform_macros.GNU files as requested
+ below, only include the contents if you use the recommended method of
+ including the platform-specific file in your file. If you use a link
+ to the platform-specific file, simply state which one - DO NOT
+ include an entire platform-specific configuration file in the form.
+
+8<----------8<----------8<----------8<----------8<----------8<----------8<----
+
+To: ace-bugs@list.isis.vanderbilt.edu
+Subject: [area]: [synopsis]
+
+ ACE VERSION: 6.3.0
+
+ HOST MACHINE and OPERATING SYSTEM:
+ If on Windows based OS's, which version of WINSOCK do you
+ use?:
+
+ TARGET MACHINE and OPERATING SYSTEM, if different from HOST:
+ COMPILER NAME AND VERSION (AND PATCHLEVEL):
+
+ THE $ACE_ROOT/ace/config.h FILE [if you use a link to a platform-
+ specific file, simply state which one]:
+
+ THE $ACE_ROOT/include/makeinclude/platform_macros.GNU FILE [if you
+ use a link to a platform-specific file, simply state which one
+ (unless this isn't used in this case, e.g., with Microsoft Visual
+ C++)]:
+
+ CONTENTS OF $ACE_ROOT/bin/MakeProjectCreator/config/default.features
+ (used by MPC when you generate your own makefiles):
+
+ AREA/CLASS/EXAMPLE AFFECTED:
+[What example failed? What module failed to compile?]
+
+ DOES THE PROBLEM AFFECT:
+ COMPILATION?
+ LINKING?
+ On Unix systems, did you run make realclean first?
+ EXECUTION?
+ OTHER (please specify)?
+[Please indicate whether ACE, your application, or both are affected.]
+
+ SYNOPSIS:
+[Brief description of the problem]
+
+ DESCRIPTION:
+[Detailed description of problem. Don't just say "
+doesn't work, here's a fix," explain what your program does
+to get to the state. ]
+
+ REPEAT BY:
+[What you did to get the error; include test program or session
+transcript if at all possible. ]
+
+ SAMPLE FIX/WORKAROUND:
+[If available ]
diff --git a/dep/acelite/README b/dep/acelite/README
new file mode 100644
index 000000000..fe9a7d2d9
--- /dev/null
+++ b/dep/acelite/README
@@ -0,0 +1,222 @@
+$Id: README 96983 2013-04-11 11:14:11Z schmidt $
+
+This document is also available at the following URL:
+
+http://www.dre.vanderbilt.edu/~schmidt/ACE.html
+
+All software and documentation is available via both anonymous ftp and
+the http.]
+
+THE ADAPTIVE COMMUNICATION ENVIRONMENT (ACE)
+
+An Object-Oriented Network Programming Toolkit
+
+----------------------------------------
+
+Overview of ACE
+
+The ADAPTIVE Communication Environment (ACE) is an object-oriented
+(OO) toolkit that implements fundamental design patterns for
+communication software. ACE provides a rich set of reusable C++
+wrappers and frameworks that perform common communication software
+tasks across a range of OS platforms, including Win32/Win64, most
+versions of UNIX (e.g., SunOS, HP-UX , AIX, Linux, NetBSD, and FreeBSD),
+real-time operating systems (e.g., VxWorks, Chorus, LynxOS, and QNX),
+OpenVMS, and MVS OpenEdition. A single source tree is used for all
+these platforms and porting ACE to other platforms is relatively easy.
+
+The communication software components provided by ACE include event
+demultiplexing and event handler dispatching, service initialization,
+interprocess communication, shared memory management, message routing,
+dynamic (re)configuration of distributed services, multi-threading,
+and concurrency control. There are both C++ and Java versions of ACE
+available.
+
+ACE is targeted for developers of high-performance and real-time
+communication services and applications on UNIX, POSIX, and Win32
+platforms. ACE simplifies the development of OO network applications
+and services that utilize interprocess communication, event
+demultiplexing, explicit dynamic linking, and concurrency. ACE
+automates system configuration and reconfiguration by dynamically
+linking services into applications at run-time and executing these
+services in one or more processes or threads.
+
+ACE is currently used in commercial projects and products by dozens of
+companies including Ericsson, Bellcore, Siemens, Motorola, Kodak,
+Boeing, Lucent, DEC, Lockheed Martin, and SAIC. Commercial support
+for ACE is available from several companies as listed at
+http://www.cs.wustl.edu/~schmidt/commercial-support.html
+
+----------------------------------------
+
+C++ Wrappers for OS Interfaces
+
+The lower-level portions of ACE provide a set of portable and
+type-secure C++ wrappers that encapsulate the following C language OS
+interfaces:
+
+ . IPC mechanisms
+ -- e.g., Internet- and UNIX-domain sockets, TLI, Named
+ Pipes (for UNIX and Win32) and STREAM pipes;
+
+ . Event demultiplexing
+ -- e.g., select(), poll(), and Win32
+ WaitForMultipleObjects and I/O completion ports;
+
+ . Multi-threading and synchronization
+ -- e.g., Solaris threads, POSIX Pthreads, and Win32
+ threads;
+
+ . Explicit dynamic linking
+ -- e.g., dlopen/dlsym on UNIX and LoadLibrary/GetProc
+ on Win32;
+
+ . Memory-mapped files and shared memory management
+ -- e.g., BSD mmap(), SYSV shared memory, and Win32
+ shared memory;
+
+ . System V IPC
+ -- e.g., shared memory, semaphores, message queues.
+
+The OS Adaptation Layer shields the upper levels of ACE from platform
+dependencies associated with the underlying OS interfaces.
+
+----------------------------------------
+
+Frameworks and Class Categories
+
+ACE also contains a higher-level network programming framework that
+integrates and enhances the lower-level C++ wrappers. This framework
+supports the dynamic configuration of concurrent distributed services
+into applications. The framework portion of ACE contains the
+following class categories:
+
+ . The Reactor
+ -- Supports both Reactive and Proactive I/O;
+
+ . The Service Configurator
+ -- Support dynamic (re)configuration of objects;
+
+ . The ADAPTIVE Service Executive
+ -- A user-level implementation of System V STREAMS,
+ that supports modular integration of
+ hierarchically-related communicaion services;
+
+ . Concurrency
+ -- Various types of higher-level concurrency
+ control and synchronization patterns (such as
+ Polymorphic Futures and Active Objects);
+
+ . Shared Malloc
+ -- Components for managing dynamically allocation
+ of shared and local memory;
+
+----------------------------------------
+
+Distributed Services and Components
+
+Finally, ACE provides a standard library of distributed services that
+are packaged as components. These service components play two roles
+in ACE:
+
+ 1. They provide reusable components for common distributed
+ system tasks such as logging, naming, locking, and time
+ synchronization.
+
+ 2. They illustrate how to utilize ACE features such as the
+ Reactor, Service Configurator, Service Initialization,
+ Concurrency, and IPC components.
+
+----------------------------------------
+
+Middleware Applications
+
+ACE has been used in research and development projects at many
+universities and companies. For instance, it has been used to build
+avionics systems at Boeing, telecommunication systems at Bellcore,
+Ericsson, Motorola, and Lucent; medical imaging systems at Siemens and
+Kodak; and many academic research projects. Two example middleware
+applications provided with the ACE release include:
+
+ 1. The ACE ORB (TAO) -- TAO is a real-time implementation of
+ CORBA built using the framework components and patterns
+ provided by ACE.
+
+ 2. JAWS -- JAWS is a high-performance, adaptive Web server
+ built using the components in ACE.
+
+----------------------------------------
+
+OBTAINING ACE
+
+ACE may be obtained electronically from
+http://download.dre.vanderbilt.edu. This release contains the source
+code, test drivers, and example applications (including JAWS) for C++
+wrapper libraries and the higher-level ACE network programming
+framework developed as part of the ADAPTIVE project at the University
+of California, Irvine, Washington University, St. Louis, and
+Vanderbilt University.
+
+You can get The ACE ORB (TAO) in a companion release at the same URL.
+
+----------------------------------------
+
+ACE DOCUMENTATION AND TUTORIALS
+
+Many of the C++ wrappers and higher-level components have been
+described in issues of the C++ Report, as well as in proceedings of
+many journals, conferences, and workshops.
+
+A collection of white papers and tutorial handouts are included at
+
+http://www.dre.vanderbilt.edu/~schmidt/ACE-papers.html
+
+This page contains PDF versions of various papers that describe
+different aspects of ACE.
+
+This material is also available available via the WWW at URL:
+
+http://www.dre.vanderbilt.edu/~schmidt/ACE.html
+
+----------------------------------------
+
+ACE MAILING LIST AND NEWSGROUP
+
+A mailing list, ace-users@list.isis.vanderbilt.edu, is available for
+discussing bug fixes, enhancements, and porting issues regarding ACE.
+Please send mail to me at the
+ace-users-request@list.isis.vanderbilt.edu if you'd like to join the
+mailing list. There is also a USENET newsgroup called
+comp.soft-sys.ace. Please see
+http://www.dre.vanderbilt.edu/~schmidt/ACE-mail.html for details on
+how to subscribe to the mailing list.
+
+----------------------------------------
+
+BUILDING AND INSTALLING ACE
+
+Please refer to the
+http://www.dre.vanderbilt.edu/~schmidt/ACE-install.html file for
+information on how to build and test the ACE wrappers. The
+BIBLIOGRAPHY file contains information on where to obtain articles
+that describe the ACE wrappers and the ADAPTIVE system in more detail.
+
+The current release has been tested extensively, but if you find any
+bugs, please report them to the ACE mailing list
+ace-users@list.isis.vanderbilt.edu using the
+$ACE_ROOT/PROBLEM-REPORT-FORM. Please use the same form to submit
+questions, comments, etc. To ensure that you see responses, please do
+one of the following:
+
+ 1) Subscribe to the ace-users mail list, by sending email with
+ contents "subscribe ace-users" to
+ ace-users-request@list.isis.vanderbilt.edu.
+
+ 2) Or, monitor the comp.soft-sys.ace newsgroup for responses.
+
+----------------------------------------
+
+ACKNOWLEDGEMENTS
+
+Please see the file `$ACE_ROOT/THANKS' for a list of the thousands of
+people who've contributed to ACE and TAO over the years.
diff --git a/dep/acelite/THANKS b/dep/acelite/THANKS
new file mode 100644
index 000000000..360df16d7
--- /dev/null
+++ b/dep/acelite/THANKS
@@ -0,0 +1,2422 @@
+ACKNOWLEDGEMENTS
+
+ACE, TAO, CIAO, and DAnCE have been deeply influenced and improved by the
+following members of my research group at Washington University in St. Louis,
+the University of California at Irvine, and Vanderbilt University in Nashville.
+
+Everett Anderson
+Alexander Babu Arulanthu
+Shawn Atkins
+Jaiganesh Balasubramanian
+Krishnakumar Balasubramanian
+Matt Braun
+Darrell Brunsch
+Dante J. Cannarozzi
+Sharath R. Cholleti
+Chris Cleeland
+Angelo Corsaro
+Gan Deng
+Mayur Deshpande
+Eric Ding
+George Edwards
+Sergio Flores-Gaitan
+Chris Gill
+Andrew G. Gilpin
+Aniruddha Gokhale
+Priyanka Gontla
+Pradeep Gore
+Matthew P. Hampton
+Tim Harrison
+John Heitmann
+James Hill
+Shawn Hannan
+Don Hinton
+Joe Hoffert
+James Hu
+Huang-Ming Huang
+Frank A. Hunleth
+Prashant Jain
+Shanshan Jiang
+Vishal Kachroo
+Michael Kircher
+Boris Kolpackov
+Arvind S. Krishna
+Yamuna Krishnamurthy
+Fred Kuhns
+David Levine
+Tao Lu
+Mike Moran
+Sumedh Mungee
+Balachandran Natarajan
+Will Otte
+Kirthika Parameswaran
+Krishnakumar Pathayapura
+Stoyan Paunov
+Carlos O'Ryan
+Ossama Othman
+Jeff Parsons
+Irfan Pyarali
+Nilabja Roy
+Lucas Seibert
+Diego Sevilla Ruiz
+Nishanth Shankaran
+Marina Spivak
+Venkita Subramonian
+Nagarajan Surendran
+Cassia Tatibana
+Sumant Tambe
+Gabriele Trombetti
+Emre Turkay
+Nanbor Wang
+Seth Widoff
+Jules White
+Friedhelm Wolf
+Torben Worm
+Ming Xiong
+
+I would also like to thank all the following people who have also
+contributed to ACE, TAO, CIAO, and DAnCE over the years:
+
+Paul Stephenson
+Olaf Kruger
+Ed Brown
+Lee Baker
+Alex Ranous
+Mark Patton
+Steffen Winther Sorensen
+Troy Warner
+Stacy Mahlon
+Charles Eads
+Mark Frutig
+Todd Hoff
+George
+Brad Needham
+Leslee Xu
+Detlef Becker
+Bruce Worden
+Chris Tarr
+Bill Sears
+Greg Lavender
+Steve Warwick
+Mats Sundvall
+Andreas Ueltschi
+Nigel Hooke
+Medhi Tabatabai
+Stuart Powell
+Bin Mu
+Andrew McGowan
+Ken Konecki
+John P. Hearn
+Giang Hoang Nguyen
+Carlos Garcia Braschi
+Jam Hamidi
+Eric Vaughan
+Karlheinz Dorn
+Gerhard Lenzer
+Steve Ritter
+Chandra Venkatapathy
+Matt Stevens
+Bob Vistica
+David Trumble
+George Reynolds
+Hans Rohnert
+Alex V. Maclinovsky
+Todd Blanchard
+Rob Clairmont
+Christian Millour
+Neil B. Cohen
+Dieter Quehl
+Reginald S. Perry
+James Morris
+Mark Seaborn
+Phil Brooks
+E. Jason Scheck
+Daniel Proulx
+Bill Tang
+John Huchinson
+Jack Erickson