1 /*****************************************************************************/
2 /* SListFile.cpp Copyright (c) Ladislav Zezula 2004 */
3 /*---------------------------------------------------------------------------*/
5 /*---------------------------------------------------------------------------*/
6 /* Date Ver Who Comment */
7 /* -------- ---- --- ------- */
8 /* 12.06.04 1.00 Lad The first version of SListFile.cpp */
9 /*****************************************************************************/
11 #define __STORMLIB_SELF__
16 //-----------------------------------------------------------------------------
17 // Listfile entry structure
19 #define LISTFILE_CACHE_SIZE 0x1000 // Size of one cache element
20 #define NO_MORE_CHARACTERS 256
21 #define HASH_TABLE_SIZE 31 // Initial hash table size (should be a prime number)
23 // TODO: Check on x64 !!!
24 #define LISTFILE_ENTRY_DELETED (DWORD_PTR)(-2)
25 #define LISTFILE_ENTRY_FREE (DWORD_PTR)(-1)
29 HANDLE hFile
; // Stormlib file handle
30 char * szMask
; // File mask
31 DWORD dwFileSize
; // Total size of the cached file
32 DWORD dwBuffSize
; // File of the cache
33 DWORD dwFilePos
; // Position of the cache in the file
34 BYTE
* pBegin
; // The begin of the listfile cache
36 BYTE
* pEnd
; // The last character in the file cache
38 BYTE Buffer
[1]; // Listfile cache itself
41 //-----------------------------------------------------------------------------
42 // Local functions (cache)
44 // Reloads the cache. Returns number of characters
45 // that has been loaded into the cache.
46 static int ReloadCache(TListFileCache
* pCache
)
48 // Check if there is enough characters in the cache
49 // If not, we have to reload the next block
50 if(pCache
->pPos
>= pCache
->pEnd
)
52 // If the cache is already at the end, do nothing more
53 if((pCache
->dwFilePos
+ pCache
->dwBuffSize
) >= pCache
->dwFileSize
)
56 pCache
->dwFilePos
+= pCache
->dwBuffSize
;
57 SFileReadFile(pCache
->hFile
, pCache
->Buffer
, pCache
->dwBuffSize
, &pCache
->dwBuffSize
, NULL
);
58 if(pCache
->dwBuffSize
== 0)
61 // Set the buffer pointers
63 pCache
->pPos
= &pCache
->Buffer
[0];
64 pCache
->pEnd
= pCache
->pBegin
+ pCache
->dwBuffSize
;
67 return pCache
->dwBuffSize
;
70 static size_t ReadLine(TListFileCache
* pCache
, char * szLine
, int nMaxChars
)
72 char * szLineBegin
= szLine
;
73 char * szLineEnd
= szLine
+ nMaxChars
- 1;
77 // Skip newlines, spaces, tabs and another non-printable stuff
78 while(pCache
->pPos
< pCache
->pEnd
&& *pCache
->pPos
<= 0x20)
81 // Copy the remaining characters
82 while(pCache
->pPos
< pCache
->pEnd
&& szLine
< szLineEnd
)
84 // If we have found a newline, stop loading
85 if(*pCache
->pPos
== 0x0D || *pCache
->pPos
== 0x0A)
88 *szLine
++ = *pCache
->pPos
++;
91 // If we now need to reload the cache, do it
92 if(pCache
->pPos
== pCache
->pEnd
)
94 if(ReloadCache(pCache
) > 0)
99 return (szLine
- szLineBegin
);
102 //-----------------------------------------------------------------------------
103 // Local functions (listfile nodes)
105 // This function creates the name for the listfile.
106 // the file will be created under unique name in the temporary directory
107 static void GetListFileName(TMPQArchive
* /* ha */, char * szListFile
)
109 char szTemp
[MAX_PATH
];
111 // Create temporary file name int TEMP directory
112 GetTempPath(sizeof(szTemp
)-1, szTemp
);
113 GetTempFileName(szTemp
, LISTFILE_NAME
, 0, szListFile
);
116 // Creates new listfile. The listfile is an array of TListFileNode
117 // structures. The size of the array is the same like the hash table size,
118 // the ordering is the same too (listfile item index is the same like
119 // the index in the MPQ hash table)
121 int SListFileCreateListFile(TMPQArchive
* ha
)
123 DWORD dwItems
= ha
->pHeader
->dwHashTableSize
;
125 // The listfile should be NULL now
126 assert(ha
->pListFile
== NULL
);
128 ha
->pListFile
= ALLOCMEM(TFileNode
*, dwItems
);
129 if(ha
->pListFile
== NULL
)
130 return ERROR_NOT_ENOUGH_MEMORY
;
132 memset(ha
->pListFile
, 0xFF, dwItems
* sizeof(TFileNode
*));
133 return ERROR_SUCCESS
;
136 // Adds a filename into the listfile. If the file name is already there,
138 int SListFileAddNode(TMPQArchive
* ha
, const char * szFileName
)
140 TFileNode
* pNode
= NULL
;
141 TMPQHash
* pHashEnd
= ha
->pHashTable
+ ha
->pHeader
->dwHashTableSize
;
142 TMPQHash
* pHash0
= GetHashEntry(ha
, szFileName
);
143 TMPQHash
* pHash
= pHash0
;
144 DWORD dwHashIndex
= 0;
145 size_t nLength
; // File name lentgth
149 // If the file does not exist within the MPQ, do nothing
151 return ERROR_SUCCESS
;
153 // If the listfile entry already exists, do nothing
154 dwHashIndex
= (DWORD
)(pHash
- ha
->pHashTable
);
155 dwName1
= pHash
->dwName1
;
156 dwName2
= pHash
->dwName2
;
157 if((DWORD_PTR
)ha
->pListFile
[dwHashIndex
] <= LISTFILE_ENTRY_DELETED
)
158 return ERROR_SUCCESS
;
160 // Create the listfile node and insert it into the listfile table
161 nLength
= strlen(szFileName
);
162 pNode
= (TFileNode
*)ALLOCMEM(char, sizeof(TFileNode
) + nLength
);
163 pNode
->dwRefCount
= 0;
164 pNode
->nLength
= nLength
;
165 strcpy(pNode
->szFileName
, szFileName
);
167 // Fill the nodes for all language versions
168 while(pHash
->dwBlockIndex
< LISTFILE_ENTRY_DELETED
)
170 if(pHash
->dwName1
== dwName1
&& pHash
->dwName2
== dwName2
)
173 ha
->pListFile
[pHash
- ha
->pHashTable
] = pNode
;
176 if(++pHash
>= pHashEnd
)
177 pHash
= ha
->pHashTable
;
182 return ERROR_SUCCESS
;
185 // Removes a filename from the listfile.
186 // If the name is not there, does nothing
187 int SListFileRemoveNode(TMPQArchive
* ha
, const char * szFileName
)
189 TFileNode
* pNode
= NULL
;
190 TMPQHash
* pHash
= GetHashEntry(ha
, szFileName
);
191 size_t nHashIndex
= 0;
195 nHashIndex
= pHash
- ha
->pHashTable
;
196 pNode
= ha
->pListFile
[nHashIndex
];
197 ha
->pListFile
[nHashIndex
] = (TFileNode
*)LISTFILE_ENTRY_DELETED
;
199 // If the reference count has reached zero, do nothing
200 if(--pNode
->dwRefCount
== 0)
203 return ERROR_SUCCESS
;
207 // Renames a node. We will not deal with the renaming, we'll simply
208 // remove the old node and insert the new one.
209 // TODO: Test for archives > 4GB
210 int SListFileRenameNode(TMPQArchive
* ha
, const char * szOldFileName
, const char * szNewFileName
)
212 SListFileRemoveNode(ha
, szOldFileName
);
213 return SListFileAddNode(ha
, szNewFileName
);
216 // TODO: Test for archives > 4GB
217 int SListFileFreeListFile(TMPQArchive
* ha
)
219 if(ha
->pListFile
!= NULL
)
221 for(DWORD i
= 0; i
< ha
->pHeader
->dwHashTableSize
; i
++)
223 TFileNode
* pNode
= ha
->pListFile
[i
];
225 if((DWORD_PTR
)pNode
< LISTFILE_ENTRY_FREE
)
227 if(--pNode
->dwRefCount
== 0)
230 ha
->pListFile
[i
] = (TFileNode
*)LISTFILE_ENTRY_FREE
;
235 FREEMEM(ha
->pListFile
);
236 ha
->pListFile
= NULL
;
239 return ERROR_SUCCESS
;
242 // Saves the whole listfile into the MPQ.
243 // TODO: Test for archives > 4GB
244 int SListFileSaveToMpq(TMPQArchive
* ha
)
246 TFileNode
* pNode
= NULL
;
247 TMPQHash
* pHashEnd
= NULL
;
248 TMPQHash
* pHash0
= NULL
;
249 TMPQHash
* pHash
= NULL
;
250 HANDLE hFile
= INVALID_HANDLE_VALUE
;
251 char szListFile
[MAX_PATH
];
252 char szBuffer
[MAX_PATH
+4];
257 LCID lcSave
= lcLocale
;
258 int nError
= ERROR_SUCCESS
;
260 // If no listfile, do nothing
261 if(ha
->pListFile
== NULL
)
262 return ERROR_SUCCESS
;
264 // Create the local listfile
265 if(nError
== ERROR_SUCCESS
)
267 GetListFileName(ha
, szListFile
);
268 hFile
= CreateFile(szListFile
, GENERIC_READ
| GENERIC_WRITE
, 0, NULL
, CREATE_ALWAYS
, FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
269 if(hFile
== INVALID_HANDLE_VALUE
)
270 nError
= GetLastError();
273 // Find the hash entry corresponding to listfile
274 pHashEnd
= ha
->pHashTable
+ ha
->pHeader
->dwHashTableSize
;
275 pHash0
= pHash
= GetHashEntry(ha
, 0);
277 pHash0
= pHash
= ha
->pHashTable
;
280 if(nError
== ERROR_SUCCESS
)
284 if(pHash
->dwName1
!= dwName1
&& pHash
->dwName2
!= dwName2
&& pHash
->dwBlockIndex
< LISTFILE_ENTRY_DELETED
)
286 dwName1
= pHash
->dwName1
;
287 dwName2
= pHash
->dwName2
;
288 pNode
= ha
->pListFile
[pHash
- ha
->pHashTable
];
290 if((DWORD_PTR
)pNode
< LISTFILE_ENTRY_DELETED
)
292 memcpy(szBuffer
, pNode
->szFileName
, pNode
->nLength
);
293 szBuffer
[pNode
->nLength
+ 0] = 0x0D;
294 szBuffer
[pNode
->nLength
+ 1] = 0x0A;
295 WriteFile(hFile
, szBuffer
, (DWORD
)(pNode
->nLength
+ 2), &dwTransferred
, NULL
);
299 if(++pHash
>= pHashEnd
)
300 pHash
= ha
->pHashTable
;
305 // Write the listfile name (if not already there)
306 if(GetHashEntry(ha
, LISTFILE_NAME
) == NULL
)
308 nLength
= strlen(LISTFILE_NAME
);
309 memcpy(szBuffer
, LISTFILE_NAME
, nLength
);
310 szBuffer
[nLength
+ 0] = 0x0D;
311 szBuffer
[nLength
+ 1] = 0x0A;
312 WriteFile(hFile
, szBuffer
, (DWORD
)(nLength
+ 2), &dwTransferred
, NULL
);
315 // Add the listfile into the archive.
316 SFileSetLocale(LANG_NEUTRAL
);
317 nError
= AddFileToArchive(ha
, hFile
, LISTFILE_NAME
, MPQ_FILE_COMPRESS_PKWARE
| MPQ_FILE_ENCRYPTED
| MPQ_FILE_REPLACEEXISTING
, 0, SFILE_TYPE_DATA
, NULL
);
320 // Close the temporary file. This will delete it too.
321 if(hFile
!= INVALID_HANDLE_VALUE
)
328 //-----------------------------------------------------------------------------
331 // Adds a listfile into the MPQ archive.
332 // Note that the function does not remove the
333 // TODO: Test for archives > 4GB
334 int WINAPI
SFileAddListFile(HANDLE hMpq
, const char * szListFile
)
336 TListFileCache
* pCache
= NULL
;
337 TMPQArchive
* ha
= (TMPQArchive
*)hMpq
;
338 HANDLE hListFile
= NULL
;
339 char szFileName
[MAX_PATH
+ 1];
340 DWORD dwSearchScope
= SFILE_OPEN_LOCAL_FILE
;
341 DWORD dwCacheSize
= 0;
342 DWORD dwFileSize
= 0;
344 int nError
= ERROR_SUCCESS
;
346 // If the szListFile is NULL, it means we have to open internal listfile
347 if(szListFile
== NULL
)
349 szListFile
= LISTFILE_NAME
;
350 dwSearchScope
= SFILE_OPEN_FROM_MPQ
;
353 // Open the local/internal listfile
354 if(nError
== ERROR_SUCCESS
)
356 if(!SFileOpenFileEx((HANDLE
)ha
, szListFile
, dwSearchScope
, &hListFile
))
357 nError
= GetLastError();
360 if(nError
== ERROR_SUCCESS
)
363 dwFileSize
= SFileGetFileSize(hListFile
, NULL
);
365 // Try to allocate memory for the complete file. If it fails,
366 // load the part of the file
367 pCache
= (TListFileCache
*)ALLOCMEM(char, (sizeof(TListFileCache
) + dwCacheSize
));
370 dwCacheSize
= LISTFILE_CACHE_SIZE
;
371 pCache
= (TListFileCache
*)ALLOCMEM(char, sizeof(TListFileCache
) + dwCacheSize
);
375 nError
= ERROR_NOT_ENOUGH_MEMORY
;
378 if(nError
== ERROR_SUCCESS
)
380 // Initialize the file cache
381 memset(pCache
, 0, sizeof(TListFileCache
));
382 pCache
->hFile
= hListFile
;
383 pCache
->dwFileSize
= dwFileSize
;
384 pCache
->dwBuffSize
= dwCacheSize
;
385 pCache
->dwFilePos
= 0;
388 SFileReadFile(hListFile
, pCache
->Buffer
, pCache
->dwBuffSize
, &pCache
->dwBuffSize
, NULL
);
390 // Initialize the pointers
392 pCache
->pPos
= &pCache
->Buffer
[0];
393 pCache
->pEnd
= pCache
->pBegin
+ pCache
->dwBuffSize
;
395 // Load the node tree
396 while((nLength
= ReadLine(pCache
, szFileName
, sizeof(szFileName
) - 1)) > 0)
397 SListFileAddNode(ha
, szFileName
);
399 // Add well-known names
400 // Sometimes, they are not in listfile, but they exist in the archive
401 SListFileAddNode(ha
, LISTFILE_NAME
);
402 SListFileAddNode(ha
, SIGNATURE_NAME
);
403 SListFileAddNode(ha
, ATTRIBUTES_NAME
);
408 SListFileFindClose((HANDLE
)pCache
);
412 //-----------------------------------------------------------------------------
413 // Passing through the listfile
415 // TODO: Test for archives > 4GB
416 HANDLE
SListFileFindFirstFile(HANDLE hMpq
, const char * szListFile
, const char * szMask
, SFILE_FIND_DATA
* lpFindFileData
)
418 TListFileCache
* pCache
= NULL
;
419 TMPQArchive
* ha
= (TMPQArchive
*)hMpq
;
420 HANDLE hListFile
= NULL
;
421 DWORD dwSearchScope
= SFILE_OPEN_LOCAL_FILE
;
422 DWORD dwCacheSize
= 0;
423 DWORD dwFileSize
= 0;
425 int nError
= ERROR_SUCCESS
;
427 // Initialize the structure with zeros
428 memset(lpFindFileData
, 0, sizeof(SFILE_FIND_DATA
));
430 // If the szListFile is NULL, it means we have to open internal listfile
431 if(szListFile
== NULL
)
433 szListFile
= LISTFILE_NAME
;
434 dwSearchScope
= SFILE_OPEN_FROM_MPQ
;
437 // Open the local/internal listfile
438 if(nError
== ERROR_SUCCESS
)
440 if(!SFileOpenFileEx((HANDLE
)ha
, szListFile
, dwSearchScope
, &hListFile
))
441 nError
= GetLastError();
444 if(nError
== ERROR_SUCCESS
)
447 dwFileSize
= SFileGetFileSize(hListFile
, NULL
);
449 // Try to allocate memory for the complete file. If it fails,
450 // load the part of the file
451 pCache
= (TListFileCache
*)ALLOCMEM(char, sizeof(TListFileCache
) + dwCacheSize
);
454 dwCacheSize
= LISTFILE_CACHE_SIZE
;
455 pCache
= (TListFileCache
*)ALLOCMEM(char, sizeof(TListFileCache
) + dwCacheSize
);
459 nError
= ERROR_NOT_ENOUGH_MEMORY
;
462 if(nError
== ERROR_SUCCESS
)
464 // Initialize the file cache
465 memset(pCache
, 0, sizeof(TListFileCache
));
466 pCache
->hFile
= hListFile
;
467 pCache
->dwFileSize
= dwFileSize
;
468 pCache
->dwBuffSize
= dwCacheSize
;
469 pCache
->dwFilePos
= 0;
472 pCache
->szMask
= ALLOCMEM(char, strlen(szMask
) + 1);
473 strcpy(pCache
->szMask
, szMask
);
477 SFileReadFile(hListFile
, pCache
->Buffer
, pCache
->dwBuffSize
, &pCache
->dwBuffSize
, NULL
);
479 // Initialize the pointers
481 pCache
->pPos
= &pCache
->Buffer
[0];
482 pCache
->pEnd
= pCache
->pBegin
+ pCache
->dwBuffSize
;
486 // Read the (next) line
487 nLength
= ReadLine(pCache
, lpFindFileData
->cFileName
, sizeof(lpFindFileData
->cFileName
));
490 nError
= ERROR_NO_MORE_FILES
;
494 // If some mask entered, check it
495 if(CheckWildCard(lpFindFileData
->cFileName
, pCache
->szMask
))
501 if(nError
!= ERROR_SUCCESS
)
503 memset(lpFindFileData
, 0, sizeof(SFILE_FIND_DATA
));
504 SListFileFindClose((HANDLE
)pCache
);
507 SetLastError(nError
);
509 return (HANDLE
)pCache
;
512 // TODO: Test for archives > 4GB
513 BOOL
SListFileFindNextFile(HANDLE hFind
, SFILE_FIND_DATA
* lpFindFileData
)
515 TListFileCache
* pCache
= (TListFileCache
*)hFind
;
517 BOOL bResult
= FALSE
;
518 int nError
= ERROR_SUCCESS
;
522 // Read the (next) line
523 nLength
= ReadLine(pCache
, lpFindFileData
->cFileName
, sizeof(lpFindFileData
->cFileName
));
526 nError
= ERROR_NO_MORE_FILES
;
530 // If some mask entered, check it
531 if(CheckWildCard(lpFindFileData
->cFileName
, pCache
->szMask
))
538 if(nError
!= ERROR_SUCCESS
)
539 SetLastError(nError
);
543 // TODO: Test for archives > 4GB
544 BOOL
SListFileFindClose(HANDLE hFind
)
546 TListFileCache
* pCache
= (TListFileCache
*)hFind
;
550 if(pCache
->hFile
!= NULL
)
551 SFileCloseFile(pCache
->hFile
);
552 if(pCache
->szMask
!= NULL
)
553 FREEMEM(pCache
->szMask
);