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