Shipwright/StormLib/src/SFileListFile.cpp
2022-06-16 20:35:52 -04:00

688 lines
22 KiB
C++

/*****************************************************************************/
/* 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++)
{
// OTRTODO
//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);
}