aboutsummaryrefslogtreecommitdiff
path: root/src/SFileListFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/SFileListFile.cpp')
-rw-r--r--src/SFileListFile.cpp636
1 files changed, 636 insertions, 0 deletions
diff --git a/src/SFileListFile.cpp b/src/SFileListFile.cpp
new file mode 100644
index 0000000..2293403
--- /dev/null
+++ b/src/SFileListFile.cpp
@@ -0,0 +1,636 @@
+/*****************************************************************************/
+/* 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
+
+struct TListFileCache
+{
+ HANDLE hFile; // Stormlib file handle
+ char * szMask; // File mask
+ DWORD dwFileSize; // Total size of the cached file
+ DWORD dwFilePos; // Position of the cache in the file
+ BYTE * pBegin; // The begin of the listfile cache
+ BYTE * pPos;
+ BYTE * pEnd; // The last character in the file cache
+
+ BYTE Buffer[CACHE_BUFFER_SIZE]; // Listfile cache itself
+};
+
+//-----------------------------------------------------------------------------
+// Local functions (cache)
+
+static bool FreeListFileCache(TListFileCache * pCache)
+{
+ // Valid parameter check
+ if(pCache == NULL)
+ return false;
+
+ // Free all allocated buffers
+ if(pCache->hFile != NULL)
+ SFileCloseFile(pCache->hFile);
+ if(pCache->szMask != NULL)
+ STORM_FREE(pCache->szMask);
+ STORM_FREE(pCache);
+ return true;
+}
+
+static TListFileCache * CreateListFileCache(HANDLE hListFile, const char * szMask)
+{
+ TListFileCache * pCache = NULL;
+ DWORD dwBytesRead = 0;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate cache for one file block
+ pCache = (TListFileCache *)STORM_ALLOC(TListFileCache, 1);
+ if(pCache == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Clear the entire structure
+ if(nError == ERROR_SUCCESS)
+ {
+ memset(pCache, 0, sizeof(TListFileCache));
+ pCache->hFile = hListFile;
+
+ // Shall we allocate a mask?
+ if(szMask != NULL)
+ {
+ pCache->szMask = STORM_ALLOC(char, strlen(szMask) + 1);
+ if(pCache->szMask != NULL)
+ strcpy(pCache->szMask, szMask);
+ else
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Initialize the file cache
+ if(nError == ERROR_SUCCESS)
+ {
+ pCache->dwFileSize = SFileGetFileSize(pCache->hFile, NULL);
+
+ // Fill the cache
+ SFileReadFile(pCache->hFile, pCache->Buffer, CACHE_BUFFER_SIZE, &dwBytesRead, NULL);
+ if(dwBytesRead == 0)
+ nError = GetLastError();
+ }
+
+ // Allocate pointers
+ if(nError == ERROR_SUCCESS)
+ {
+ pCache->pBegin =
+ pCache->pPos = &pCache->Buffer[0];
+ pCache->pEnd = pCache->pBegin + dwBytesRead;
+ }
+ else
+ {
+ FreeListFileCache(pCache);
+ SetLastError(nError);
+ pCache = NULL;
+ }
+
+ // Return the cache
+ return pCache;
+}
+
+// Reloads the cache. Returns number of characters
+// that has been loaded into the cache.
+static DWORD ReloadListFileCache(TListFileCache * pCache)
+{
+ DWORD dwBytesToRead;
+ DWORD dwBytesRead = 0;
+
+ // Only do something if the cache is empty
+ if(pCache->pPos >= pCache->pEnd)
+ {
+// __TryReadBlock:
+
+ // Move the file position forward
+ pCache->dwFilePos += CACHE_BUFFER_SIZE;
+ if(pCache->dwFilePos >= pCache->dwFileSize)
+ return 0;
+
+ // Get the number of bytes remaining
+ dwBytesToRead = pCache->dwFileSize - pCache->dwFilePos;
+ if(dwBytesToRead > CACHE_BUFFER_SIZE)
+ dwBytesToRead = CACHE_BUFFER_SIZE;
+
+ // Load the next data chunk to the cache
+ SFileSetFilePointer(pCache->hFile, pCache->dwFilePos, NULL, FILE_BEGIN);
+ SFileReadFile(pCache->hFile, pCache->Buffer, CACHE_BUFFER_SIZE, &dwBytesRead, NULL);
+
+ // If we didn't read anything, it might mean that the block
+ // of the file is not available (in case of partial MPQs).
+ // We stop reading the file at this point, because the rest
+ // of the listfile is unreliable
+ if(dwBytesRead == 0)
+ return 0;
+
+ // Set the buffer pointers
+ pCache->pBegin =
+ pCache->pPos = &pCache->Buffer[0];
+ pCache->pEnd = pCache->pBegin + dwBytesRead;
+ }
+
+ return dwBytesRead;
+}
+
+static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, int nMaxChars)
+{
+ char * szLineBegin = szLine;
+ char * szLineEnd = szLine + nMaxChars - 1;
+ char * szExtraString = NULL;
+
+ // Skip newlines, spaces, tabs and another non-printable stuff
+ for(;;)
+ {
+ // If we need to reload the cache, do it
+ if(pCache->pPos == pCache->pEnd)
+ {
+ if(ReloadListFileCache(pCache) == 0)
+ break;
+ }
+
+ // If we found a non-whitespace character, stop
+ if(*pCache->pPos > 0x20)
+ break;
+
+ // Skip the character
+ pCache->pPos++;
+ }
+
+ // Copy the remaining characters
+ while(szLine < szLineEnd)
+ {
+ // If we need to reload the cache, do it now and resume copying
+ if(pCache->pPos == pCache->pEnd)
+ {
+ if(ReloadListFileCache(pCache) == 0)
+ break;
+ }
+
+ // If we have found a newline, stop loading
+ if(*pCache->pPos == 0x0D || *pCache->pPos == 0x0A)
+ break;
+
+ // Blizzard listfiles can also contain information about patch:
+ // Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326)
+ if(*pCache->pPos == '~')
+ szExtraString = szLine;
+
+ // Copy the character
+ *szLine++ = *pCache->pPos++;
+ }
+
+ // Terminate line with zero
+ *szLine = 0;
+
+ // If there was extra string after the file name, clear it
+ if(szExtraString != NULL)
+ {
+ if(szExtraString[0] == '~' && szExtraString[1] == 'P')
+ {
+ szLine = szExtraString;
+ *szExtraString = 0;
+ }
+ }
+
+ // Return the length of the line
+ return (szLine - szLineBegin);
+}
+
+static int CompareFileNodes(const void * p1, const void * p2)
+{
+ char * szFileName1 = *(char **)p1;
+ char * szFileName2 = *(char **)p2;
+
+ return _stricmp(szFileName1, szFileName2);
+}
+
+static int WriteListFileLine(
+ TMPQFile * hf,
+ const char * szLine)
+{
+ char szNewLine[2] = {0x0D, 0x0A};
+ size_t nLength = strlen(szLine);
+ int nError;
+
+ nError = SFileAddFile_Write(hf, szLine, (DWORD)nLength, MPQ_COMPRESSION_ZLIB);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ return SFileAddFile_Write(hf, szNewLine, sizeof(szNewLine), MPQ_COMPRESSION_ZLIB);
+}
+
+//-----------------------------------------------------------------------------
+// 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 int SListFileCreateNodeForAllLocales(TMPQArchive * ha, const char * szFileName)
+{
+ TMPQHeader * pHeader = ha->pHeader;
+ TFileEntry * pFileEntry;
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+ bool bNameEntryCreated = false;
+
+ // If we have HET table, use that one
+ if(ha->pHetTable != NULL)
+ {
+ pFileEntry = GetFileEntryAny(ha, szFileName);
+ if(pFileEntry != NULL)
+ {
+ // Allocate file name for the file entry
+ AllocateFileName(pFileEntry, szFileName);
+ bNameEntryCreated = true;
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ // If we have hash table, we use it
+ if(bNameEntryCreated == false && ha->pHashTable != NULL)
+ {
+ // Look for the first hash table entry for the file
+ pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
+
+ // Go while we found something
+ while(pHash != NULL)
+ {
+ // Is it a valid file table index ?
+ if(pHash->dwBlockIndex < pHeader->dwBlockTableSize)
+ {
+ // Allocate file name for the file entry
+ AllocateFileName(ha->pFileTable + pHash->dwBlockIndex, szFileName);
+ bNameEntryCreated = true;
+ }
+
+ // Now find the next language version of the file
+ pHash = GetNextHashEntry(ha, pFirstHash, pHash);
+ }
+ }
+
+ return ERROR_CAN_NOT_COMPLETE;
+}
+
+// Saves the whole listfile into the MPQ.
+int SListFileSaveToMpq(TMPQArchive * ha)
+{
+ TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
+ TFileEntry * pFileEntry;
+ TMPQFile * hf = NULL;
+ char * szPrevItem;
+ char ** SortTable = NULL;
+ DWORD dwFileSize = 0;
+ size_t nFileNodes = 0;
+ size_t i;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate the table for sorting listfile
+ SortTable = STORM_ALLOC(char*, ha->dwFileTableSize);
+ if(SortTable == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Construct the sort table
+ // Note: in MPQs with multiple locale versions of the same file,
+ // this code causes adding multiple listfile entries.
+ // Since those MPQs were last time used in Starcraft,
+ // we leave it as it is.
+ for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
+ {
+ // Only take existing items
+ if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->szFileName != NULL)
+ {
+ // Ignore pseudo-names
+ if(!IsPseudoFileName(pFileEntry->szFileName, NULL) && !IsInternalMpqFileName(pFileEntry->szFileName))
+ {
+ SortTable[nFileNodes++] = pFileEntry->szFileName;
+ }
+ }
+ }
+
+ // Sort the table
+ qsort(SortTable, nFileNodes, sizeof(char *), CompareFileNodes);
+
+ // Now parse the table of file names again - remove duplicates
+ // and count file size.
+ if(nFileNodes != 0)
+ {
+ // Count the 0-th item
+ dwFileSize += (DWORD)strlen(SortTable[0]) + 2;
+ szPrevItem = SortTable[0];
+
+ // Count all next items
+ for(i = 1; i < nFileNodes; i++)
+ {
+ // If the item is the same like the last one, skip it
+ if(_stricmp(SortTable[i], szPrevItem))
+ {
+ dwFileSize += (DWORD)strlen(SortTable[i]) + 2;
+ szPrevItem = SortTable[i];
+ }
+ }
+
+ // Determine the flags for (listfile)
+ if(ha->dwFileFlags1 == 0)
+ ha->dwFileFlags1 = GetDefaultSpecialFileFlags(ha, dwFileSize);
+
+ // Create the listfile in the MPQ
+ nError = SFileAddFile_Init(ha, LISTFILE_NAME,
+ 0,
+ dwFileSize,
+ LANG_NEUTRAL,
+ ha->dwFileFlags1 | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+ // Add all file names
+ if(nError == ERROR_SUCCESS)
+ {
+ // Each name is followed by newline ("\x0D\x0A")
+ szPrevItem = SortTable[0];
+ nError = WriteListFileLine(hf, SortTable[0]);
+
+ // Count all next items
+ for(i = 1; i < nFileNodes; i++)
+ {
+ // If the item is the same like the last one, skip it
+ if(_stricmp(SortTable[i], szPrevItem))
+ {
+ WriteListFileLine(hf, SortTable[i]);
+ szPrevItem = SortTable[i];
+ }
+ }
+ }
+ }
+ else
+ {
+ // Create the listfile in the MPQ
+ dwFileSize = (DWORD)strlen(LISTFILE_NAME) + 2;
+ nError = SFileAddFile_Init(ha, LISTFILE_NAME,
+ 0,
+ dwFileSize,
+ LANG_NEUTRAL,
+ MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING,
+ &hf);
+
+ // Just add "(listfile)" there
+ if(nError == ERROR_SUCCESS)
+ {
+ WriteListFileLine(hf, LISTFILE_NAME);
+ }
+ }
+
+ // Finalize the file in the MPQ
+ if(hf != NULL)
+ {
+ SFileAddFile_Finish(hf);
+ }
+
+ // Free buffers
+ if(nError == ERROR_SUCCESS)
+ ha->dwFlags &= ~MPQ_FLAG_INV_LISTFILE;
+ if(SortTable != NULL)
+ STORM_FREE(SortTable);
+ return nError;
+}
+
+static int SFileAddArbitraryListFile(
+ TMPQArchive * ha,
+ HANDLE hListFile)
+{
+ TListFileCache * pCache = NULL;
+ size_t nLength;
+ char szFileName[MAX_PATH];
+ int nError = ERROR_SUCCESS;
+
+ // Create the listfile cache for that file
+ pCache = CreateListFileCache(hListFile, NULL);
+ if(pCache == NULL)
+ nError = GetLastError();
+
+ // Load the node list. Add the node for every locale in the archive
+ if(nError == ERROR_SUCCESS)
+ {
+ while((nLength = ReadListFileLine(pCache, szFileName, sizeof(szFileName))) > 0)
+ SListFileCreateNodeForAllLocales(ha, szFileName);
+ pCache->hFile = NULL;
+ }
+
+ // Delete the cache
+ if(pCache != NULL)
+ FreeListFileCache(pCache);
+ return nError;
+}
+
+static int SFileAddExternalListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq,
+ const char * szListFile)
+{
+ HANDLE hListFile;
+ int nError = ERROR_SUCCESS;
+
+ // Open the external list file
+ if(SFileOpenFileEx(hMpq, szListFile, SFILE_OPEN_LOCAL_FILE, &hListFile))
+ {
+ // Add the data from the listfile to MPQ
+ nError = SFileAddArbitraryListFile(ha, hListFile);
+ SFileCloseFile(hListFile);
+ }
+ return nError;
+}
+
+static int SFileAddInternalListFile(
+ TMPQArchive * ha,
+ HANDLE hMpq)
+{
+ TMPQArchive * haMpq = (TMPQArchive *)hMpq;
+ TMPQHash * pFirstHash;
+ TMPQHash * pHash;
+ HANDLE hListFile;
+ LCID lcSaveLocale = lcFileLocale;
+ int nError = ERROR_SUCCESS;
+
+ // If there is hash table, we need to support multiple listfiles
+ // with different locales (BrooDat.mpq)
+ if(haMpq->pHashTable != NULL)
+ {
+ pFirstHash = pHash = GetFirstHashEntry(haMpq, LISTFILE_NAME);
+ while(nError == ERROR_SUCCESS && pHash != NULL)
+ {
+ // Set the prefered locale to that from list file
+ SFileSetLocale(pHash->lcLocale);
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hListFile))
+ {
+ // Add the data from the listfile to MPQ
+ nError = SFileAddArbitraryListFile(ha, hListFile);
+ SFileCloseFile(hListFile);
+ }
+
+ // Restore the original locale
+ SFileSetLocale(lcSaveLocale);
+
+ // Move to the next hash
+ pHash = GetNextHashEntry(haMpq, pFirstHash, pHash);
+ }
+ }
+ else
+ {
+ // Open the external list file
+ if(SFileOpenFileEx(hMpq, LISTFILE_NAME, 0, &hListFile))
+ {
+ // Add the data from the listfile to MPQ
+ // The function also closes the listfile handle
+ nError = SFileAddArbitraryListFile(ha, hListFile);
+ SFileCloseFile(hListFile);
+ }
+ }
+
+ // Return the result of the operation
+ return nError;
+}
+
+//-----------------------------------------------------------------------------
+// File functions
+
+// Adds a listfile into the MPQ archive.
+int WINAPI SFileAddListFile(HANDLE hMpq, const char * szListFile)
+{
+ TMPQArchive * ha = (TMPQArchive *)hMpq;
+ int nError = ERROR_SUCCESS;
+
+ // Add the listfile for each MPQ in the patch chain
+ while(ha != NULL)
+ {
+ if(szListFile != NULL)
+ SFileAddExternalListFile(ha, hMpq, szListFile);
+ else
+ 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 nError;
+}
+
+//-----------------------------------------------------------------------------
+// Enumerating files in listfile
+
+HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const char * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
+{
+ TListFileCache * pCache = NULL;
+ HANDLE hListFile;
+ size_t nLength = 0;
+ DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
+ int nError = ERROR_SUCCESS;
+
+ // Initialize the structure with zeros
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+
+ // If the szListFile is NULL, it means we have to open internal listfile
+ if(szListFile == NULL)
+ {
+ // Use SFILE_OPEN_ANY_LOCALE for listfile. This will allow us to load
+ // the listfile even if there is only non-neutral version of the listfile in the MPQ
+ dwSearchScope = SFILE_OPEN_ANY_LOCALE;
+ szListFile = LISTFILE_NAME;
+ }
+
+ // Open the local/internal listfile
+ if(!SFileOpenFileEx(hMpq, szListFile, dwSearchScope, &hListFile))
+ nError = GetLastError();
+
+ // Load the listfile to cache
+ if(nError == ERROR_SUCCESS)
+ {
+ pCache = CreateListFileCache(hListFile, szMask);
+ if(pCache == NULL)
+ nError = GetLastError();
+ }
+
+ // Perform file search
+ if(nError == ERROR_SUCCESS)
+ {
+ for(;;)
+ {
+ // Read the (next) line
+ nLength = ReadListFileLine(pCache, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName));
+ if(nLength == 0)
+ {
+ nError = ERROR_NO_MORE_FILES;
+ break;
+ }
+
+ // If some mask entered, check it
+ if(CheckWildCard(lpFindFileData->cFileName, pCache->szMask))
+ break;
+ }
+ }
+
+ // Cleanup & exit
+ if(nError != ERROR_SUCCESS)
+ {
+ memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
+ FreeListFileCache(pCache);
+ SetLastError(nError);
+ pCache = NULL;
+ }
+ return (HANDLE)pCache;
+}
+
+bool WINAPI SListFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
+{
+ TListFileCache * pCache = (TListFileCache *)hFind;
+ size_t nLength;
+ bool bResult = false;
+ int nError = ERROR_SUCCESS;
+
+ for(;;)
+ {
+ // Read the (next) line
+ nLength = ReadListFileLine(pCache, lpFindFileData->cFileName, sizeof(lpFindFileData->cFileName));
+ if(nLength == 0)
+ {
+ nError = ERROR_NO_MORE_FILES;
+ break;
+ }
+
+ // If some mask entered, check it
+ if(CheckWildCard(lpFindFileData->cFileName, pCache->szMask))
+ {
+ bResult = true;
+ break;
+ }
+ }
+
+ if(nError != ERROR_SUCCESS)
+ SetLastError(nError);
+ return bResult;
+}
+
+bool WINAPI SListFileFindClose(HANDLE hFind)
+{
+ return FreeListFileCache((TListFileCache *)hFind);
+}
+