PageRenderTime 66ms CodeModel.GetById 26ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 0ms

/StormLib/stormlib/SListFile.cpp

http://ghostcb.googlecode.com/
C++ | 599 lines | 417 code | 91 blank | 91 comment | 122 complexity | 8e3e36028e8bb17aba6628fc966b16e3 MD5 | raw file
  1/*****************************************************************************/
  2/* SListFile.cpp                          Copyright (c) Ladislav Zezula 2004 */
  3/*---------------------------------------------------------------------------*/
  4/* Description:                                                              */
  5/*---------------------------------------------------------------------------*/
  6/*   Date    Ver   Who  Comment                                              */
  7/* --------  ----  ---  -------                                              */
  8/* 12.06.04  1.00  Lad  The first version of SListFile.cpp                   */
  9/*****************************************************************************/
 10
 11#define __STORMLIB_SELF__
 12#include "StormLib.h"
 13#include "SCommon.h"
 14#include <assert.h>
 15
 16//-----------------------------------------------------------------------------
 17// Listfile entry structure
 18
 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)
 22
 23struct TListFileCache
 24{
 25    HANDLE  hFile;                      // Stormlib file handle
 26    char  * szMask;                     // File mask
 27    DWORD   dwFileSize;                 // Total size of the cached file
 28    DWORD   dwBuffSize;                 // File of the cache
 29    DWORD   dwFilePos;                  // Position of the cache in the file
 30    BYTE  * pBegin;                     // The begin of the listfile cache
 31    BYTE  * pPos;
 32    BYTE  * pEnd;                       // The last character in the file cache
 33
 34    BYTE Buffer[1];                     // Listfile cache itself
 35};
 36
 37//-----------------------------------------------------------------------------
 38// Local functions (cache)
 39
 40// Reloads the cache. Returns number of characters
 41// that has been loaded into the cache.
 42static int ReloadCache(TListFileCache * pCache)
 43{
 44    // Check if there is enough characters in the cache
 45    // If not, we have to reload the next block
 46    if(pCache->pPos >= pCache->pEnd)
 47    {
 48        // If the cache is already at the end, do nothing more
 49        if((pCache->dwFilePos + pCache->dwBuffSize) >= pCache->dwFileSize)
 50            return 0;
 51
 52        pCache->dwFilePos += pCache->dwBuffSize;
 53        SFileReadFile(pCache->hFile, pCache->Buffer, pCache->dwBuffSize, &pCache->dwBuffSize, NULL);
 54        if(pCache->dwBuffSize == 0)
 55            return 0;
 56
 57        // Set the buffer pointers
 58        pCache->pBegin =
 59        pCache->pPos = &pCache->Buffer[0];
 60        pCache->pEnd = pCache->pBegin + pCache->dwBuffSize;
 61    }
 62
 63    return pCache->dwBuffSize;
 64}
 65
 66static size_t ReadLine(TListFileCache * pCache, char * szLine, int nMaxChars)
 67{
 68    char * szLineBegin = szLine;
 69    char * szLineEnd = szLine + nMaxChars - 1;
 70    
 71__BeginLoading:
 72
 73    // Skip newlines, spaces, tabs and another non-printable stuff
 74    while(pCache->pPos < pCache->pEnd && *pCache->pPos <= 0x20)
 75        pCache->pPos++;
 76
 77    // Copy the remaining characters
 78    while(pCache->pPos < pCache->pEnd && szLine < szLineEnd)
 79    {
 80        // If we have found a newline, stop loading
 81        if(*pCache->pPos == 0x0D || *pCache->pPos == 0x0A)
 82            break;
 83
 84        *szLine++ = *pCache->pPos++;
 85    }
 86
 87    // If we now need to reload the cache, do it
 88    if(pCache->pPos == pCache->pEnd)
 89    {
 90        if(ReloadCache(pCache) > 0)
 91            goto __BeginLoading;
 92    }
 93
 94    *szLine = 0;
 95    return (szLine - szLineBegin);
 96}
 97
 98//-----------------------------------------------------------------------------
 99// Local functions (listfile nodes)
