1 /*****************************************************************************/
2 /* SFileCreateArchiveEx.cpp Copyright (c) Ladislav Zezula 2003 */
3 /*---------------------------------------------------------------------------*/
4 /* MPQ Editing functions */
5 /*---------------------------------------------------------------------------*/
6 /* Date Ver Who Comment */
7 /* -------- ---- --- ------- */
8 /* 24.03.03 1.00 Lad Splitted from SFileOpenArchive.cpp */
9 /*****************************************************************************/
11 #define __STORMLIB_SELF__
15 //-----------------------------------------------------------------------------
18 #define DEFAULT_BLOCK_SIZE 3 // Default size of the block
20 //-----------------------------------------------------------------------------
23 static DWORD PowersOfTwo
[] =
25 0x0000002, 0x0000004, 0x0000008,
26 0x0000010, 0x0000020, 0x0000040, 0x0000080,
27 0x0000100, 0x0000200, 0x0000400, 0x0000800,
28 0x0001000, 0x0002000, 0x0004000, 0x0008000,
29 0x0010000, 0x0020000, 0x0040000, 0x0080000,
33 /*****************************************************************************/
34 /* Public functions */
35 /*****************************************************************************/
37 //-----------------------------------------------------------------------------
38 // Opens or creates a (new) MPQ archive.
40 // szMpqName - Name of the archive to be created.
42 // dwCreationDisposition:
44 // Value Archive exists Archive doesn't exist
45 // ---------- --------------------- ---------------------
46 // CREATE_NEW Fails Creates new archive
47 // CREATE_ALWAYS Overwrites existing Creates new archive
48 // OPEN_EXISTING Opens the archive Fails
49 // OPEN_ALWAYS Opens the archive Creates new archive
51 // The above mentioned values can be combined with the following flags:
53 // MPQ_CREATE_ARCHIVE_V1 - Creates MPQ archive version 1
54 // MPQ_CREATE_ARCHIVE_V2 - Creates MPQ archive version 2
56 // dwHashTableSize - Size of the hash table (only if creating a new archive).
57 // Must be between 2^4 (= 16) and 2^18 (= 262 144)
59 // phMpq - Receives handle to the archive
62 // TODO: Test for archives > 4GB
63 BOOL WINAPI
SFileCreateArchiveEx(const char * szMpqName
, DWORD dwCreationDisposition
, DWORD dwHashTableSize
, HANDLE
* phMPQ
)
65 LARGE_INTEGER MpqPos
= {0}; // Position of MPQ header in the file
66 TMPQArchive
* ha
= NULL
; // MPQ archive handle
67 HANDLE hFile
= INVALID_HANDLE_VALUE
; // File handle
68 DWORD dwTransferred
= 0; // Number of bytes written into the archive
69 USHORT wFormatVersion
;
70 BOOL bFileExists
= FALSE
;
72 int nError
= ERROR_SUCCESS
;
74 // Pre-initialize the result value
78 // Check the parameters, if they are valid
79 if(szMpqName
== NULL
|| *szMpqName
== 0 || phMPQ
== NULL
)
81 SetLastError(ERROR_INVALID_PARAMETER
);
85 // Check the value of dwCreationDisposition against file existence
86 bFileExists
= (GetFileAttributes(szMpqName
) != 0xFFFFFFFF);
88 // Extract format version from the "dwCreationDisposition"
89 wFormatVersion
= (USHORT
)(dwCreationDisposition
>> 0x10);
90 dwCreationDisposition
&= 0x0000FFFF;
92 // If the file exists and open required, do it.
93 if(bFileExists
&& (dwCreationDisposition
== OPEN_EXISTING
|| dwCreationDisposition
== OPEN_ALWAYS
))
95 // Try to open the archive normal way. If it fails, it means that
96 // the file exist, but it is not a MPQ archive.
97 if(SFileOpenArchiveEx(szMpqName
, 0, 0, phMPQ
, GENERIC_READ
| GENERIC_WRITE
))
102 if(dwCreationDisposition
== CREATE_NEW
&& bFileExists
)
104 SetLastError(ERROR_ALREADY_EXISTS
);
107 if(dwCreationDisposition
== OPEN_EXISTING
&& bFileExists
== FALSE
)
109 SetLastError(ERROR_FILE_NOT_FOUND
);
113 // At this point, we have to create the archive. If the file exists,
114 // we will convert it to MPQ archive.
115 // Check the value of hash table size. It has to be a power of two
116 // and must be between HASH_TABLE_SIZE_MIN and HASH_TABLE_SIZE_MAX
117 if(dwHashTableSize
< HASH_TABLE_SIZE_MIN
)
118 dwHashTableSize
= HASH_TABLE_SIZE_MIN
;
119 if(dwHashTableSize
> HASH_TABLE_SIZE_MAX
)
120 dwHashTableSize
= HASH_TABLE_SIZE_MAX
;
122 // Round the hash table size up to the nearest power of two
123 for(nIndex
= 0; PowersOfTwo
[nIndex
] != 0; nIndex
++)
125 if(dwHashTableSize
<= PowersOfTwo
[nIndex
])
127 dwHashTableSize
= PowersOfTwo
[nIndex
];
132 // Prepare the buffer for decryption engine
133 if(nError
== ERROR_SUCCESS
)
134 nError
= PrepareStormBuffer();
136 // Get the position where the MPQ header will begin.
137 if(nError
== ERROR_SUCCESS
)
139 hFile
= CreateFile(szMpqName
,
140 GENERIC_READ
| GENERIC_WRITE
,
143 dwCreationDisposition
,
146 if(hFile
== INVALID_HANDLE_VALUE
)
147 nError
= GetLastError();
150 // Retrieve the file size and round it up to 0x200 bytes
151 if(nError
== ERROR_SUCCESS
)
153 MpqPos
.LowPart
= GetFileSize(hFile
, (LPDWORD
)&MpqPos
.HighPart
);
154 MpqPos
.QuadPart
+= 0x1FF;
155 MpqPos
.LowPart
&= 0xFFFFFE00;
157 if(wFormatVersion
== MPQ_FORMAT_VERSION_1
&& MpqPos
.HighPart
!= 0)
158 nError
= ERROR_DISK_FULL
;
159 if(wFormatVersion
== MPQ_FORMAT_VERSION_2
&& MpqPos
.HighPart
> 0x0000FFFF)
160 nError
= ERROR_DISK_FULL
;
163 // Move to the end of the file (i.e. begin of the MPQ)
164 if(nError
== ERROR_SUCCESS
)
166 if(SetFilePointer(hFile
, MpqPos
.LowPart
, &MpqPos
.HighPart
, FILE_BEGIN
) == 0xFFFFFFFF)
167 nError
= GetLastError();
170 // Set the new end of the file to the MPQ header position
171 if(nError
== ERROR_SUCCESS
)
173 if(!SetEndOfFile(hFile
))
174 nError
= GetLastError();
177 // Create the archive handle
178 if(nError
== ERROR_SUCCESS
)
180 if((ha
= ALLOCMEM(TMPQArchive
, 1)) == NULL
)
181 nError
= ERROR_NOT_ENOUGH_MEMORY
;
184 // Fill the MPQ archive handle structure and create the header,
185 // block buffer, hash table and block table
186 if(nError
== ERROR_SUCCESS
)
188 memset(ha
, 0, sizeof(TMPQArchive
));
189 strcpy(ha
->szFileName
, szMpqName
);
191 ha
->dwBlockSize
= 0x200 << DEFAULT_BLOCK_SIZE
;
193 ha
->FilePointer
= MpqPos
;
194 ha
->pHeader
= &ha
->Header
;
195 ha
->pHashTable
= ALLOCMEM(TMPQHash
, dwHashTableSize
);
196 ha
->pBlockTable
= ALLOCMEM(TMPQBlock
, dwHashTableSize
);
197 ha
->pExtBlockTable
= ALLOCMEM(TMPQBlockEx
, dwHashTableSize
);
198 ha
->pbBlockBuffer
= ALLOCMEM(BYTE
, ha
->dwBlockSize
);
199 ha
->pListFile
= NULL
;
200 ha
->dwFlags
|= MPQ_FLAG_CHANGED
;
202 if(!ha
->pHashTable
|| !ha
->pBlockTable
|| !ha
->pExtBlockTable
|| !ha
->pbBlockBuffer
)
203 nError
= GetLastError();
204 hFile
= INVALID_HANDLE_VALUE
;
207 // Fill the MPQ header and all buffers
208 if(nError
== ERROR_SUCCESS
)
210 LARGE_INTEGER TempPos
;
211 TMPQHeader2
* pHeader
= ha
->pHeader
;
212 DWORD dwHeaderSize
= (wFormatVersion
== MPQ_FORMAT_VERSION_2
) ? sizeof(TMPQHeader2
) : sizeof(TMPQHeader
);
214 memset(pHeader
, 0, sizeof(TMPQHeader2
));
215 pHeader
->dwID
= ID_MPQ
;
216 pHeader
->dwHeaderSize
= dwHeaderSize
;
217 pHeader
->dwArchiveSize
= pHeader
->dwHeaderSize
+ dwHashTableSize
* sizeof(TMPQHash
);
218 pHeader
->wFormatVersion
= wFormatVersion
;
219 pHeader
->wBlockSize
= 3; // 0x1000 bytes per block
220 pHeader
->dwHashTableSize
= dwHashTableSize
;
222 // Set proper hash table positions
223 ha
->HashTablePos
.QuadPart
= ha
->MpqPos
.QuadPart
+ pHeader
->dwHeaderSize
;
224 ha
->pHeader
->dwHashTablePos
= pHeader
->dwHeaderSize
;
225 ha
->pHeader
->wHashTablePosHigh
= 0;
227 // Set proper block table positions
228 ha
->BlockTablePos
.QuadPart
= ha
->HashTablePos
.QuadPart
+
229 (ha
->pHeader
->dwHashTableSize
* sizeof(TMPQHash
));
230 TempPos
.QuadPart
= ha
->BlockTablePos
.QuadPart
- ha
->MpqPos
.QuadPart
;
231 ha
->pHeader
->dwBlockTablePos
= TempPos
.LowPart
;
232 ha
->pHeader
->wBlockTablePosHigh
= (USHORT
)TempPos
.HighPart
;
234 // For now, we set extended block table positioon top zero unless we add enough
235 // files to cause the archive size exceed 4 GB
236 ha
->ExtBlockTablePos
.QuadPart
= 0;
239 memset(ha
->pBlockTable
, 0, sizeof(TMPQBlock
) * dwHashTableSize
);
240 memset(ha
->pExtBlockTable
, 0, sizeof(TMPQBlockEx
) * dwHashTableSize
);
241 memset(ha
->pHashTable
, 0xFF, sizeof(TMPQHash
) * dwHashTableSize
);
244 // Write the MPQ header to the file
245 if(nError
== ERROR_SUCCESS
)
247 DWORD dwHeaderSize
= ha
->pHeader
->dwHeaderSize
;
249 BSWAP_TMPQHEADER(ha
->pHeader
);
250 WriteFile(ha
->hFile
, ha
->pHeader
, dwHeaderSize
, &dwTransferred
, NULL
);
251 BSWAP_TMPQHEADER(ha
->pHeader
);
253 if(dwTransferred
!= ha
->pHeader
->dwHeaderSize
)
254 nError
= ERROR_DISK_FULL
;
256 ha
->FilePointer
.QuadPart
= ha
->MpqPos
.QuadPart
+ dwTransferred
;
257 ha
->MpqSize
.QuadPart
+= dwTransferred
;
260 // Create the internal listfile
261 if(nError
== ERROR_SUCCESS
)
262 nError
= SListFileCreateListFile(ha
);
264 // Try to add the internal listfile
265 if(nError
== ERROR_SUCCESS
)
266 SFileAddListFile((HANDLE
)ha
, NULL
);
268 // Cleanup : If an error, delete all buffers and return
269 if(nError
!= ERROR_SUCCESS
)
272 if(hFile
!= INVALID_HANDLE_VALUE
)
274 SetLastError(nError
);
279 return (nError
== ERROR_SUCCESS
);
282 //-----------------------------------------------------------------------------
283 // Changes locale ID of a file
285 // TODO: Test for archives > 4GB
286 BOOL WINAPI
SFileSetFileLocale(HANDLE hFile
, LCID lcNewLocale
)
288 TMPQFile
* hf
= (TMPQFile
*)hFile
;
290 // Invalid handle => do nothing
291 if(IsValidFileHandle(hf
) == FALSE
|| IsValidMpqHandle(hf
->ha
) == FALSE
)
293 SetLastError(ERROR_INVALID_PARAMETER
);
297 // If the file has not been open for writing, do nothing.
298 if(hf
->ha
->pListFile
== NULL
)
299 return ERROR_ACCESS_DENIED
;
301 hf
->pHash
->lcLocale
= (USHORT
)lcNewLocale
;
302 hf
->ha
->dwFlags
|= MPQ_FLAG_CHANGED
;
306 //-----------------------------------------------------------------------------
307 // Adds a file into the archive
309 // TODO: Test for archives > 4GB
310 BOOL WINAPI
SFileAddFileEx(HANDLE hMPQ
, const char * szFileName
, const char * szArchivedName
, DWORD dwFlags
, DWORD dwQuality
, int nFileType
)
312 TMPQArchive
* ha
= (TMPQArchive
*)hMPQ
;
313 HANDLE hFile
= INVALID_HANDLE_VALUE
;
314 BOOL bReplaced
= FALSE
; // TRUE if replacing file in the archive
315 int nError
= ERROR_SUCCESS
;
317 if(nError
== ERROR_SUCCESS
)
319 // Check valid parameters
320 if(IsValidMpqHandle(ha
) == FALSE
|| szFileName
== NULL
|| *szFileName
== 0 || szArchivedName
== NULL
|| *szArchivedName
== 0)
321 nError
= ERROR_INVALID_PARAMETER
;
323 // Check the values of dwFlags
324 if((dwFlags
& MPQ_FILE_COMPRESS_PKWARE
) && (dwFlags
& MPQ_FILE_COMPRESS_MULTI
))
325 nError
= ERROR_INVALID_PARAMETER
;
328 // If anyone is trying to add listfile, and the archive already has a listfile,
329 // deny the operation, but return success.
330 if(nError
== ERROR_SUCCESS
)
332 if(ha
->pListFile
!= NULL
&& !_stricmp(szFileName
, LISTFILE_NAME
))
333 return ERROR_SUCCESS
;
337 if(nError
== ERROR_SUCCESS
)
339 hFile
= CreateFile(szFileName
, GENERIC_READ
, FILE_SHARE_READ
, 0, OPEN_EXISTING
, 0, NULL
);
340 if(hFile
== INVALID_HANDLE_VALUE
)
341 nError
= GetLastError();
344 if(nError
== ERROR_SUCCESS
)
345 nError
= AddFileToArchive(ha
, hFile
, szArchivedName
, dwFlags
, dwQuality
, nFileType
, &bReplaced
);
347 // Add the file into listfile also
348 if(nError
== ERROR_SUCCESS
&& bReplaced
== FALSE
)
349 nError
= SListFileAddNode(ha
, szArchivedName
);
352 if(hFile
!= INVALID_HANDLE_VALUE
)
354 if(nError
!= ERROR_SUCCESS
)
355 SetLastError(nError
);
356 return (nError
== ERROR_SUCCESS
);
359 // Adds a data file into the archive
360 // TODO: Test for archives > 4GB
361 BOOL WINAPI
SFileAddFile(HANDLE hMPQ
, const char * szFileName
, const char * szArchivedName
, DWORD dwFlags
)
363 return SFileAddFileEx(hMPQ
, szFileName
, szArchivedName
, dwFlags
, 0, SFILE_TYPE_DATA
);
366 // Adds a WAVE file into the archive
367 // TODO: Test for archives > 4GB
368 BOOL WINAPI
SFileAddWave(HANDLE hMPQ
, const char * szFileName
, const char * szArchivedName
, DWORD dwFlags
, DWORD dwQuality
)
370 return SFileAddFileEx(hMPQ
, szFileName
, szArchivedName
, dwFlags
, dwQuality
, SFILE_TYPE_WAVE
);
373 //-----------------------------------------------------------------------------
374 // BOOL SFileRemoveFile(HANDLE hMPQ, char * szFileName)
376 // This function removes a file from the archive. The file content
377 // remains there, only the entries in the hash table and in the block
378 // table are updated.
380 // TODO: Test for archives > 4GB
381 BOOL WINAPI
SFileRemoveFile(HANDLE hMPQ
, const char * szFileName
, DWORD dwSearchScope
)
383 TMPQArchive
* ha
= (TMPQArchive
*)hMPQ
;
384 TMPQBlockEx
* pBlockEx
= NULL
; // Block entry of deleted file
385 TMPQBlock
* pBlock
= NULL
; // Block entry of deleted file
386 TMPQHash
* pHash
= NULL
; // Hash entry of deleted file
387 DWORD dwBlockIndex
= 0;
388 int nError
= ERROR_SUCCESS
;
390 // Check the parameters
391 if(nError
== ERROR_SUCCESS
)
393 if(IsValidMpqHandle(ha
) == FALSE
)
394 nError
= ERROR_INVALID_PARAMETER
;
395 if(dwSearchScope
!= SFILE_OPEN_BY_INDEX
&& *szFileName
== 0)
396 nError
= ERROR_INVALID_PARAMETER
;
399 // Do not allow to remove listfile
400 if(nError
== ERROR_SUCCESS
)
402 if(dwSearchScope
!= SFILE_OPEN_BY_INDEX
&& !_stricmp(szFileName
, LISTFILE_NAME
))
403 nError
= ERROR_ACCESS_DENIED
;
406 // Get hash entry belonging to this file
407 if(nError
== ERROR_SUCCESS
)
409 if((pHash
= GetHashEntryEx(ha
, (char *)szFileName
, lcLocale
)) == NULL
)
410 nError
= ERROR_FILE_NOT_FOUND
;
413 // If index was not found, or is greater than number of files, exit.
414 if(nError
== ERROR_SUCCESS
)
416 if((dwBlockIndex
= pHash
->dwBlockIndex
) > ha
->pHeader
->dwBlockTableSize
)
417 nError
= ERROR_FILE_NOT_FOUND
;
420 // Get block and test if the file is not already deleted
421 if(nError
== ERROR_SUCCESS
)
423 pBlockEx
= ha
->pExtBlockTable
+ dwBlockIndex
;
424 pBlock
= ha
->pBlockTable
+ dwBlockIndex
;
425 if((pBlock
->dwFlags
& MPQ_FILE_EXISTS
) == 0)
426 nError
= ERROR_FILE_NOT_FOUND
;
429 // Now invalidate the block entry and the hash entry. Do not make any
430 // relocations and file copying, use SFileCompactArchive for it.
431 if(nError
== ERROR_SUCCESS
)
433 pBlockEx
->wFilePosHigh
= 0;
434 pBlock
->dwFilePos
= 0;
438 pHash
->dwName1
= 0xFFFFFFFF;
439 pHash
->dwName2
= 0xFFFFFFFF;
440 pHash
->lcLocale
= 0xFFFF;
441 pHash
->wPlatform
= 0xFFFF;
442 pHash
->dwBlockIndex
= HASH_ENTRY_DELETED
;
444 // Update MPQ archive
445 ha
->dwFlags
|= MPQ_FLAG_CHANGED
;
448 // Remove the file from the list file
449 if(nError
== ERROR_SUCCESS
&& lcLocale
== LANG_NEUTRAL
)
450 nError
= SListFileRemoveNode(ha
, szFileName
);
452 // Resolve error and exit
453 if(nError
!= ERROR_SUCCESS
)
454 SetLastError(nError
);
455 return (nError
== ERROR_SUCCESS
);
458 // Renames the file within the archive.
459 // TODO: Test for archives > 4GB
460 BOOL WINAPI
SFileRenameFile(HANDLE hMPQ
, const char * szFileName
, const char * szNewFileName
)
462 TMPQArchive
* ha
= (TMPQArchive
*)hMPQ
;
463 TMPQHash
* pOldHash
= NULL
; // Hash entry for the original file
464 TMPQHash
* pNewHash
= NULL
; // Hash entry for the renamed file
465 DWORD dwBlockIndex
= 0;
466 int nError
= ERROR_SUCCESS
;
468 // Test the valid parameters
469 if(nError
== ERROR_SUCCESS
)
471 if(hMPQ
== NULL
|| szNewFileName
== NULL
|| *szNewFileName
== 0)
472 nError
= ERROR_INVALID_PARAMETER
;
473 if(szFileName
== NULL
|| *szFileName
== 0)
474 nError
= ERROR_INVALID_PARAMETER
;
477 // Do not allow to rename listfile
478 if(nError
== ERROR_SUCCESS
)
480 if(!_stricmp(szFileName
, LISTFILE_NAME
))
481 nError
= ERROR_ACCESS_DENIED
;
484 // Test if the file already exists in the archive
485 if(nError
== ERROR_SUCCESS
)
487 if((pNewHash
= GetHashEntryEx(ha
, szNewFileName
, lcLocale
)) != NULL
)
488 nError
= ERROR_ALREADY_EXISTS
;
491 // Get the hash table entry for the original file
492 if(nError
== ERROR_SUCCESS
)
494 if((pOldHash
= GetHashEntryEx(ha
, szFileName
, lcLocale
)) == NULL
)
495 nError
= ERROR_FILE_NOT_FOUND
;
498 // Get the hash table entry for the renamed file
499 if(nError
== ERROR_SUCCESS
)
501 // Save block table index and remove the hash table entry
502 dwBlockIndex
= pOldHash
->dwBlockIndex
;
503 pOldHash
->dwName1
= 0xFFFFFFFF;
504 pOldHash
->dwName2
= 0xFFFFFFFF;
505 pOldHash
->lcLocale
= 0xFFFF;
506 pOldHash
->wPlatform
= 0xFFFF;
507 pOldHash
->dwBlockIndex
= HASH_ENTRY_DELETED
;
509 if((pNewHash
= FindFreeHashEntry(ha
, szNewFileName
)) == NULL
)
510 nError
= ERROR_CAN_NOT_COMPLETE
;
513 // Save the block index and clear the hash entry
514 if(nError
== ERROR_SUCCESS
)
516 // Copy the block table index
517 pNewHash
->dwBlockIndex
= dwBlockIndex
;
518 ha
->dwFlags
|= MPQ_FLAG_CHANGED
;
521 // Rename the file in the list file
522 if(nError
== ERROR_SUCCESS
)
523 nError
= SListFileRenameNode(ha
, szFileName
, szNewFileName
);
525 // Resolve error and return
526 if(nError
!= ERROR_SUCCESS
)
527 SetLastError(nError
);
528 return (nError
== ERROR_SUCCESS
);