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