shlainn 9e5c1fdd30 * Updated StormLib to recent Version
* changed stuffextract output dir to "extractedstuff" to avoid conflicts on *nix
2010-03-03 15:42:01 +00:00

613 lines
19 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 "SCommon.h"
#include <assert.h>
//-----------------------------------------------------------------------------
// Listfile entry structure
#define LISTFILE_CACHE_SIZE 0x1000 // Size of one cache element
#define NO_MORE_CHARACTERS 256
#define HASH_TABLE_SIZE 31 // Initial hash table size (should be a prime number)
struct TListFileCache
{
HANDLE hFile; // Stormlib file handle
char * szMask; // File mask
DWORD dwFileSize; // Total size of the cached file
DWORD dwBuffSize; // File of the cache
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[1]; // Listfile cache itself
};
//-----------------------------------------------------------------------------
// Local functions (cache)
// Reloads the cache. Returns number of characters
// that has been loaded into the cache.
static int ReloadCache(TListFileCache * pCache)
{
// Check if there is enough characters in the cache
// If not, we have to reload the next block
if(pCache->pPos >= pCache->pEnd)
{
// If the cache is already at the end, do nothing more
if((pCache->dwFilePos + pCache->dwBuffSize) >= pCache->dwFileSize)
return 0;
pCache->dwFilePos += pCache->dwBuffSize;
SFileReadFile(pCache->hFile, pCache->Buffer, pCache->dwBuffSize, &pCache->dwBuffSize, NULL);
if(pCache->dwBuffSize == 0)
return 0;
// Set the buffer pointers
pCache->pBegin =
pCache->pPos = &pCache->Buffer[0];
pCache->pEnd = pCache->pBegin + pCache->dwBuffSize;
}
return pCache->dwBuffSize;
}
static size_t ReadLine(TListFileCache * pCache, char * szLine, int nMaxChars)
{
char * szLineBegin = szLine;
char * szLineEnd = szLine + nMaxChars - 1;
__BeginLoading:
// Skip newlines, spaces, tabs and another non-printable stuff
while(pCache->pPos < pCache->pEnd && *pCache->pPos <= 0x20)
pCache->pPos++;
// Copy the remaining characters
while(pCache->pPos < pCache->pEnd && szLine < szLineEnd)
{
// If we have found a newline, stop loading
if(*pCache->pPos == 0x0D || *pCache->pPos == 0x0A)
break;
*szLine++ = *pCache->pPos++;
}
// If we now need to reload the cache, do it
if(pCache->pPos == pCache->pEnd)
{
if(ReloadCache(pCache) > 0)
goto __BeginLoading;
}
*szLine = 0;
return (szLine - szLineBegin);
}
//-----------------------------------------------------------------------------
// Local functions (listfile nodes)
// This function creates the name for the listfile.
// the file will be created under unique name in the temporary directory
static void GetListFileName(TMPQArchive * /* ha */, char * szListFile)
{
char szTemp[MAX_PATH];
// Create temporary file name int TEMP directory
GetTempPath(sizeof(szTemp)-1, szTemp);
GetTempFileName(szTemp, LISTFILE_NAME, 0, szListFile);
}
// Creates new listfile. The listfile is an array of TListFileNode
// structures. The size of the array is the same like the hash table size,
// the ordering is the same too (listfile item index is the same like
// the index in the MPQ hash table)
int SListFileCreateListFile(TMPQArchive * ha)
{
DWORD dwItems = ha->pHeader->dwHashTableSize;
// The listfile should be NULL now
assert(ha->pListFile == NULL);
ha->pListFile = ALLOCMEM(TFileNode *, dwItems);
if(ha->pListFile == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
memset(ha->pListFile, 0xFF, dwItems * sizeof(TFileNode *));
return ERROR_SUCCESS;
}
// 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)
{
TFileNode * pNode = NULL;
TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
TMPQHash * pHash0 = GetHashEntry(ha, szFileName);
TMPQHash * pHash = pHash0;
DWORD dwHashIndex = 0;
size_t nLength; // File name lentgth
DWORD dwName1;
DWORD dwName2;
// If the file does not exist within the MPQ, do nothing
if(pHash == NULL)
return ERROR_SUCCESS;
// Remember the name
dwName1 = pHash->dwName1;
dwName2 = pHash->dwName2;
// Pass all entries in the hash table
while(pHash->dwBlockIndex != HASH_ENTRY_FREE)
{
// There may be an entry deleted amongst various language versions
if(pHash->dwBlockIndex != HASH_ENTRY_DELETED)
{
if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2)
{
// Compute the hash index
dwHashIndex = (DWORD)(pHash - ha->pHashTable);
// Create the lang version, if none
if((DWORD_PTR)ha->pListFile[dwHashIndex] >= LISTFILE_ENTRY_DELETED)
{
// Create the listfile node, if doesn't exist yet
if(pNode == NULL)
{
nLength = strlen(szFileName);
pNode = (TFileNode *)ALLOCMEM(char, sizeof(TFileNode) + nLength);
pNode->dwRefCount = 0;
pNode->nLength = nLength;
strcpy(pNode->szFileName, szFileName);
}
// Insert the node to the listfile table
ha->pListFile[dwHashIndex] = pNode;
pNode->dwRefCount++;
}
}
}
else
{
dwHashIndex = (DWORD)(pHash - ha->pHashTable);
ha->pListFile[dwHashIndex] = (TFileNode *)LISTFILE_ENTRY_DELETED;
}
// Move to the next hash entry
if(++pHash >= pHashEnd)
pHash = ha->pHashTable;
if(pHash == pHash0)
break;
}
return ERROR_SUCCESS;
}
// Adds a filename into the listfile. If the file name is already there,
// does nothing.
int SListFileCreateNode(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
{
TFileNode * pNode = NULL;
TMPQHash * pHash0 = GetHashEntry(ha, szFileName);
TMPQHash * pHash1 = GetHashEntryEx(ha, szFileName, lcLocale);
DWORD dwHashIndex0 = 0;
DWORD dwHashIndex1 = 0;
size_t nLength; // File name lentgth
// If the file does not exist within the MPQ, do nothing
if(pHash1 == NULL || pHash1->dwBlockIndex >= HASH_ENTRY_DELETED)
return ERROR_SUCCESS;
// If the locale-cpecific listfile entry already exists, do nothing
dwHashIndex0 = (DWORD)(pHash0 - ha->pHashTable);
dwHashIndex1 = (DWORD)(pHash1 - ha->pHashTable);
if((DWORD_PTR)ha->pListFile[dwHashIndex1] < LISTFILE_ENTRY_DELETED)
return ERROR_SUCCESS;
// Does the neutral table entry exist ?
if((DWORD_PTR)ha->pListFile[dwHashIndex0] < LISTFILE_ENTRY_DELETED)
pNode = ha->pListFile[dwHashIndex0];
// If no node yet, we have to create new one
if(pNode == NULL)
{
nLength = strlen(szFileName);
pNode = (TFileNode *)ALLOCMEM(char, sizeof(TFileNode) + nLength);
pNode->dwRefCount = 1;
pNode->nLength = nLength;
strcpy(pNode->szFileName, szFileName);
ha->pListFile[dwHashIndex0] = pNode;
}
// Also insert the node in the locale-specific entry
if(dwHashIndex1 != dwHashIndex0)
{
pNode->dwRefCount++;
ha->pListFile[dwHashIndex1] = pNode;
}
return ERROR_SUCCESS;
}
// Removes a filename from the listfile.
// If the name is not there, does nothing
int SListFileRemoveNode(TMPQArchive * ha, const char * szFileName, LCID lcLocale)
{
TFileNode * pNode = NULL;
TMPQHash * pHash = GetHashEntryEx(ha, szFileName, lcLocale);
size_t nHashIndex = 0;
if(pHash != NULL)
{
nHashIndex = pHash - ha->pHashTable;
pNode = ha->pListFile[nHashIndex];
ha->pListFile[nHashIndex] = (TFileNode *)LISTFILE_ENTRY_DELETED;
// Free the node
pNode->dwRefCount--;
if(pNode->dwRefCount == 0)
FREEMEM(pNode);
}
return ERROR_SUCCESS;
}
void SListFileFreeListFile(TMPQArchive * ha)
{
if(ha->pListFile != NULL)
{
for(DWORD i = 0; i < ha->pHeader->dwHashTableSize; i++)
{
TFileNode * pNode = ha->pListFile[i];
if((DWORD_PTR)pNode < LISTFILE_ENTRY_DELETED)
{
ha->pListFile[i] = (TFileNode *)LISTFILE_ENTRY_FREE;
pNode->dwRefCount--;
if(pNode->dwRefCount == 0)
FREEMEM(pNode);
}
}
FREEMEM(ha->pListFile);
ha->pListFile = NULL;
}
}
// Saves the whole listfile into the MPQ.
int SListFileSaveToMpq(TMPQArchive * ha)
{
TFileNode * pNode = NULL;
TMPQHash * pHashEnd = NULL;
TMPQHash * pHash0 = NULL;
TMPQHash * pHash = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
char szListFile[MAX_PATH];
char szBuffer[MAX_PATH+4];
DWORD dwTransferred;
size_t nLength = 0;
DWORD dwName1 = 0;
DWORD dwName2 = 0;
LCID lcSave = lcLocale;
int nError = ERROR_SUCCESS;
// If no listfile, do nothing
if(ha->pListFile == NULL)
return ERROR_SUCCESS;
// Create the local listfile
if(nError == ERROR_SUCCESS)
{
GetListFileName(ha, szListFile);
hFile = CreateFile(szListFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if(hFile == INVALID_HANDLE_VALUE)
nError = GetLastError();
}
// Find the hash entry corresponding to listfile
pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
pHash0 = pHash = GetHashEntry(ha, 0);
if(pHash == NULL)
pHash0 = pHash = ha->pHashTable;
// Save the file
if(nError == ERROR_SUCCESS)
{
for(;;)
{
if(pHash->dwName1 != dwName1 && pHash->dwName2 != dwName2 && pHash->dwBlockIndex < HASH_ENTRY_DELETED)
{
dwName1 = pHash->dwName1;
dwName2 = pHash->dwName2;
pNode = ha->pListFile[pHash - ha->pHashTable];
if((DWORD_PTR)pNode < LISTFILE_ENTRY_DELETED)
{
memcpy(szBuffer, pNode->szFileName, pNode->nLength);
szBuffer[pNode->nLength + 0] = 0x0D;
szBuffer[pNode->nLength + 1] = 0x0A;
WriteFile(hFile, szBuffer, (DWORD)(pNode->nLength + 2), &dwTransferred, NULL);
}
}
if(++pHash >= pHashEnd)
pHash = ha->pHashTable;
if(pHash == pHash0)
break;
}
// Write the listfile name (if not already there)
if(GetHashEntry(ha, LISTFILE_NAME) == NULL)
{
nLength = strlen(LISTFILE_NAME);
memcpy(szBuffer, LISTFILE_NAME, nLength);
szBuffer[nLength + 0] = 0x0D;
szBuffer[nLength + 1] = 0x0A;
WriteFile(hFile, szBuffer, (DWORD)(nLength + 2), &dwTransferred, NULL);
}
// Add the listfile into the archive.
SFileSetLocale(LANG_NEUTRAL);
nError = AddFileToArchive(ha,
hFile,
LISTFILE_NAME,
MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS | MPQ_FILE_REPLACEEXISTING,
0,
SFILE_TYPE_DATA,
NULL);
lcLocale = lcSave;
}
// Close the temporary file and delete it.
// There is no FILE_FLAG_DELETE_ON_CLOSE on LINUX.
if(hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
DeleteFile(szListFile);
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;
HANDLE hListFile = NULL;
char szFileName[MAX_PATH + 1];
DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
DWORD dwCacheSize = 0;
DWORD dwFileSize = 0;
size_t nLength = 0;
int nError = ERROR_SUCCESS;
// If the szListFile is NULL, it means we have to open internal listfile
if(szListFile == NULL)
{
szListFile = LISTFILE_NAME;
dwSearchScope = SFILE_OPEN_FROM_MPQ;
}
// Open the local/internal listfile
if(nError == ERROR_SUCCESS)
{
if(!SFileOpenFileEx((HANDLE)ha, szListFile, dwSearchScope, &hListFile))
nError = GetLastError();
}
if(nError == ERROR_SUCCESS)
{
dwCacheSize =
dwFileSize = SFileGetFileSize(hListFile, NULL);
// Try to allocate memory for the complete file. If it fails,
// load the part of the file
pCache = (TListFileCache *)ALLOCMEM(char, (sizeof(TListFileCache) + dwCacheSize));
if(pCache == NULL)
{
dwCacheSize = LISTFILE_CACHE_SIZE;
pCache = (TListFileCache *)ALLOCMEM(char, sizeof(TListFileCache) + dwCacheSize);
}
if(pCache == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
if(nError == ERROR_SUCCESS)
{
// Initialize the file cache
memset(pCache, 0, sizeof(TListFileCache));
pCache->hFile = hListFile;
pCache->dwFileSize = dwFileSize;
pCache->dwBuffSize = dwCacheSize;
pCache->dwFilePos = 0;
// Fill the cache
SFileReadFile(hListFile, pCache->Buffer, pCache->dwBuffSize, &pCache->dwBuffSize, NULL);
// Initialize the pointers
pCache->pBegin =
pCache->pPos = &pCache->Buffer[0];
pCache->pEnd = pCache->pBegin + pCache->dwBuffSize;
// Load the node list. Add the node for every locale in the archive
while((nLength = ReadLine(pCache, szFileName, sizeof(szFileName) - 1)) > 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);
}
// Cleanup & exit
if(pCache != NULL)
SListFileFindClose((HANDLE)pCache);
return nError;
}
//-----------------------------------------------------------------------------
// Passing through the listfile
HANDLE SListFileFindFirstFile(HANDLE hMpq, const char * szListFile, const char * szMask, SFILE_FIND_DATA * lpFindFileData)
{
TListFileCache * pCache = NULL;
TMPQArchive * ha = (TMPQArchive *)hMpq;
HANDLE hListFile = NULL;
DWORD dwSearchScope = SFILE_OPEN_LOCAL_FILE;
DWORD dwCacheSize = 0;
DWORD dwFileSize = 0;
size_t nLength = 0;
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)
{
szListFile = LISTFILE_NAME;
dwSearchScope = SFILE_OPEN_FROM_MPQ;
}
// Open the local/internal listfile
if(nError == ERROR_SUCCESS)
{
if(!SFileOpenFileEx((HANDLE)ha, szListFile, dwSearchScope, &hListFile))
nError = GetLastError();
}
if(nError == ERROR_SUCCESS)
{
dwCacheSize =
dwFileSize = SFileGetFileSize(hListFile, NULL);
// Try to allocate memory for the complete file. If it fails,
// load the part of the file
pCache = (TListFileCache *)ALLOCMEM(char, sizeof(TListFileCache) + dwCacheSize);
if(pCache == NULL)
{
dwCacheSize = LISTFILE_CACHE_SIZE;
pCache = (TListFileCache *)ALLOCMEM(char, sizeof(TListFileCache) + dwCacheSize);
}
if(pCache == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
if(nError == ERROR_SUCCESS)
{
// Initialize the file cache
memset(pCache, 0, sizeof(TListFileCache));
pCache->hFile = hListFile;
pCache->dwFileSize = dwFileSize;
pCache->dwBuffSize = dwCacheSize;
pCache->dwFilePos = 0;
if(szMask != NULL)
{
pCache->szMask = ALLOCMEM(char, strlen(szMask) + 1);
strcpy(pCache->szMask, szMask);
}
// Fill the cache
SFileReadFile(hListFile, pCache->Buffer, pCache->dwBuffSize, &pCache->dwBuffSize, NULL);
// Initialize the pointers
pCache->pBegin =
pCache->pPos = &pCache->Buffer[0];
pCache->pEnd = pCache->pBegin + pCache->dwBuffSize;
for(;;)
{
// Read the (next) line
nLength = ReadLine(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 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 = ReadLine(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 SListFileFindClose(HANDLE hFind)
{
TListFileCache * pCache = (TListFileCache *)hFind;
if(pCache != NULL)
{
if(pCache->hFile != NULL)
SFileCloseFile(pCache->hFile);
if(pCache->szMask != NULL)
FREEMEM(pCache->szMask);
FREEMEM(pCache);
return TRUE;
}
return FALSE;
}