aboutsummaryrefslogtreecommitdiff
path: root/src/SFileOpenArchive.cpp
diff options
context:
space:
mode:
authorLadislav Zezula <ladislav.zezula@avg.com>2013-01-11 14:55:08 +0100
committerLadislav Zezula <ladislav.zezula@avg.com>2013-01-11 14:55:08 +0100
commit3a926f0228c68d7d91cf3946624d7859976440ec (patch)
treec4e7d36dc8157576929988cdfcf5bfd8262cd09c /src/SFileOpenArchive.cpp
parentdf4b0c085478389c9a21a09521d46735a0109c8a (diff)
Initial creation
Diffstat (limited to 'src/SFileOpenArchive.cpp')
-rw-r--r--src/SFileOpenArchive.cpp480
1 files changed, 480 insertions, 0 deletions
diff --git a/src/SFileOpenArchive.cpp b/src/SFileOpenArchive.cpp
new file mode 100644
index 0000000..c385019
--- /dev/null
+++ b/src/SFileOpenArchive.cpp
@@ -0,0 +1,480 @@
+/*****************************************************************************/
+/* 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"
+
+/*****************************************************************************/
+/* Local functions */
+/*****************************************************************************/
+
+static bool IsAviFile(void * pvFileBegin)
+{
+ LPDWORD AviHeader = (DWORD *)pvFileBegin;
+ DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(AviHeader[0]);
+ DWORD DwordValue2 = BSWAP_INT32_UNSIGNED(AviHeader[2]);
+ DWORD DwordValue3 = BSWAP_INT32_UNSIGNED(AviHeader[3]);
+
+ // Test for 'RIFF', 'AVI ' or 'LIST'
+ return (DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C);
+}
+
+static TFileBitmap * CreateFileBitmap(TMPQArchive * ha, TMPQBitmap * pMpqBitmap, bool bFileIsComplete)
+{
+ TFileBitmap * pBitmap;
+ size_t nLength;
+
+ // Calculate the length of the bitmap in blocks and in bytes
+ nLength = (size_t)(((ha->pHeader->ArchiveSize64 - 1) / pMpqBitmap->dwBlockSize) + 1);
+ nLength = (size_t)(((nLength - 1) / 8) + 1);
+
+ // Allocate the file bitmap
+ pBitmap = (TFileBitmap *)STORM_ALLOC(BYTE, sizeof(TFileBitmap) + nLength);
+ if(pBitmap != NULL)
+ {
+ // Fill the structure
+ pBitmap->StartOffset = ha->MpqPos;
+ pBitmap->EndOffset = ha->MpqPos + ha->pHeader->ArchiveSize64;
+ pBitmap->IsComplete = bFileIsComplete ? 1 : 0;
+ pBitmap->BitmapSize = (DWORD)nLength;
+ pBitmap->BlockSize = pMpqBitmap->dwBlockSize;
+ pBitmap->Reserved = 0;
+
+ // Copy the file bitmap
+ memcpy((pBitmap + 1), (pMpqBitmap + 1), nLength);
+ }
+
+ return pBitmap;
+}
+
+// This function gets the right positions of the hash table and the block table.
+static int VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize)
+{
+ 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 = ha->MpqPos + 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 = ha->MpqPos + 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)
+{
+ TFileStream * pStream = NULL; // Open file stream
+ TMPQArchive * ha = NULL; // Archive handle
+ TFileEntry * pFileEntry;
+ ULONGLONG FileSize = 0; // Size of the file
+ int nError = ERROR_SUCCESS;
+
+ // Verify the parameters
+ if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL)
+ nError = ERROR_INVALID_PARAMETER;
+
+ // One time initialization of MPQ cryptography
+ InitializeMpqCryptography();
+ dwPriority = dwPriority;
+
+ // Open the MPQ archive file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Initialize the stream
+ pStream = FileStream_OpenFile(szMpqName, (dwFlags & STREAM_OPTIONS_MASK));
+ if(pStream == NULL)
+ nError = GetLastError();
+ }
+
+ // Allocate the MPQhandle
+ if(nError == ERROR_SUCCESS)
+ {
+ FileStream_GetSize(pStream, &FileSize);
+ if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Initialize handle structure and allocate structure for MPQ header
+ if(nError == ERROR_SUCCESS)
+ {
+ memset(ha, 0, sizeof(TMPQArchive));
+ ha->pStream = pStream;
+ pStream = NULL;
+
+ // Remember if the archive is open for write
+ if(FileStream_IsReadOnly(ha->pStream))
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+
+ // Also remember if we shall check sector CRCs when reading file
+ if(dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC)
+ ha->dwFlags |= MPQ_FLAG_CHECK_SECTOR_CRC;
+ }
+
+ // Find the offset of MPQ header within the file
+ if(nError == ERROR_SUCCESS)
+ {
+ ULONGLONG SearchPos = 0;
+ DWORD dwHeaderID;
+
+ while(SearchPos < FileSize)
+ {
+ DWORD dwBytesAvailable = MPQ_HEADER_SIZE_V4;
+
+ // Cut the bytes available, if needed
+ if((FileSize - SearchPos) < MPQ_HEADER_SIZE_V4)
+ dwBytesAvailable = (DWORD)(FileSize - SearchPos);
+
+ // Read the eventual MPQ header
+ if(!FileStream_Read(ha->pStream, &SearchPos, ha->HeaderData, dwBytesAvailable))
+ {
+ nError = GetLastError();
+ break;
+ }
+
+ // There are AVI files from Warcraft III with 'MPQ' extension.
+ if(SearchPos == 0 && IsAviFile(ha->HeaderData))
+ {
+ nError = ERROR_AVI_FILE;
+ break;
+ }
+
+ // If there is the MPQ user data signature, process it
+ dwHeaderID = BSWAP_INT32_UNSIGNED(*(LPDWORD)ha->HeaderData);
+ if(dwHeaderID == ID_MPQ_USERDATA && ha->pUserData == NULL)
+ {
+ // Ignore the MPQ user data completely if the caller wants to open the MPQ as V1.0
+ if((dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
+ {
+ // Fill the user data header
+ ha->pUserData = &ha->UserData;
+ memcpy(ha->pUserData, ha->HeaderData, sizeof(TMPQUserData));
+ BSWAP_TMPQUSERDATA(ha->pUserData);
+
+ // Remember the position of the user data and continue search
+ ha->UserDataPos = SearchPos;
+ SearchPos += ha->pUserData->dwHeaderOffs;
+ continue;
+ }
+ }
+
+ // There must be MPQ header signature
+ if(dwHeaderID == ID_MPQ)
+ {
+ // Save the position where the MPQ header has been found
+ if(ha->pUserData == NULL)
+ ha->UserDataPos = SearchPos;
+ ha->pHeader = (TMPQHeader *)ha->HeaderData;
+ ha->MpqPos = SearchPos;
+
+ // Now convert the header to version 4
+ BSWAP_TMPQHEADER(ha->pHeader);
+ nError = ConvertMpqHeaderToFormat4(ha, FileSize, dwFlags);
+ break;
+ }
+
+ // Move to the next possible offset
+ SearchPos += 0x200;
+ }
+
+ // If we haven't found MPQ header in the file, it's an error
+ if(ha->pHeader == NULL)
+ 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 probably 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;
+ }
+
+ // 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;
+
+ // 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);
+ }
+
+ // Check if the MPQ has data bitmap. If yes, we can verify if the MPQ is complete
+ if(nError == ERROR_SUCCESS && ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_4)
+ {
+ TFileBitmap * pBitmap;
+ bool bFileIsComplete = true;
+
+ LoadMpqDataBitmap(ha, FileSize, &bFileIsComplete);
+ if(ha->pBitmap != NULL && bFileIsComplete == false)
+ {
+ // Convert the MPQ bitmap to the file bitmap
+ pBitmap = CreateFileBitmap(ha, ha->pBitmap, bFileIsComplete);
+
+ // Set the data bitmap into the file stream for additional checks
+ FileStream_SetBitmap(ha->pStream, pBitmap);
+ ha->dwFlags |= MPQ_FLAG_READ_ONLY;
+ }
+ }
+
+ // Read the hash table. Ignore the result, as hash table is no longer required
+ // Read HET table. Ignore the result, as HET table is no longer required
+ if(nError == ERROR_SUCCESS)
+ {
+ 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, FileSize);
+ }
+
+ // Verify the file table, if no kind of protection was detected
+ if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0)
+ {
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->pHeader->dwBlockTableSize;
+// ULONGLONG ArchiveSize = 0;
+ ULONGLONG RawFilePos;
+
+ // Parse all file entries
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // If that file entry is valid, check the file position
+ if(pFileEntry->dwFlags & MPQ_FILE_EXISTS)
+ {
+ // Get the 64-bit file position,
+ // relative to the begin of the file
+ RawFilePos = ha->MpqPos + pFileEntry->ByteOffset;
+
+ // Begin of the file must be within range
+ if(RawFilePos > FileSize)
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // End of the file must be within range
+ RawFilePos += pFileEntry->dwCmpSize;
+ if(RawFilePos > FileSize)
+ {
+ nError = ERROR_FILE_CORRUPT;
+ break;
+ }
+
+ // Also, we remember end of the file
+// if(RawFilePos > ArchiveSize)
+// ArchiveSize = RawFilePos;
+ }
+ }
+ }
+
+ // Load the internal listfile and include it to the file table
+ if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0)
+ {
+ // Save the flags for (listfile)
+ pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ ha->dwFileFlags1 = pFileEntry->dwFlags;
+
+ // Ignore result of the operation. (listfile) is optional.
+ SFileAddListFile((HANDLE)ha, NULL);
+ }
+
+ // Load the "(attributes)" file and merge it to the file table
+ if(nError == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0)
+ {
+ // Save the flags for (attributes)
+ pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
+ if(pFileEntry != NULL)
+ ha->dwFileFlags2 = pFileEntry->dwFlags;
+
+ // Ignore result of the operation. (attributes) is optional.
+ SAttrLoadAttributes(ha);
+ }
+
+ // Cleanup and exit
+ if(nError != ERROR_SUCCESS)
+ {
+ FileStream_Close(pStream);
+ FreeMPQArchive(ha);
+ SetLastError(nError);
+ ha = NULL;
+ }
+
+ *phMpq = ha;
+ return (nError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// SFileGetArchiveBitmap
+
+bool WINAPI SFileGetArchiveBitmap(HANDLE hMpq, TFileBitmap * pBitmap, DWORD Length, LPDWORD LengthNeeded)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+
+ return FileStream_GetBitmap(ha->pStream, pBitmap, Length, LengthNeeded);
+}
+
+//-----------------------------------------------------------------------------
+// bool SFileFlushArchive(HANDLE hMpq)
+//
+// Saves all dirty data into MPQ archive.
+// 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 = (TMPQArchive *)hMpq;
+ int nResultError = ERROR_SUCCESS;
+ int nError;
+
+ // Do nothing if 'hMpq' is bad parameter
+ if(!IsValidMpqHandle(ha))
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return false;
+ }
+
+ // If the (listfile) has been invalidated, save it
+ if(ha->dwFlags & MPQ_FLAG_INV_LISTFILE)
+ {
+ nError = SListFileSaveToMpq(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+ }
+
+ // If the (attributes) has been invalidated, save it
+ if(ha->dwFlags & MPQ_FLAG_INV_ATTRIBUTES)
+ {
+ 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)
+ {
+ nError = SaveMPQTables(ha);
+ if(nError != ERROR_SUCCESS)
+ nResultError = nError;
+ }
+
+ // Return the error
+ if(nResultError != ERROR_SUCCESS)
+ SetLastError(nResultError);
+ return (nResultError == ERROR_SUCCESS);
+}
+
+//-----------------------------------------------------------------------------
+// bool SFileCloseArchive(HANDLE hMpq);
+//
+
+bool WINAPI SFileCloseArchive(HANDLE hMpq)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ bool bResult;
+
+ // Flush all unsaved data to the storage
+ bResult = SFileFlushArchive(hMpq);
+
+ // Free all memory used by MPQ archive
+ FreeMPQArchive(ha);
+ return bResult;
+}
+