100
101// This function creates the name for the listfile.
102// the file will be created under unique name in the temporary directory
103static void GetListFileName(TMPQArchive * /* ha */, char * szListFile)
104{
105    char szTemp[MAX_PATH];
106
107    // Create temporary file name int TEMP directory
108    GetTempPath(sizeof(szTemp)-1, szTemp);
109    GetTempFileName(szTemp, LISTFILE_NAME, 0, szListFile);
110}
111
112// Creates new listfile. The listfile is an array of TListFileNode
113// structures. The size of the array is the same like the hash table size,
114// the ordering is the same too (listfile item index is the same like
115// the index in the MPQ hash table)
116
117int SListFileCreateListFile(TMPQArchive * ha)
118{
119    DWORD dwItems = ha->pHeader->dwHashTableSize;
120
121    // The listfile should be NULL now
122    assert(ha->pListFile == NULL);
123
124    ha->pListFile = ALLOCMEM(TFileNode *, dwItems);
125    if(ha->pListFile == NULL)
126        return ERROR_NOT_ENOUGH_MEMORY;
127
128    memset(ha->pListFile, 0xFF, dwItems * sizeof(TFileNode *));
129    return ERROR_SUCCESS;
130}
131
132// Adds a name into the list of all names. For each locale in the MPQ,
133// one entry will be created
134// If the file name is already there, does nothing.
135int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFileName)
136{
137    TFileNode * pNode   = NULL;
138    TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
139    TMPQHash * pHash0   = GetHashEntry(ha, szFileName);
140    TMPQHash * pHash    = pHash0;
141    DWORD dwHashIndex = 0;
142    size_t nLength;                     // File name lentgth
143    DWORD dwName1;
144    DWORD dwName2;
145
146    // If the file does not exist within the MPQ, do nothing
147    if(pHash == NULL)
148        return ERROR_SUCCESS;
149
150    // Remember the name
151    dwName1 = pHash->dwName1;
152    dwName2 = pHash->dwName2;
153
154    // Pass all entries in the hash table
155    while(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->dwBlockIndex != HASH_ENTRY_FREE)
156    {
157        // There may be an entry deleted amongst various language versions
158        if(pHash->dwBlockIndex != HASH_ENTRY_DELETED)
159        {
160            // Create the lang version, if none
161            dwHashIndex = (DWORD)(pHash - ha->pHashTable);
162            if((DWORD_PTR)ha->pListFile[dwHashIndex] >= LISTFILE_ENTRY_DELETED)
163            {
164                // Create the listfile node and insert it into the listfile table
165                // If the name node already exists there, we just increment number of references
166                if(pNode == NULL)
167                {
168                    nLength = strlen(szFileName);
169                    pNode = (TFileNode *)ALLOCMEM(char, sizeof(TFileNode) + nLength);
170                    pNode->dwRefCount = 1;
171                    pNode->nLength = nLength;
172                    strcpy(pNode->szFileName, szFileName);
173                    ha->pListFile[dwHashIndex] = pNode;
174                }
175                else
176                {
177                    pNode->dwRefCount++;
178                    ha->pListFile[dwHashIndex] = pNode;
179                }
180            }
181        }
182
183        // Move to the next hash entry
184        if(++pHash >= pHashEnd)
185            pHash = ha->pHashTable;
186        if(pHash == pHash0)
187            break;
188    }
189    return ERROR_SUCCESS;
190}
191
192// Adds a filename into the listfile. If the file name is already there,
193// does nothing.
194int SListFileCreateNode(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
195{
196    TFileNode * pNode = NULL;
197    TMPQHash * pHash0 = GetHashEntry(ha, szFileName);
198    TMPQHash * pHash1 = GetHashEntryEx(ha, szFileName, lcLocale);
199    DWORD dwHashIndex0 = 0;
200    DWORD dwHashIndex1 = 0;
201    size_t nLength;                     // File name lentgth
202
203    // If the file does not exist within the MPQ, do nothing
204    if(pHash1 == NULL || pHash1->dwBlockIndex >= HASH_ENTRY_DELETED)
205        return ERROR_SUCCESS;
206
207    // If the locale-cpecific listfile entry already exists, do nothing
208    dwHashIndex0 = (DWORD)(pHash0 - ha->pHashTable);
209    dwHashIndex1 = (DWORD)(pHash1 - ha->pHashTable);
210    if((DWORD_PTR)ha->pListFile[dwHashIndex1] < LISTFILE_ENTRY_DELETED)
211        return ERROR_SUCCESS;
212
213    // Does the neutral table entry exist ?
214    if((DWORD_PTR)ha->pListFile[dwHashIndex0] < LISTFILE_ENTRY_DELETED)
215        pNode = ha->pListFile[dwHashIndex0];
216
217    // If no node yet, we have to create new one
218    if(pNode == NULL)
219    {
220        nLength = strlen(szFileName);
221        pNode = (TFileNode *)ALLOCMEM(char, sizeof(TFileNode) + nLength);
222        pNode->dwRefCount = 1;
223        pNode->nLength = nLength;
224        strcpy(pNode->szFileName, szFileName);
225        ha->pListFile[dwHashIndex0] = pNode;
226    }
227
228    // Also insert the node in the locale-specific entry
229    if(dwHashIndex1 != dwHashIndex0)
230    {
231        pNode->dwRefCount++;
232        ha->pListFile[dwHashIndex1] = pNode;
233    }
234
235    return ERROR_SUCCESS;
236}
237
238// Removes a filename from the listfile.
239// If the name is not there, does nothing
240int SListFileRemoveNode(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
241{
242    TFileNode * pNode = NULL;
243    TMPQHash * pHash = GetHashEntryEx(ha, szFileName, lcLocale);
244    size_t nHashIndex = 0;
245
246    if(pHash != NULL)
247    {
248        nHashIndex = pHash - ha->pHashTable;
249        pNode = ha->pListFile[nHashIndex];
250        ha->pListFile[nHashIndex] = (TFileNode *)LISTFILE_ENTRY_DELETED;
251
252        // Free the node
253        pNode->dwRefCount--;
254        if(pNode->dwRefCount == 0)
255            FREEMEM(pNode);
256    }
257    return ERROR_SUCCESS;
258}
259
260void SListFileFreeListFile(TMPQArchive * ha)
261{
262    if(ha->pListFile != NULL)
263    {
264        for(DWORD i = 0; i < ha->pHeader->dwHashTableSize; i++)
265        {
266            TFileNode * pNode = ha->pListFile[i];
267
268            if((DWORD_PTR)pNode < LISTFILE_ENTRY_DELETED)
269            {
270                ha->pListFile[i] = (TFileNode *)LISTFILE_ENTRY_FREE;
271                pNode->dwRefCount--;
272
273                if(pNode->dwRefCount == 0)
274                    FREEMEM(pNode);
275            }
276        }
277
278        FREEMEM(ha->pListFile);
279        ha->pListFile = NULL;
280    }
281}
282
283// Saves the whole listfile into the MPQ.
284int SListFileSaveToMpq(TMPQArchive * ha)
285{
286    TFileNode * pNode = NULL;
287    TMPQHash * pHashEnd = NULL;
288    TMPQHash * pHash0 = NULL;
289    TMPQHash * pHash = NULL;
290    HANDLE hFile = INVALID_HANDLE_VALUE;
291    char   szListFile[MAX_PATH];
292    char   szBuffer[MAX_PATH+4];
293    DWORD dwTransferred;
294    size_t nLength = 0;
295    DWORD dwName1 = 0;
296    DWORD dwName2 = 0;
297    LCID lcSave = lcLocale;
298    int  nError = ERROR_SUCCESS;
299
300    // If no listfile, do nothing
301    if(ha->pListFile == NULL)
302        return ERROR_SUCCESS;
303
304    // Create the local listfile
305    if(nError == ERROR_SUCCESS)
306    {
307        GetListFileName(ha, szListFile);
308        hFile = CreateFile(szListFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
309        if(hFile == INVALID_HANDLE_VALUE)
310            nError = GetLastError();
311    }
312
313    // Find the hash entry corresponding to listfile
314    pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
315    pHash0 = pHash = GetHashEntry(ha, 0);
316    if(pHash == NULL)
317        pHash0 = pHash = ha->pHashTable;
318
319    // Save the file
320    if(nError == ERROR_SUCCESS)
321    {
322        for(;;)
323        {
324            if(pHash->dwName1 != dwName1 && pHash->dwName2 != dwName2 && pHash->dwBlockIndex < HASH_ENTRY_DELETED)
325            {
326                dwName1 = pHash->dwName1;
327                dwName2 = pHash->dwName2;
328                pNode = ha->pListFile[pHash - ha->pHashTable];
329
330                if((DWORD_PTR)pNode < LISTFILE_ENTRY_DELETED)
331                {
332                    memcpy(szBuffer, pNode->szFileName, pNode->nLength);
333                    szBuffer[pNode->nLength + 0] = 0x0D;
334                    szBuffer[pNode->nLength + 1] = 0x0A;
335                    WriteFile(hFile, szBuffer, (DWORD)(pNode->nLength + 2), &dwTransferred, NULL);
336                }
337            }
338
339            if(++pHash >= pHashEnd)
340                pHash = ha->pHashTable;
341            if(pHash == pHash0)
342                break;
343        }
344
345        // Write the listfile name (if not already there)
346        if(GetHashEntry(ha, LISTFILE_NAME) == NULL)
347        {
348            nLength = strlen(LISTFILE_NAME);
349            memcpy(szBuffer, LISTFILE_NAME, nLength);
350            szBuffer[nLength + 0] = 0x0D;
351            szBuffer[nLength + 1] = 0x0A;
352            WriteFile(hFile, szBuffer, (DWORD)(nLength + 2), &dwTransferred, NULL);
353        }                     
354        
355        // Add the listfile into the archive.
356        SFileSetLocale(LANG_NEUTRAL);
357        nError = AddFileToArchive(ha,
358                                  hFile,
359                                  LISTFILE_NAME,
360                                  MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING,
361                                  0,
362                                  SFILE_TYPE_DATA,
363                                  NULL);
364        lcLocale = lcSave;
365    }
366
367    // Close the temporary file and delete it.
368    // There is no FILE_FLAG_DELETE_ON_CLOSE on LINUX.
369    if(hFile != INVALID_HANDLE_VALUE)
370        CloseHandle(hFile);
371    DeleteFile(szListFile);
372
373    return nError;
374}
375
376//-----------------------------------------------------------------------------
377// File functions
378
379// Adds a listfile into the MPQ archive.
380// Note that the function does not remove the 
381int WINAPI SFileAddListFile(HANDLE hMpq, const char * szListFile)
382{
383    TListFileCache * pCache = NULL;
384    TMPQArchive * ha = (TMPQArchive *)hMpq;
385    HANDLE hListFile = NULL;
386    char  szFileName[MAX_PATH + 1];
387    DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
388    DWORD dwCacheSize = 0;
389    DWORD dwFileSize = 0;
390    size_t nLength = 0;
391    int nError = ERROR_SUCCESS;
392
393    // If the szListFile is NULL, it means we have to open internal listfile
394    if(szListFile == NULL)
395    {
396        szListFile = LISTFILE_NAME;
397        dwSearchScope = SFILE_OPEN_FROM_MPQ;
398    }
399
400    // Open the local/internal listfile
401    if(nError == ERROR_SUCCESS)
402    {
403        if(!SFileOpenFileEx((HANDLE)ha, szListFile, dwSearchScope, &hListFile))
404            nError = GetLastError();
405    }
406
407    if(nError == ERROR_SUCCESS)
408    {
409        dwCacheSize = 
410        dwFileSize = SFileGetFileSize(hListFile, NULL);
411
412        // Try to allocate memory for the complete file. If it fails,
413        // load the part of the file
414        pCache = (TListFileCache *)ALLOCMEM(char, (sizeof(TListFileCache) + dwCacheSize));
415        if(pCache == NULL)
416        {
417            dwCacheSize = LISTFILE_CACHE_SIZE;
418            pCache = (TListFileCache *)ALLOCMEM(char, sizeof(TListFileCache) + dwCacheSize);
419        }
420
421        if(pCache == NULL)
422            nError = ERROR_NOT_ENOUGH_MEMORY;
423    }
424
425    if(nError == ERROR_SUCCESS)
426    {
427        // Initialize the file cache
428        memset(pCache, 0, sizeof(TListFileCache));
429        pCache->hFile      = hListFile;
430        pCache->dwFileSize = dwFileSize;
431        pCache->dwBuffSize = dwCacheSize;
432        pCache->dwFilePos  = 0;
433
434        // Fill the cache
435        SFileReadFile(hListFile, pCache->Buffer, pCache->dwBuffSize, &pCache->dwBuffSize, NULL);
436
437        // Initialize the pointers
438        pCache->pBegin =
439        pCache->pPos = &pCache->Buffer[0];
440        pCache->pEnd = pCache->pBegin + pCache->dwBuffSize;
441
442        // Load the node list. Add the node for every locale in the archive
443        while((nLength = ReadLine(pCache, szFileName, sizeof(szFileName) - 1)) > 0)
444            SListFileCreateNodeForAllLocales(ha, szFileName);
445    }
446
447    // Cleanup & exit
448    if(pCache != NULL)
449        SListFileFindClose((HANDLE)pCache);
450    return nError;
451}
452
453//-----------------------------------------------------------------------------
454// Passing through the listfile
455
456HANDLE SListFileFindFirstFile(HANDLE hMpq, const char * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
457{
458    TListFileCache * pCache = NULL;
459    TMPQArchive * ha = (TMPQArchive *)hMpq;
460    HANDLE hListFile = NULL;
461    DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
462    DWORD dwCacheSize = 0;
463    DWORD dwFileSize = 0;
464    size_t nLength = 0;
465    int nError = ERROR_SUCCESS;
466
467    // Initialize the structure with zeros
468    memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
469
470    // If the szListFile is NULL, it means we have to open internal listfile
471    if(szListFile == NULL)
472    {
473        szListFile = LISTFILE_NAME;
474        dwSearchScope = SFILE_OPEN_FROM_MPQ;
475    }
476
477    // Open the local/internal listfile
478    if(nError == ERROR_SUCCESS)
479    {
480        if(!SFileOpenFileEx((HANDLE)ha, szListFile, dwSearchScope, &hListFile))
481            nError = GetLastError();
482    }
483
484    if(nError == ERROR_SUCCESS)
485    {
486        dwCacheSize = 
487        dwFileSize = SFileGetFileSize(hListFile, NULL);
488
489        // Try to allocate memory for the complete file. If it fails,
490        // load the part of the file
491        pCache = (TListFileCache *)ALLOCMEM(char, sizeof(TListFileCache) + dwCacheSize);
492        if(pCache == NULL)
493        {
494            dwCacheSize = LISTFILE_CACHE_SIZE;
495            pCache = (TListFileCache *)ALLOCMEM(char, sizeof(TListFileCache) + dwCacheSize);
496        }
497
498        if(pCache == NULL)
499            nError = ERROR_NOT_ENOUGH_MEMORY;
500    }
501
502    if(nError == ERROR_SUCCESS)
503    {
504        // Initialize the file cache
505        memset(pCache, 0, sizeof(TListFileCache));
506        pCache->hFile      = hListFile;
507        pCache->dwFileSize = dwFileSize;
508        pCache->dwBuffSize = dwCacheSize;
509        pCache->dwFilePos  = 0;
510        if(szMask != NULL)
511        {
512            pCache->szMask = ALLOCMEM(char, strlen(szMask) + 1);
513            strcpy(pCache->szMask, szMask);
514        }
515
516        // Fill the cache
517        SFileReadFile(hListFile, pCache->Buffer, pCache->dwBuffSize, &pCache->dwBuffSize, NULL);
518
519        // Initialize the pointers
520        pCache->pBegin =
521        pCache->pPos = &pCache->Buffer[0];
522        pCache->pEnd = pCache->pBegin + pCache->dwBuffSize;
523
524        for(;;)
525        {
526            // Read the (next) line
527            nLength = ReadLine(pCache, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName));
528            if(nLength == 0)
529            {
530                nError = ERROR_NO_MORE_FILES;
531                break;
532            }
533
534            // If some mask entered, check it
535            if(CheckWildCard(lpFindFileData->cFileName, pCache->szMask))
536                break;                
537        }
538    }
539
540    // Cleanup & exit
541    if(nError != ERROR_SUCCESS)
542    {
543        memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
544        SListFileFindClose((HANDLE)pCache);
545        pCache = NULL;
546
547        SetLastError(nError);
548    }
549    return (HANDLE)pCache;
550}
551
552BOOL SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
553{
554    TListFileCache * pCache = (TListFileCache *)hFind;
555    size_t nLength;
556    BOOL bResult = FALSE;
557    int nError = ERROR_SUCCESS;
558
559    for(;;)
560    {
561        // Read the (next) line
562        nLength = ReadLine(pCache, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName));
563        if(nLength == 0)
564        {
565            nError = ERROR_NO_MORE_FILES;
566            break;
567        }
568
569        // If some mask entered, check it
570        if(CheckWildCard(lpFindFileData->cFileName, pCache->szMask))
571        {
572            bResult = TRUE;
573            break;
574        }
575    }
576
577    if(nError != ERROR_SUCCESS)
578        SetLastError(nError);
579    return bResult;
580}
581
582BOOL SListFileFindClose(HANDLE hFind)
583{
584    TListFileCache * pCache = (TListFileCache *)hFind;
585
586    if(pCache != NULL)
587    {
588        if(pCache->hFile != NULL)
589            SFileCloseFile(pCache->hFile);
590        if(pCache->szMask != NULL)
591            FREEMEM(pCache->szMask);
592
593        FREEMEM(pCache);
594        return TRUE;
595    }
596
597    return FALSE;
598}
599