pandaria_5.4.8/dep/StormLib/src/SFileListFile.cpp
2021-12-28 12:17:51 +03:00

557 lines
17 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
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 TListFileCache * CreateListFileCache(HANDLE hMpq, const char * szListFile)
{
TListFileCache * pCache = NULL;
HANDLE hListFile = NULL;
DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
DWORD dwBytesRead = 0;
int nError = ERROR_SUCCESS;
// 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))
{
TMPQArchive * ha = (TMPQArchive *)hMpq;
TMPQFile * hf = (TMPQFile *)hListFile;
// Remember flags for (listfile)
if(hf->pFileEntry != NULL)
ha->dwFileFlags1 = hf->pFileEntry->dwFlags;
}
else
nError = GetLastError();
// Allocate cache for one file block
if(nError == ERROR_SUCCESS)
{
pCache = (TListFileCache *)STORM_ALLOC(TListFileCache, 1);
if(pCache == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
if(nError == ERROR_SUCCESS)
{
// Initialize the file cache
memset(pCache, 0, sizeof(TListFileCache));
pCache->dwFileSize = SFileGetFileSize(hListFile, NULL);
pCache->hFile = hListFile;
// Fill the cache
SFileReadFile(hListFile, pCache->Buffer, CACHE_BUFFER_SIZE, &dwBytesRead, NULL);
if(dwBytesRead == 0)
nError = GetLastError();
}
// Initialize the pointers
if(nError == ERROR_SUCCESS)
{
pCache->pBegin =
pCache->pPos = &pCache->Buffer[0];
pCache->pEnd = pCache->pBegin + dwBytesRead;
}
else
{
SListFileFindClose((HANDLE)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.
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;
}
//-----------------------------------------------------------------------------
// File functions
// Adds a listfile into the MPQ archive.
// Note that the function does not remove the
int WINAPI SFileAddListFile(HANDLE hMpq, const char * szListFile)
{
TListFileCache * pCache = NULL;
TMPQArchive * ha = (TMPQArchive *)hMpq;
char szFileName[MAX_PATH];
size_t nLength = 0;
int nError = ERROR_SUCCESS;
// Add the listfile for each MPQ in the patch chain
while(ha != NULL)
{
// Load the listfile to cache
pCache = CreateListFileCache(hMpq, szListFile);
if(pCache == NULL)
{
nError = GetLastError();
break;
}
// Load the node list. Add the node for every locale in the archive
while((nLength = ReadListFileLine(pCache, szFileName, sizeof(szFileName))) > 0)
SListFileCreateNodeForAllLocales(ha, szFileName);
// 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);
// Delete the cache
SListFileFindClose((HANDLE)pCache);
// Move to the next archive in the chain
ha = ha->haPatch;
}
return nError;
}
//-----------------------------------------------------------------------------
// Passing through the listfile
HANDLE WINAPI SListFileFindFirstFile(HANDLE hMpq, const char * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
{
TListFileCache * pCache = NULL;
size_t nLength = 0;
int nError = ERROR_SUCCESS;
// Initialize the structure with zeros
memset(lpFindFileData, 0, sizeof(SFILE_FIND_DATA));
// Load the listfile to cache
pCache = CreateListFileCache(hMpq, szListFile);
if(pCache == NULL)
nError = GetLastError();
// Allocate file mask
if(nError == ERROR_SUCCESS && szMask != NULL)
{
pCache->szMask = STORM_ALLOC(char, strlen(szMask) + 1);
if(pCache->szMask != NULL)
strcpy(pCache->szMask, szMask);
else
nError = ERROR_NOT_ENOUGH_MEMORY;
}
// 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));
SListFileFindClose((HANDLE)pCache);
pCache = NULL;
SetLastError(nError);
}
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)
{
TListFileCache * pCache = (TListFileCache *)hFind;
if(pCache != NULL)
{
if(pCache->hFile != NULL)
SFileCloseFile(pCache->hFile);
if(pCache->szMask != NULL)
STORM_FREE(pCache->szMask);
STORM_FREE(pCache);
return true;
}
return false;
}