1 /*****************************************************************************/
2 /* SFileOpenArchive.cpp                       Copyright Ladislav Zezula 1999 */
3 /*                                                                           */
4 /* Author : Ladislav Zezula                                                  */
5 /* E-mail : ladik@zezula.net                                                 */
6 /* WWW    : www.zezula.net                                                   */
7 /*---------------------------------------------------------------------------*/
8 /* Implementation of archive functions                                       */
9 /*---------------------------------------------------------------------------*/
10 /*   Date    Ver   Who  Comment                                              */
11 /* --------  ----  ---  -------                                              */
12 /* xx.xx.xx  1.00  Lad  Created                                              */
13 /* 19.11.03  1.01  Dan  Big endian handling                                  */
14 /*****************************************************************************/
15 
16 #define __STORMLIB_SELF__
17 #include "StormLib.h"
18 #include "StormCommon.h"
19 
20 #define HEADER_SEARCH_BUFFER_SIZE   0x1000
21 
22 //-----------------------------------------------------------------------------
23 // Local functions
24 
CheckMapType(LPCTSTR szFileName,LPBYTE pbHeaderBuffer,size_t cbHeaderBuffer)25 static MTYPE CheckMapType(LPCTSTR szFileName, LPBYTE pbHeaderBuffer, size_t cbHeaderBuffer)
26 {
27     LPDWORD HeaderInt32 = (LPDWORD)pbHeaderBuffer;
28     LPCTSTR szExtension;
29 
30     // Don't do any checks if there is not at least 16 bytes
31     if(cbHeaderBuffer > 0x10)
32     {
33         DWORD DwordValue0 = BSWAP_INT32_UNSIGNED(HeaderInt32[0]);
34         DWORD DwordValue1 = BSWAP_INT32_UNSIGNED(HeaderInt32[1]);
35         DWORD DwordValue2 = BSWAP_INT32_UNSIGNED(HeaderInt32[2]);
36         DWORD DwordValue3 = BSWAP_INT32_UNSIGNED(HeaderInt32[3]);
37 
38         // Test for AVI files (Warcraft III cinematics) - 'RIFF', 'AVI ' or 'LIST'
39         if(DwordValue0 == 0x46464952 && DwordValue2 == 0x20495641 && DwordValue3 == 0x5453494C)
40             return MapTypeAviFile;
41 
42         // Check for Starcraft II maps
43         if((szExtension = _tcsrchr(szFileName, _T('.'))) != NULL)
44         {
45             // The "NP_Protect" protector places fake Warcraft III header
46             // into the Starcraft II maps, whilst SC2 maps have no other header but MPQ v4
47             if(!_tcsicmp(szExtension, _T(".s2ma")) || !_tcsicmp(szExtension, _T(".SC2Map")) || !_tcsicmp(szExtension, _T(".SC2Mod")))
48             {
49                 return MapTypeStarcraft2;
50             }
51         }
52 
53         // Check for Warcraft III maps
54         if(DwordValue0 == 0x57334D48 && DwordValue1 == 0x00000000)
55             return MapTypeWarcraft3;
56     }
57 
58     // MIX files are DLL files that contain MPQ in overlay.
59     // Only Warcraft III is able to load them, so we consider them Warcraft III maps
60     if(cbHeaderBuffer > 0x200 && pbHeaderBuffer[0] == 'M' && pbHeaderBuffer[1] == 'Z')
61     {
62         // Check the value of IMAGE_DOS_HEADER::e_lfanew at offset 0x3C
63         if(0 < HeaderInt32[0x0F] && HeaderInt32[0x0F] < 0x10000)
64             return MapTypeWarcraft3;
65     }
66 
67     // No special map type recognized
68     return MapTypeNotRecognized;
69 }
70 
IsValidMpqUserData(ULONGLONG ByteOffset,ULONGLONG FileSize,void * pvUserData)71 static TMPQUserData * IsValidMpqUserData(ULONGLONG ByteOffset, ULONGLONG FileSize, void * pvUserData)
72 {
73     TMPQUserData * pUserData;
74 
75     // BSWAP the source data and copy them to our buffer
76     BSWAP_ARRAY32_UNSIGNED(pvUserData, sizeof(TMPQUserData));
77     pUserData = (TMPQUserData *)pvUserData;
78 
79     // Check the sizes
80     if(pUserData->cbUserDataHeader <= pUserData->cbUserDataSize && pUserData->cbUserDataSize <= pUserData->dwHeaderOffs)
81     {
82         // Move to the position given by the userdata
83         ByteOffset += pUserData->dwHeaderOffs;
84 
85         // The MPQ header should be within range of the file size
86         if((ByteOffset + MPQ_HEADER_SIZE_V1) < FileSize)
87         {
88             // Note: We should verify if there is the MPQ header.
89             // However, the header could be at any position below that
90             // that is multiplier of 0x200
91             return (TMPQUserData *)pvUserData;
92         }
93     }
94 
95     return NULL;
96 }
97 
98 // This function gets the right positions of the hash table and the block table.
VerifyMpqTablePositions(TMPQArchive * ha,ULONGLONG FileSize)99 static DWORD VerifyMpqTablePositions(TMPQArchive * ha, ULONGLONG FileSize)
100 {
101     TMPQHeader * pHeader = ha->pHeader;
102     ULONGLONG ByteOffset;
103     //bool bMalformed = (ha->dwFlags & MPQ_FLAG_MALFORMED) ? true : false;
104 
105     // Check the begin of HET table
106     if(pHeader->HetTablePos64)
107     {
108         ByteOffset = ha->MpqPos + pHeader->HetTablePos64;
109         if(ByteOffset > FileSize)
110             return ERROR_BAD_FORMAT;
111     }
112 
113     // Check the begin of BET table
114     if(pHeader->BetTablePos64)
115     {
116         ByteOffset = ha->MpqPos + pHeader->BetTablePos64;
117         if(ByteOffset > FileSize)
118             return ERROR_BAD_FORMAT;
119     }
120 
121     // Check the begin of hash table
122     if(pHeader->wHashTablePosHi || pHeader->dwHashTablePos)
123     {
124         ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wHashTablePosHi, pHeader->dwHashTablePos));
125         if(ByteOffset > FileSize)
126             return ERROR_BAD_FORMAT;
127     }
128 
129     // Check the begin of block table
130     if(pHeader->wBlockTablePosHi || pHeader->dwBlockTablePos)
131     {
132         ByteOffset = FileOffsetFromMpqOffset(ha, MAKE_OFFSET64(pHeader->wBlockTablePosHi, pHeader->dwBlockTablePos));
133         if(ByteOffset > FileSize)
134             return ERROR_BAD_FORMAT;
135     }
136 
137     // Check the begin of hi-block table
138     //if(pHeader->HiBlockTablePos64 != 0)
139     //{
140     //    ByteOffset = ha->MpqPos + pHeader->HiBlockTablePos64;
141     //    if(ByteOffset > FileSize)
142     //        return ERROR_BAD_FORMAT;
143     //}
144 
145     // All OK.
146     return ERROR_SUCCESS;
147 }
148 
149 //-----------------------------------------------------------------------------
150 // Support for alternate markers. Call before opening an archive
151 
152 #define SFILE_MARKERS_MIN_SIZE   (sizeof(DWORD) + sizeof(DWORD) + sizeof(const char *) + sizeof(const char *))
153 
SFileSetArchiveMarkers(PSFILE_MARKERS pMarkers)154 bool WINAPI SFileSetArchiveMarkers(PSFILE_MARKERS pMarkers)
155 {
156     // Check structure minimum size
157     if(pMarkers == NULL || pMarkers->dwSize < SFILE_MARKERS_MIN_SIZE)
158     {
159         SetLastError(ERROR_INVALID_PARAMETER);
160         return false;
161     }
162 
163     // Make sure that the MPQ cryptography is initialized at this time
164     InitializeMpqCryptography();
165 
166     // Remember the marker for MPQ header
167     if(pMarkers->dwSignature != 0)
168         g_dwMpqSignature = pMarkers->dwSignature;
169 
170     // Remember the encryption key for hash table
171     if(pMarkers->szHashTableKey != NULL)
172         g_dwHashTableKey = HashString(pMarkers->szHashTableKey, MPQ_HASH_FILE_KEY);
173 
174     // Remember the encryption key for block table
175     if(pMarkers->szBlockTableKey != NULL)
176         g_dwBlockTableKey = HashString(pMarkers->szBlockTableKey, MPQ_HASH_FILE_KEY);
177 
178     // Succeeded
179     return true;
180 }
181 
182 //-----------------------------------------------------------------------------
183 // SFileGetLocale and SFileSetLocale
184 // Set the locale for all newly opened files
185 
SFileGetLocale()186 LCID WINAPI SFileGetLocale()
187 {
188     return g_lcFileLocale;
189 }
190 
SFileSetLocale(LCID lcNewLocale)191 LCID WINAPI SFileSetLocale(LCID lcNewLocale)
192 {
193     g_lcFileLocale = lcNewLocale;
194     return g_lcFileLocale;
195 }
196 
197 //-----------------------------------------------------------------------------
198 // SFileOpenArchive
199 //
200 //   szFileName - MPQ archive file name to open
201 //   dwPriority - When SFileOpenFileEx called, this contains the search priority for searched archives
202 //   dwFlags    - See MPQ_OPEN_XXX in StormLib.h
203 //   phMpq      - Pointer to store open archive handle
204 
SFileOpenArchive(const TCHAR * szMpqName,DWORD dwPriority,DWORD dwFlags,HANDLE * phMpq)205 bool WINAPI SFileOpenArchive(
206     const TCHAR * szMpqName,
207     DWORD dwPriority,
208     DWORD dwFlags,
209     HANDLE * phMpq)
210 {
211     TMPQUserData * pUserData;
212     TFileStream * pStream = NULL;       // Open file stream
213     TMPQArchive * ha = NULL;            // Archive handle
214     TFileEntry * pFileEntry;
215     ULONGLONG FileSize = 0;             // Size of the file
216     LPBYTE pbHeaderBuffer = NULL;       // Buffer for searching MPQ header
217     DWORD dwStreamFlags = (dwFlags & STREAM_FLAGS_MASK);
218     MTYPE MapType = MapTypeNotChecked;
219     DWORD dwErrCode = ERROR_SUCCESS;
220 
221     // Verify the parameters
222     if(szMpqName == NULL || *szMpqName == 0 || phMpq == NULL)
223     {
224         SetLastError(ERROR_INVALID_PARAMETER);
225         return false;
226     }
227 
228     // One time initialization of MPQ cryptography
229     InitializeMpqCryptography();
230     dwPriority = dwPriority;
231 
232     // If not forcing MPQ v 1.0, also use file bitmap
233     dwStreamFlags |= (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) ? 0 : STREAM_FLAG_USE_BITMAP;
234 
235     // Open the MPQ archive file
236     pStream = FileStream_OpenFile(szMpqName, dwStreamFlags);
237     if(pStream == NULL)
238         return false;
239 
240     // Check the file size. There must be at least 0x20 bytes
241     if(dwErrCode == ERROR_SUCCESS)
242     {
243         FileStream_GetSize(pStream, &FileSize);
244         if(FileSize < MPQ_HEADER_SIZE_V1)
245             dwErrCode = ERROR_BAD_FORMAT;
246     }
247 
248     // Allocate the MPQhandle
249     if(dwErrCode == ERROR_SUCCESS)
250     {
251         if((ha = STORM_ALLOC(TMPQArchive, 1)) == NULL)
252             dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
253     }
254 
255     // Allocate buffer for searching MPQ header
256     if(dwErrCode == ERROR_SUCCESS)
257     {
258         pbHeaderBuffer = STORM_ALLOC(BYTE, HEADER_SEARCH_BUFFER_SIZE);
259         if(pbHeaderBuffer == NULL)
260             dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
261     }
262 
263     // Find the position of MPQ header
264     if(dwErrCode == ERROR_SUCCESS)
265     {
266         ULONGLONG ByteOffset = 0;
267         ULONGLONG EndOfSearch = FileSize;
268         DWORD dwStrmFlags = 0;
269         DWORD dwHeaderSize;
270         DWORD dwHeaderID;
271         bool bSearchComplete = false;
272 
273         memset(ha, 0, sizeof(TMPQArchive));
274         ha->pfnHashString = HashStringSlash;
275         ha->pStream = pStream;
276         pStream = NULL;
277 
278         // Set the archive read only if the stream is read-only
279         FileStream_GetFlags(ha->pStream, &dwStrmFlags);
280         ha->dwFlags |= (dwStrmFlags & STREAM_FLAG_READ_ONLY) ? MPQ_FLAG_READ_ONLY : 0;
281 
282         // Also remember if we shall check sector CRCs when reading file
283         ha->dwFlags |= (dwFlags & MPQ_OPEN_CHECK_SECTOR_CRC) ? MPQ_FLAG_CHECK_SECTOR_CRC : 0;
284 
285         // Also remember if this MPQ is a patch
286         ha->dwFlags |= (dwFlags & MPQ_OPEN_PATCH) ? MPQ_FLAG_PATCH : 0;
287 
288         // Limit the header searching to about 130 MB of data
289         if(EndOfSearch > 0x08000000)
290             EndOfSearch = 0x08000000;
291 
292         // Find the offset of MPQ header within the file
293         while(bSearchComplete == false && ByteOffset < EndOfSearch)
294         {
295             // Always read at least 0x1000 bytes for performance.
296             // This is what Storm.dll (2002) does.
297             DWORD dwBytesAvailable = HEADER_SEARCH_BUFFER_SIZE;
298 
299             // Cut the bytes available, if needed
300             if((FileSize - ByteOffset) < HEADER_SEARCH_BUFFER_SIZE)
301                 dwBytesAvailable = (DWORD)(FileSize - ByteOffset);
302 
303             // Read the eventual MPQ header
304             if(!FileStream_Read(ha->pStream, &ByteOffset, pbHeaderBuffer, dwBytesAvailable))
305             {
306                 dwErrCode = GetLastError();
307                 break;
308             }
309 
310             // Check whether the file is AVI file or a Warcraft III/Starcraft II map
311             if(MapType == MapTypeNotChecked)
312             {
313                 // Do nothing if the file is an AVI file
314                 if((MapType = CheckMapType(szMpqName, pbHeaderBuffer, dwBytesAvailable)) == MapTypeAviFile)
315                 {
316                     dwErrCode = ERROR_AVI_FILE;
317                     break;
318                 }
319             }
320 
321             // Search the header buffer
322             for(DWORD dwInBufferOffset = 0; dwInBufferOffset < dwBytesAvailable; dwInBufferOffset += 0x200)
323             {
324                 // Copy the data from the potential header buffer to the MPQ header
325                 memcpy(ha->HeaderData, pbHeaderBuffer + dwInBufferOffset, sizeof(ha->HeaderData));
326 
327                 // If there is the MPQ user data, process it
328                 // Note that Warcraft III does not check for user data, which is abused by many map protectors
329                 dwHeaderID = BSWAP_INT32_UNSIGNED(ha->HeaderData[0]);
330                 if(MapType != MapTypeWarcraft3 && (dwFlags & MPQ_OPEN_FORCE_MPQ_V1) == 0)
331                 {
332                     if(ha->pUserData == NULL && dwHeaderID == ID_MPQ_USERDATA)
333                     {
334                         // Verify if this looks like a valid user data
335                         pUserData = IsValidMpqUserData(ByteOffset, FileSize, ha->HeaderData);
336                         if(pUserData != NULL)
337                         {
338                             // Fill the user data header
339                             ha->UserDataPos = ByteOffset;
340                             ha->pUserData = &ha->UserData;
341                             memcpy(ha->pUserData, pUserData, sizeof(TMPQUserData));
342 
343                             // Continue searching from that position
344                             ByteOffset += ha->pUserData->dwHeaderOffs;
345                             break;
346                         }
347                     }
348                 }
349 
350                 // There must be MPQ header signature. Note that STORM.dll from Warcraft III actually
351                 // tests the MPQ header size. It must be at least 0x20 bytes in order to load it
352                 // Abused by Spazzler Map protector. Note that the size check is not present
353                 // in Storm.dll v 1.00, so Diablo I code would load the MPQ anyway.
354                 dwHeaderSize = BSWAP_INT32_UNSIGNED(ha->HeaderData[1]);
355                 if(dwHeaderID == g_dwMpqSignature && dwHeaderSize >= MPQ_HEADER_SIZE_V1)
356                 {
357                     // Now convert the header to version 4
358                     dwErrCode = ConvertMpqHeaderToFormat4(ha, ByteOffset, FileSize, dwFlags, MapType);
359                     if(dwErrCode != ERROR_FAKE_MPQ_HEADER)
360                     {
361                         bSearchComplete = true;
362                         break;
363                     }
364                 }
365 
366                 // Check for MPK archives (Longwu Online - MPQ fork)
367                 if(MapType == MapTypeNotRecognized && dwHeaderID == ID_MPK)
368                 {
369                     // Now convert the MPK header to MPQ Header version 4
370                     dwErrCode = ConvertMpkHeaderToFormat4(ha, FileSize, dwFlags);
371                     bSearchComplete = true;
372                     break;
373                 }
374 
375                 // If searching for the MPQ header is disabled, return an error
376                 if(dwFlags & MPQ_OPEN_NO_HEADER_SEARCH)
377                 {
378                     dwErrCode = ERROR_NOT_SUPPORTED;
379                     bSearchComplete = true;
380                     break;
381                 }
382 
383                 // Move the pointers
384                 ByteOffset += 0x200;
385             }
386         }
387 
388         // Did we identify one of the supported headers?
389         if(dwErrCode == ERROR_SUCCESS)
390         {
391             // Set the user data position to the MPQ header, if none
392             if(ha->pUserData == NULL)
393                 ha->UserDataPos = ByteOffset;
394 
395             // Set the position of the MPQ header
396             ha->pHeader  = (TMPQHeader *)ha->HeaderData;
397             ha->MpqPos   = ByteOffset;
398             ha->FileSize = FileSize;
399 
400             // Sector size must be nonzero.
401             if(ByteOffset >= FileSize || ha->pHeader->wSectorSize == 0)
402                 dwErrCode = ERROR_BAD_FORMAT;
403         }
404     }
405 
406     // Fix table positions according to format
407     if(dwErrCode == ERROR_SUCCESS)
408     {
409         // Dump the header
410 //      DumpMpqHeader(ha->pHeader);
411 
412         // W3x Map Protectors use the fact that War3's Storm.dll ignores the MPQ user data,
413         // and ignores the MPQ format version as well. The trick is to
414         // fake MPQ format 2, with an improper hi-word position of hash table and block table
415         // We can overcome such protectors by forcing opening the archive as MPQ v 1.0
416         if(dwFlags & MPQ_OPEN_FORCE_MPQ_V1)
417         {
418             ha->pHeader->wFormatVersion = MPQ_FORMAT_VERSION_1;
419             ha->pHeader->dwHeaderSize = MPQ_HEADER_SIZE_V1;
420             ha->dwFlags |= MPQ_FLAG_READ_ONLY;
421             ha->pUserData = NULL;
422         }
423 
424         // Anti-overflow. If the hash table size in the header is
425         // higher than 0x10000000, it would overflow in 32-bit version
426         // Observed in the malformed Warcraft III maps
427         // Example map: MPQ_2016_v1_ProtectedMap_TableSizeOverflow.w3x
428         ha->pHeader->dwBlockTableSize = (ha->pHeader->dwBlockTableSize & BLOCK_INDEX_MASK);
429         ha->pHeader->dwHashTableSize = (ha->pHeader->dwHashTableSize & BLOCK_INDEX_MASK);
430 
431         // Both MPQ_OPEN_NO_LISTFILE or MPQ_OPEN_NO_ATTRIBUTES trigger read only mode
432         if(dwFlags & (MPQ_OPEN_NO_LISTFILE | MPQ_OPEN_NO_ATTRIBUTES))
433             ha->dwFlags |= MPQ_FLAG_READ_ONLY;
434 
435         // Check if the caller wants to force adding listfile
436         if(dwFlags & MPQ_OPEN_FORCE_LISTFILE)
437             ha->dwFlags |= MPQ_FLAG_LISTFILE_FORCE;
438 
439         // Remember whether whis is a map for Warcraft III
440         if(MapType == MapTypeWarcraft3)
441             ha->dwFlags |= MPQ_FLAG_WAR3_MAP;
442 
443         // Set the size of file sector
444         ha->dwSectorSize = (0x200 << ha->pHeader->wSectorSize);
445 
446         // Verify if any of the tables doesn't start beyond the end of the file
447         dwErrCode = VerifyMpqTablePositions(ha, FileSize);
448     }
449 
450     // Read the hash table. Ignore the result, as hash table is no longer required
451     // Read HET table. Ignore the result, as HET table is no longer required
452     if(dwErrCode == ERROR_SUCCESS)
453     {
454         dwErrCode = LoadAnyHashTable(ha);
455     }
456 
457     // Now, build the file table. It will be built by combining
458     // the block table, BET table, hi-block table, (attributes) and (listfile).
459     if(dwErrCode == ERROR_SUCCESS)
460     {
461         dwErrCode = BuildFileTable(ha);
462     }
463 
464     // Load the internal listfile and include it to the file table
465     if(dwErrCode == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_LISTFILE) == 0)
466     {
467         // Quick check for (listfile)
468         pFileEntry = GetFileEntryLocale(ha, LISTFILE_NAME, LANG_NEUTRAL);
469         if(pFileEntry != NULL)
470         {
471             // Ignore result of the operation. (listfile) is optional.
472             SFileAddListFile((HANDLE)ha, NULL);
473             ha->dwFileFlags1 = pFileEntry->dwFlags;
474         }
475     }
476 
477     // Load the "(attributes)" file and merge it to the file table
478     if(dwErrCode == ERROR_SUCCESS && (dwFlags & MPQ_OPEN_NO_ATTRIBUTES) == 0 && (ha->dwFlags & MPQ_FLAG_BLOCK_TABLE_CUT) == 0)
479     {
480         // Quick check for (attributes)
481         pFileEntry = GetFileEntryLocale(ha, ATTRIBUTES_NAME, LANG_NEUTRAL);
482         if(pFileEntry != NULL)
483         {
484             // Ignore result of the operation. (attributes) is optional.
485             SAttrLoadAttributes(ha);
486             ha->dwFileFlags2 = pFileEntry->dwFlags;
487         }
488     }
489 
490     // Remember whether the archive has weak signature. Only for MPQs format 1.0.
491     if(dwErrCode == ERROR_SUCCESS)
492     {
493         // Quick check for (signature)
494         pFileEntry = GetFileEntryLocale(ha, SIGNATURE_NAME, LANG_NEUTRAL);
495         if(pFileEntry != NULL)
496         {
497             // Just remember that the archive is weak-signed
498             assert((pFileEntry->dwFlags & MPQ_FILE_EXISTS) != 0);
499             ha->dwFileFlags3 = pFileEntry->dwFlags;
500         }
501 
502         // Finally, set the MPQ_FLAG_READ_ONLY if the MPQ was found malformed
503         ha->dwFlags |= (ha->dwFlags & MPQ_FLAG_MALFORMED) ? MPQ_FLAG_READ_ONLY : 0;
504     }
505 
506     // Cleanup and exit
507     if(dwErrCode != ERROR_SUCCESS)
508     {
509         FileStream_Close(pStream);
510         FreeArchiveHandle(ha);
511         SetLastError(dwErrCode);
512         ha = NULL;
513     }
514 
515     // Free the header buffer
516     if(pbHeaderBuffer != NULL)
517         STORM_FREE(pbHeaderBuffer);
518     if(phMpq != NULL)
519         *phMpq = ha;
520     return (dwErrCode == ERROR_SUCCESS);
521 }
522 
523 //-----------------------------------------------------------------------------
524 // bool WINAPI SFileSetDownloadCallback(HANDLE, SFILE_DOWNLOAD_CALLBACK, void *);
525 //
526 // Sets a callback that is called when content is downloaded from the master MPQ
527 //
528 
SFileSetDownloadCallback(HANDLE hMpq,SFILE_DOWNLOAD_CALLBACK DownloadCB,void * pvUserData)529 bool WINAPI SFileSetDownloadCallback(HANDLE hMpq, SFILE_DOWNLOAD_CALLBACK DownloadCB, void * pvUserData)
530 {
531     TMPQArchive * ha = (TMPQArchive *)hMpq;
532 
533     // Do nothing if 'hMpq' is bad parameter
534     if(!IsValidMpqHandle(hMpq))
535     {
536         SetLastError(ERROR_INVALID_HANDLE);
537         return false;
538     }
539 
540     return FileStream_SetCallback(ha->pStream, DownloadCB, pvUserData);
541 }
542 
543 //-----------------------------------------------------------------------------
544 // bool SFileFlushArchive(HANDLE hMpq)
545 //
546 // Saves all dirty data into MPQ archive.
547 // Has similar effect like SFileCloseArchive, but the archive is not closed.
548 // Use on clients who keep MPQ archive open even for write operations,
549 // and terminating without calling SFileCloseArchive might corrupt the archive.
550 //
551 
SFileFlushArchive(HANDLE hMpq)552 bool WINAPI SFileFlushArchive(HANDLE hMpq)
553 {
554     TMPQArchive * ha;
555     DWORD dwResultError = ERROR_SUCCESS;
556     DWORD dwErrCode;
557 
558     // Do nothing if 'hMpq' is bad parameter
559     if((ha = IsValidMpqHandle(hMpq)) == NULL)
560     {
561         SetLastError(ERROR_INVALID_HANDLE);
562         return false;
563     }
564 
565     // Only if the MPQ was changed
566     if(ha->dwFlags & MPQ_FLAG_CHANGED)
567     {
568         // Indicate that we are saving MPQ internal structures
569         ha->dwFlags |= MPQ_FLAG_SAVING_TABLES;
570 
571         // Defragment the file table. This will allow us to put the internal files to the end
572         DefragmentFileTable(ha);
573 
574         //
575         // Create each internal file
576         // Note that the (signature) file is usually before (listfile) in the file table
577         //
578 
579         if(ha->dwFlags & MPQ_FLAG_SIGNATURE_NEW)
580         {
581             dwErrCode = SSignFileCreate(ha);
582             if(dwErrCode != ERROR_SUCCESS)
583                 dwResultError = dwErrCode;
584         }
585 
586         if(ha->dwFlags & (MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_FORCE))
587         {
588             dwErrCode = SListFileSaveToMpq(ha);
589             if(dwErrCode != ERROR_SUCCESS)
590                 dwResultError = dwErrCode;
591         }
592 
593         if(ha->dwFlags & MPQ_FLAG_ATTRIBUTES_NEW)
594         {
595             dwErrCode = SAttrFileSaveToMpq(ha);
596             if(dwErrCode != ERROR_SUCCESS)
597                 dwResultError = dwErrCode;
598         }
599 
600         // Save HET table, BET table, hash table, block table, hi-block table
601         if(ha->dwFlags & MPQ_FLAG_CHANGED)
602         {
603             // Rebuild the HET table
604             if(ha->pHetTable != NULL)
605                 RebuildHetTable(ha);
606 
607             // Save all MPQ tables first
608             dwErrCode = SaveMPQTables(ha);
609             if(dwErrCode != ERROR_SUCCESS)
610                 dwResultError = dwErrCode;
611 
612             // If the archive has weak signature, we need to finish it
613             if(ha->dwFileFlags3 != 0)
614             {
615                 dwErrCode = SSignFileFinish(ha);
616                 if(dwErrCode != ERROR_SUCCESS)
617                     dwResultError = dwErrCode;
618             }
619         }
620 
621         // We are no longer saving internal MPQ structures
622         ha->dwFlags &= ~MPQ_FLAG_SAVING_TABLES;
623     }
624 
625     // Return the error
626     if(dwResultError != ERROR_SUCCESS)
627         SetLastError(dwResultError);
628     return (dwResultError == ERROR_SUCCESS);
629 }
630 
631 //-----------------------------------------------------------------------------
632 // bool SFileCloseArchive(HANDLE hMpq);
633 //
634 
SFileCloseArchive(HANDLE hMpq)635 bool WINAPI SFileCloseArchive(HANDLE hMpq)
636 {
637     TMPQArchive * ha = IsValidMpqHandle(hMpq);
638     bool bResult = false;
639 
640     // Only if the handle is valid
641     if(ha == NULL)
642     {
643         SetLastError(ERROR_INVALID_HANDLE);
644         return false;
645     }
646 
647     // Invalidate the add file callback so it won't be called
648     // when saving (listfile) and (attributes)
649     ha->pfnAddFileCB = NULL;
650     ha->pvAddFileUserData = NULL;
651 
652     // Flush all unsaved data to the storage
653     bResult = SFileFlushArchive(hMpq);
654 
655     // Free all memory used by MPQ archive
656     FreeArchiveHandle(ha);
657     return bResult;
658 }
659