summaryrefslogtreecommitdiff
path: root/StormLib/src/SFileListFile.cpp
diff options
context:
space:
mode:
authorNicholas Estelami <NEstelami@users.noreply.github.com>2022-06-01 16:05:05 -0400
committerNicholas Estelami <NEstelami@users.noreply.github.com>2022-06-16 20:35:52 -0400
commitb3e299dbdee829c12ada556a1c3aa1f109a0e05b (patch)
treea6e647da673ba7df9389cb1f382a11fc8350bc23 /StormLib/src/SFileListFile.cpp
parent941c19f2c33a0d1af75a10a64fe0905387d1e440 (diff)
Replaced StormLib .lib files with source code.
Some modifications to handle backslashes and forward slashes, along with some optimizations to speed up OTR generation.
Diffstat (limited to 'StormLib/src/SFileListFile.cpp')
-rw-r--r--StormLib/src/SFileListFile.cpp686
1 files changed, 686 insertions, 0 deletions
diff --git a/StormLib/src/SFileListFile.cpp b/StormLib/src/SFileListFile.cpp
new file mode 100644
index 000000000..c36f1812c
--- /dev/null
+++ b/StormLib/src/SFileListFile.cpp
@@ -0,0 +1,686 @@
+/*****************************************************************************/
+/* SListFile.cpp Copyright (c) Ladislav Zezula 2004 */
+/*---------------------------------------------------------------------------*/
+/* Description: */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 12.06.04 1.00 Lad The first version of SListFile.cpp */
+/*****************************************************************************/
+
+#define __STORMLIB_SELF__
+#include "StormLib.h"
+#include "StormCommon.h"
+#include <assert.h>
+
+//-----------------------------------------------------------------------------
+// Listfile entry structure
+
+#define CACHE_BUFFER_SIZE 0x1000 // Size of the cache buffer
+#define MAX_LISTFILE_SIZE 0x8000000 // Maximum accepted listfile size is 128 MB
+
+union TListFileHandle
+{
+ TFileStream * pStream; // Opened local file
+ HANDLE hFile; // Opened MPQ file
+};
+
+struct TListFileCache
+{
+ char * szWildCard; // Self-relative pointer to file mask
+ LPBYTE pBegin; // The begin of the listfile cache
+ LPBYTE pPos; // Current position in the cache
+ LPBYTE pEnd; // The last character in the file cache
+ DWORD dwFlags; // Flags from TMPQArchive
+
+// char szWildCard[wildcard_length]; // Followed by the name mask (if any)
+// char szListFile[listfile_length]; // Followed by the listfile (if any)
+};
+
+typedef bool (*LOAD_LISTFILE)(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead);
+
+//-----------------------------------------------------------------------------
+// Local functions (cache)
+
+// In SFileFindFile.cll
+bool SFileCheckWildCard(const char * szString, const char * szWildCard);
+
+static char * CopyListLine(char * szListLine, const char * szFileName)
+{
+ // Copy the string
+ while (szFileName[0] != 0)
+ {
+ *szListLine++ = *szFileName++;
+ }
+ // Append the end-of-line
+ *szListLine++ = 0x0D;
+ *szListLine++ = 0x0A;
+ return szListLine;
+}
+
+static bool LoadListFile_Stream(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead)
+{
+ ULONGLONG ByteOffset = 0;
+ bool bResult;
+
+ bResult = FileStream_Read(pHandle->pStream, &ByteOffset, pvBuffer, cbBuffer);
+ if(bResult)
+ *pdwBytesRead = cbBuffer;
+ return bResult;
+}
+
+static bool LoadListFile_MPQ(TListFileHandle * pHandle, void * pvBuffer, DWORD cbBuffer, LPDWORD pdwBytesRead)
+{
+ return SFileReadFile(pHandle->hFile, pvBuffer, cbBuffer, pdwBytesRead, NULL);
+}
+
+static bool FreeListFileCache(TListFileCache * pCache)
+{
+ // Valid parameter check
+ if(pCache != NULL)
+ STORM_FREE(pCache);
+ return true;
+}
+
+static TListFileCache * CreateListFileCache(
+ LOAD_LISTFILE PfnLoadFile,
+ TListFileHandle * pHandle,
+ const char * szWildCard,
+ DWORD dwFileSize,
+ DWORD dwMaxSize,
+ DWORD dwFlags)
+{
+ TListFileCache * pCache = NULL;
+ size_t cchWildCardAligned = 0;
+ size_t cchWildCard = 0;
+ DWORD dwBytesRead = 0;
+
+ // Get the amount of bytes that need to be allocated
+ if(dwFileSize == 0 || dwFileSize > dwMaxSize)
+ return NULL;
+
+ // Append buffer for name mask, if any
+ if(szWildCard != NULL)
+ {
+ cchWildCard = strlen(szWildCard) + 1;
+ cchWildCardAligned = (cchWildCard + 3) & 0xFFFFFFFC;
+ }
+
+ // Allocate cache for one file block
+ pCache = (TListFileCache *)STORM_ALLOC(BYTE, sizeof(TListFileCache) + cchWildCardAligned + dwFileSize + 1);
+ if(pCache != NULL)
+ {
+ // Clear the entire structure
+ memset(pCache, 0, sizeof(TListFileCache) + cchWildCard);
+ pCache->dwFlags = dwFlags;
+
+ // Shall we copy the mask?
+ if(cchWildCard != 0)
+ {
+ pCache->szWildCard = (char *)(pCache + 1);
+ memcpy(pCache->szWildCard, szWildCard, cchWildCard);
+ }
+
+ // Fill-in the rest of the cache pointers
+ pCache->pBegin = (LPBYTE)(pCache + 1) + cchWildCardAligned;
+
+ // Load the entire listfile to the cache
+ PfnLoadFile(pHandle, pCache->pBegin, dwFileSize, &dwBytesRead);
+ if(dwBytesRead != 0)
+ {
+ // Allocate pointers
+ pCache->pPos = pCache->pBegin;
+ pCache->pEnd = pCache->pBegin + dwBytesRead;
+ }
+ else
+ {
+ FreeListFileCache(pCache);
+ pCache = NULL;
+ }
+ }
+
+ // Return the cache
+ return pCache;
+}
+
+static TListFileCache * CreateListFileCache(
+ HANDLE hMpq,
+ const TCHAR * szListFile,
+ const char * szWildCard,
+ DWORD dwMaxSize,
+ DWORD dwFlags)
+{
+ TListFileCache * pCache = NULL;
+ TListFileHandle ListHandle = {NULL};
+
+ // Put default value to dwMaxSize
+ if(dwMaxSize == 0)
+ dwMaxSize = MAX_LISTFILE_SIZE;
+
+ // Internal listfile: hMPQ must be non NULL and szListFile must be NULL.
+ // We load the MPQ::(listfile) file
+ if(hMpq != NULL && szListFile == NULL)
+ {
+ DWORD dwFileSize = 0;
+
+ // Open the file from the MPQ
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &ListHandle.hFile))
+ {
+ // Get the file size and create the listfile cache
+ dwFileSize = SFileGetFileSize(ListHandle.hFile, NULL);
+ pCache = CreateListFileCache(LoadListFile_MPQ, &ListHandle, szWildCard, dwFileSize, dwMaxSize, dwFlags);
+
+ // Close the MPQ file
+ SFileCloseFile(ListHandle.hFile);
+ }
+
+ // Return the loaded cache
+ return pCache;
+ }
+
+ // External listfile: hMpq must be NULL and szListFile must be non-NULL.
+ // We load the file using TFileStream
+ if(hMpq == NULL && szListFile != NULL)
+ {
+ ULONGLONG FileSize = 0;
+
+ // Open the local file
+ ListHandle.pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY);
+ if(ListHandle.pStream != NULL)
+ {
+ // Verify the file size
+ FileStream_GetSize(ListHandle.pStream, &FileSize);
+ if(0 < FileSize && FileSize < dwMaxSize)
+ {
+ pCache = CreateListFileCache(LoadListFile_Stream, &ListHandle, szWildCard, (DWORD)FileSize, dwMaxSize, dwFlags);
+ }
+
+ // Close the stream
+ FileStream_Close(ListHandle.pStream);
+ }
+
+ // Return the loaded cache
+ return pCache;
+ }
+
+ // This combination should never happen
+ SetLastError(ERROR_INVALID_PARAMETER);
+ assert(false);
+ return NULL;
+}
+
+#ifdef _DEBUG
+/*
+TMPQNameCache * CreateNameCache(HANDLE hListFile, const char * szSearchMask)
+{
+ TMPQNameCache * pNameCache;
+ char * szCachePointer;
+ size_t cbToAllocate;
+ size_t nMaskLength = 1;
+ DWORD dwBytesRead = 0;
+ DWORD dwFileSize;
+
+ // Get the size of the listfile. Ignore zero or too long ones
+ dwFileSize = SFileGetFileSize(hListFile, NULL);
+ if(dwFileSize == 0 || dwFileSize > MAX_LISTFILE_SIZE)
+ return NULL;
+
+ // Get the length of the search mask
+ if(szSearchMask == NULL)
+ szSearchMask = "*";
+ nMaskLength = strlen(szSearchMask) + 1;
+
+ // Allocate the name cache
+ cbToAllocate = sizeof(TMPQNameCache) + nMaskLength + dwFileSize + 1;
+ pNameCache = (TMPQNameCache *)STORM_ALLOC(BYTE, cbToAllocate);
+ if(pNameCache != NULL)
+ {
+ // Initialize the name cache
+ memset(pNameCache, 0, sizeof(TMPQNameCache));
+ pNameCache->TotalCacheSize = (DWORD)(nMaskLength + dwFileSize + 1);
+ szCachePointer = (char *)(pNameCache + 1);
+
+ // Copy the search mask, if any
+ memcpy(szCachePointer, szSearchMask, nMaskLength);
+ pNameCache->FirstNameOffset = (DWORD)nMaskLength;
+ pNameCache->FreeSpaceOffset = (DWORD)nMaskLength;
+
+ // Read the listfile itself
+ SFileSetFilePointer(hListFile, 0, NULL, FILE_BEGIN);
+ SFileReadFile(hListFile, szCachePointer + nMaskLength, dwFileSize, &dwBytesRead, NULL);
+
+ // If nothing has been read from the listfile, clear the cache
+ if(dwBytesRead == 0)
+ {
+ STORM_FREE(pNameCache);
+ return NULL;
+ }
+
+ // Move the free space offset
+ pNameCache->FreeSpaceOffset = pNameCache->FirstNameOffset + dwBytesRead + 1;
+ szCachePointer[nMaskLength + dwBytesRead] = 0;
+ }
+
+ return pNameCache;
+}
+
+static void FreeNameCache(TMPQNameCache * pNameCache)
+{
+ if(pNameCache != NULL)
+ STORM_FREE(pNameCache);
+ pNameCache = NULL;
+}
+*/
+#endif // _DEBUG
+
+static char * ReadListFileLine(TListFileCache * pCache, size_t * PtrLength)
+{
+ LPBYTE pbLineBegin;
+ LPBYTE pbLineEnd;
+
+ // Skip newlines. Keep spaces and tabs, as they can be a legal part of the file name
+ while(pCache->pPos < pCache->pEnd && (pCache->pPos[0] == 0x0A || pCache->pPos[0] == 0x0D))
+ pCache->pPos++;
+
+ // Set the line begin and end
+ if(pCache->pPos >= pCache->pEnd)
+ return NULL;
+ pbLineBegin = pbLineEnd = pCache->pPos;
+
+ // Find the end of the line
+ while(pCache->pPos < pCache->pEnd && pCache->pPos[0] != 0x0A && pCache->pPos[0] != 0x0D)
+ pCache->pPos++;
+
+ // Remember the end of the line
+ pbLineEnd = pCache->pPos++;
+ pbLineEnd[0] = 0;
+
+ // Give the line to the caller
+ if(PtrLength != NULL)
+ PtrLength[0] = (size_t)(pbLineEnd - pbLineBegin);
+ return (char *)pbLineBegin;
+}
+
+static int STORMLIB_CDECL CompareFileNodes(const void * p1, const void * p2)
+{
+ char * szFileName1 = *(char **)p1;
+ char * szFileName2 = *(char **)p2;
+
+ return _stricmp(szFileName1, szFileName2);
+}
+
+static LPBYTE CreateListFile(TMPQArchive * ha, DWORD * pcbListFile)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ char ** SortTable = NULL;
+ char * szListFile = NULL;
+ char * szListLine;
+ size_t nFileNodes = 0;
+ size_t cbListFile = 0;
+ size_t nIndex0;
+ size_t nIndex1;
+
+ // Allocate the table for sorting listfile
+ SortTable = STORM_ALLOC(char*, ha->dwFileTableSize);
+ if(SortTable == NULL)
+ return NULL;
+
+ // Construct the sort table
+ // Note: in MPQs with multiple locale versions of the same file,
+ // this code causes adding multiple listfile entries.
+ // They will get removed after the listfile sorting
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Only take existing items
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL)
+ {
+ // Ignore pseudo-names and internal names
+ if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName))
+ {
+ for (int i = 0; i < strlen(pFileEntry->szFileName); i++)
+ {
+ if (pFileEntry->szFileName[i] == '/')
+ pFileEntry->szFileName[i] = '\\';
+ }
+
+ SortTable[nFileNodes++] = pFileEntry->szFileName;
+ }
+ }
+ }
+
+ // Remove duplicities
+ if(nFileNodes > 0)
+ {
+ // Sort the table
+ qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes);
+
+ // Count the 0-th item
+ cbListFile += strlen(SortTable[0]) + 2;
+
+ // Walk through the items and only use the ones that are not duplicated
+ for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++)
+ {
+ // If the next file node is different, we will include it to the result listfile
+ if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0)
+ {
+ cbListFile += strlen(SortTable[nIndex1]) + 2;
+ nIndex0 = nIndex1;
+ }
+ }
+
+ // Now allocate buffer for the entire listfile
+ szListFile = szListLine = STORM_ALLOC(char, cbListFile + 1);
+ if(szListFile != NULL)
+ {
+ // Copy the 0-th item
+ szListLine = CopyListLine(szListLine, SortTable[0]);
+
+ // Walk through the items and only use the ones that are not duplicated
+ for(nIndex0 = 0, nIndex1 = 1; nIndex1 < nFileNodes; nIndex1++)
+ {
+ // If the next file node is different, we will include it to the result listfile
+ if(_stricmp(SortTable[nIndex1], SortTable[nIndex0]) != 0)
+ {
+ // Copy the listfile line
+ szListLine = CopyListLine(szListLine, SortTable[nIndex1]);
+ nIndex0 = nIndex1;
+ }
+ }
+
+ // Sanity check - does the size match?
+ assert((size_t)(szListLine - szListFile) == cbListFile);
+ }
+ }
+ else
+ {
+ szListFile = STORM_ALLOC(char, 1);
+ cbListFile = 0;
+ }
+
+ // Free the sort table
+ STORM_FREE(SortTable);
+
+ // Give away the listfile
+ if(pcbListFile != NULL)
+ *pcbListFile = (DWORD)cbListFile;
+ return (LPBYTE)szListFile;
+}
+
+//-----------------------------------------------------------------------------
+// Local functions (listfile nodes)
+
+// Adds a name into the list of all names. For each locale in the MPQ,
+// one entry will be created
+// If the file name is already there, does nothing.
+static DWORD SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFileName)
+{
+ TFileEntry * pFileEntry;
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+
+ // If we have HET table, use that one
+ if(ha->pHetTable != NULL)
+ {
+ pFileEntry = GetFileEntryLocale(ha, szFileName, 0);
+ if(pFileEntry != NULL)
+ {
+ // Allocate file name for the file entry
+ AllocateFileName(ha, pFileEntry, szFileName);
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ // If we have hash table, we use it
+ if(ha->pHashTable != NULL)
+ {
+ // Go while we found something
+ pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
+ while(pHash != NULL)
+ {
+ // Allocate file name for the file entry
+ AllocateFileName(ha, ha->pFileTable + MPQ_BLOCK_INDEX(pHash), szFileName);
+
+ // Now find the next language version of the file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+// Saves the whole listfile to the MPQ
+DWORD SListFileSaveToMpq(TMPQArchive * ha)
+{
+ TMPQFile * hf = NULL;
+ LPBYTE pbListFile;
+ DWORD cbListFile = 0;
+ DWORD dwErrCode = ERROR_SUCCESS;
+
+ // Only save the listfile if we should do so
+ if(ha->dwFileFlags1 != 0)
+ {
+ // At this point, we expect to have at least one reserved entry in the file table
+ assert(ha->dwFlags & MPQ_FLAG_LISTFILE_NEW);
+ assert(ha->dwReservedFiles > 0);
+
+ // Create the raw data that is to be written to (listfile)
+ // Note: Creating the raw data before the (listfile) has been created in the MPQ
+ // causes that the name of the listfile will not be included in the listfile itself.
+ // That is OK, because (listfile) in Blizzard MPQs does not contain it either.
+ pbListFile = CreateListFile(ha, &cbListFile);
+ if(pbListFile != NULL)
+ {
+ // Determine the real flags for (listfile)
+ if(ha->dwFileFlags1 == MPQ_FILE_DEFAULT_INTERNAL)
+ ha->dwFileFlags1 = GetDefaultSpecialFileFlags(cbListFile, ha->pHeader->wFormatVersion);
+
+ // Create the listfile in the MPQ
+ dwErrCode = SFileAddFile_Init(ha, LISTFILE_NAME,
+ 0,
+ cbListFile,
+ LANG_NEUTRAL,
+ ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Write the listfile raw data to it
+ if(dwErrCode == ERROR_SUCCESS)
+ {
+ // Write the content of the listfile to the MPQ
+ dwErrCode = SFileAddFile_Write(hf, pbListFile, cbListFile, MPQ_COMPRESSION_ZLIB);
+ SFileAddFile_Finish(hf);
+ }
+
+ // Clear the listfile flags
+ ha->dwFlags &= ~(MPQ_FLAG_LISTFILE_NEW | MPQ_FLAG_LISTFILE_NONE);
+ ha->dwReservedFiles--;
+
+ // Free the listfile buffer
+ STORM_FREE(pbListFile);
+ }
+ else
+ {
+ // If the (listfile) file would be empty, its OK
+ dwErrCode = (cbListFile == 0) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return dwErrCode;
+}
+
+static DWORD SFileAddArbitraryListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq,
+ const TCHAR * szListFile,
+ DWORD dwMaxSize)
+{
+ TListFileCache * pCache = NULL;
+
+ // Create the listfile cache for that file
+ pCache = CreateListFileCache(hMpq, szListFile, NULL, dwMaxSize, ha->dwFlags);
+ if(pCache != NULL)
+ {
+ char * szFileName;
+ size_t nLength = 0;
+
+ // Get the next line
+ while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL)
+ {
+ // Add the line to the MPQ
+ if(nLength != 0)
+ SListFileCreateNodeForAllLocales(ha, szFileName);
+ }
+
+ // Delete the cache
+ FreeListFileCache(pCache);
+ }
+
+ return (pCache != NULL) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
+}
+
+static DWORD SFileAddInternalListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq)
+{
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+ LCID lcSaveLocale = g_lcFileLocale;
+ DWORD dwMaxSize = MAX_LISTFILE_SIZE;
+ DWORD dwErrCode = ERROR_SUCCESS;
+
+ // If there is hash table, we need to support multiple listfiles
+ // with different locales (BrooDat.mpq)
+ if(ha->pHashTable != NULL)
+ {
+ // If the archive is a malformed map, ignore too large listfiles
+ if(ha->dwFlags & MPQ_FLAG_MALFORMED)
+ dwMaxSize = 0x40000;
+
+ pFirstHash = pHash = GetFirstHashEntry(ha, LISTFILE_NAME);
+ while(dwErrCode == ERROR_SUCCESS && pHash != NULL)
+ {
+ // Set the prefered locale to that from list file
+ SFileSetLocale(pHash->lcLocale);
+
+ // Add that listfile
+ dwErrCode = SFileAddArbitraryListFile(ha, hMpq, NULL, dwMaxSize);
+
+ // Move to the next hash
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+
+ // Restore the original locale
+ SFileSetLocale(lcSaveLocale);
+ }
+ else
+ {
+ // Add the single listfile
+ dwErrCode = SFileAddArbitraryListFile(ha, hMpq, NULL, dwMaxSize);
+ }
+
+ // Return the result of the operation
+ return dwErrCode;
+}
+
+static bool DoListFileSearch(TListFileCache * pCache, SFILE_FIND_DATA * lpFindFileData)
+{
+ // Check for the valid search handle
+ if(pCache != NULL)
+ {
+ char * szFileName;
+ size_t nLength = 0;
+
+ // Get the next line
+ while((szFileName = ReadListFileLine(pCache, &nLength)) != NULL)
+ {
+ // Check search mask
+ if(nLength != 0 && SFileCheckWildCard(szFileName, pCache->szWildCard))
+ {
+ if(nLength >= sizeof(lpFindFileData->cFileName))
+ nLength = sizeof(lpFindFileData->cFileName) - 1;
+
+ memcpy(lpFindFileData->cFileName, szFileName, nLength);
+ lpFindFileData->cFileName[nLength] = 0;
+ return true;
+ }
+ }
+ }
+
+ // No more files
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+ SetLastError(ERROR_NO_MORE_FILES);
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// File functions
+
+// Adds a listfile into the MPQ archive.
+DWORD WINAPI SFileAddListFile(HANDLE hMpq, const TCHAR * szListFile)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ DWORD dwErrCode = ERROR_SUCCESS;
+
+ // Add the listfile for each MPQ in the patch chain
+ while(ha != NULL)
+ {
+ if(szListFile != NULL)
+ dwErrCode = SFileAddArbitraryListFile(ha, NULL, szListFile, MAX_LISTFILE_SIZE);
+ else
+ dwErrCode = SFileAddInternalListFile(ha, hMpq);
+
+ // Also, add three special files to the listfile:
+ // (listfile) itself, (attributes) and (signature)
+ SListFileCreateNodeForAllLocales(ha, LISTFILE_NAME);
+ SListFileCreateNodeForAllLocales(ha, SIGNATURE_NAME);
+ SListFileCreateNodeForAllLocales(ha, ATTRIBUTES_NAME);
+
+ // Move to the next archive in the chain
+ ha = ha->haPatch;
+ }
+
+ return dwErrCode;
+}
+
+//-----------------------------------------------------------------------------
+// Enumerating files in listfile
+
+HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const TCHAR * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
+{
+ TListFileCache * pCache = NULL;
+
+ // Initialize the structure with zeros
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+
+ // Open the local/internal listfile
+ pCache = CreateListFileCache(hMpq, szListFile, szMask, 0, 0);
+ if(pCache != NULL)
+ {
+ if(!DoListFileSearch(pCache, lpFindFileData))
+ {
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+ SetLastError(ERROR_NO_MORE_FILES);
+ FreeListFileCache(pCache);
+ pCache = NULL;
+ }
+ }
+
+ // Return the listfile cache as handle
+ return (HANDLE)pCache;
+}
+
+bool WINAPI SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
+{
+ return DoListFileSearch((TListFileCache *)hFind, lpFindFileData);
+}
+
+bool WINAPI SListFileFindClose(HANDLE hFind)
+{
+ TListFileCache * pCache = (TListFileCache *)hFind;
+
+ return FreeListFileCache(pCache);
+}
+