Initial Patch of Auction House bot rev. 135
[auctionmangos.git] / contrib / vmap_extractor_v2 / stormlib / SFileCreateArchiveEx.cpp
blob2410920fc9391f17d325b3286f84df7d4b8e13a6
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__
12 #include "StormLib.h"
13 #include "SCommon.h"
15 //-----------------------------------------------------------------------------
16 // Defines
18 #define DEFAULT_BLOCK_SIZE 3 // Default size of the block
20 //-----------------------------------------------------------------------------
21 // Local tables
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,
30 0x0000000
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
55 //
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;
71 int nIndex = 0;
72 int nError = ERROR_SUCCESS;
74 // Pre-initialize the result value
75 if(phMPQ != NULL)
76 *phMPQ = NULL;
78 // Check the parameters, if they are valid
79 if(szMpqName == NULL || *szMpqName == 0 || phMPQ == NULL)
81 SetLastError(ERROR_INVALID_PARAMETER);
82 return FALSE;
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))
98 return TRUE;
101 // Two error cases
102 if(dwCreationDisposition == CREATE_NEW && bFileExists)
104 SetLastError(ERROR_ALREADY_EXISTS);
105 return FALSE;
107 if(dwCreationDisposition == OPEN_EXISTING && bFileExists == FALSE)
109 SetLastError(ERROR_FILE_NOT_FOUND);
110 return FALSE;
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];
128 break;
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,
141 FILE_SHARE_READ,
142 NULL,
143 dwCreationDisposition,
145 NULL);
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);
190 ha->hFile = hFile;
191 ha->dwBlockSize = 0x200 << DEFAULT_BLOCK_SIZE;
192 ha->MpqPos = MpqPos;
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;
238 // Clear all tables
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)
271 FreeMPQArchive(ha);
272 if(hFile != INVALID_HANDLE_VALUE)
273 CloseHandle(hFile);
274 SetLastError(nError);
277 // Return the values
278 *phMPQ = (HANDLE)ha;
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);
294 return FALSE;
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;
303 return TRUE;
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;
336 // Open added file
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);
351 // Cleanup and exit
352 if(hFile != INVALID_HANDLE_VALUE)
353 CloseHandle(hFile);
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;
435 pBlock->dwFSize = 0;
436 pBlock->dwCSize = 0;
437 pBlock->dwFlags = 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);