mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-11 20:15:07 -05:00
484 lines
16 KiB
C++
484 lines
16 KiB
C++
|
/*****************************************************************************/
|
||
|
/* SFileFindFile.cpp Copyright (c) Ladislav Zezula 2003 */
|
||
|
/*---------------------------------------------------------------------------*/
|
||
|
/* A module for file searching within MPQs */
|
||
|
/*---------------------------------------------------------------------------*/
|
||
|
/* Date Ver Who Comment */
|
||
|
/* -------- ---- --- ------- */
|
||
|
/* 25.03.03 1.00 Lad The first version of SFileFindFile.cpp */
|
||
|
/*****************************************************************************/
|
||
|
|
||
|
#define __STORMLIB_SELF__
|
||
|
#include "StormLib.h"
|
||
|
#include "StormCommon.h"
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Private structure used for file search (search handle)
|
||
|
|
||
|
// Used by searching in MPQ archives
|
||
|
struct TMPQSearch
|
||
|
{
|
||
|
TMPQArchive * ha; // Handle to MPQ, where the search runs
|
||
|
TFileEntry ** pSearchTable; // Table for files that have been already found
|
||
|
DWORD dwSearchTableItems; // Number of items in the search table
|
||
|
DWORD dwNextIndex; // Next file index to be checked
|
||
|
DWORD dwFlagMask; // For checking flag mask
|
||
|
char szSearchMask[1]; // Search mask (variable length)
|
||
|
};
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Local functions
|
||
|
|
||
|
static TMPQSearch * IsValidSearchHandle(HANDLE hFind)
|
||
|
{
|
||
|
TMPQSearch * hs = (TMPQSearch *)hFind;
|
||
|
|
||
|
if(hs != NULL && IsValidMpqHandle(hs->ha))
|
||
|
return hs;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
bool SFileCheckWildCard(const char * szString, const char * szWildCard)
|
||
|
{
|
||
|
const char * szWildCardPtr;
|
||
|
|
||
|
for(;;)
|
||
|
{
|
||
|
// If there is '?' in the wildcard, we skip one char
|
||
|
while(szWildCard[0] == '?')
|
||
|
{
|
||
|
if(szString[0] == 0)
|
||
|
return false;
|
||
|
|
||
|
szWildCard++;
|
||
|
szString++;
|
||
|
}
|
||
|
|
||
|
// Handle '*'
|
||
|
szWildCardPtr = szWildCard;
|
||
|
if(szWildCardPtr[0] != 0)
|
||
|
{
|
||
|
if(szWildCardPtr[0] == '*')
|
||
|
{
|
||
|
while(szWildCardPtr[0] == '*')
|
||
|
szWildCardPtr++;
|
||
|
|
||
|
if(szWildCardPtr[0] == 0)
|
||
|
return true;
|
||
|
|
||
|
if(AsciiToUpperTable[szWildCardPtr[0]] == AsciiToUpperTable[szString[0]])
|
||
|
{
|
||
|
if(SFileCheckWildCard(szString, szWildCardPtr))
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(AsciiToUpperTable[szWildCardPtr[0]] != AsciiToUpperTable[szString[0]])
|
||
|
return false;
|
||
|
|
||
|
szWildCard = szWildCardPtr + 1;
|
||
|
}
|
||
|
|
||
|
if(szString[0] == 0)
|
||
|
return false;
|
||
|
szString++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return (szString[0] == 0) ? true : false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static DWORD GetSearchTableItems(TMPQArchive * ha)
|
||
|
{
|
||
|
DWORD dwMergeItems = 0;
|
||
|
|
||
|
// Loop over all patches
|
||
|
while(ha != NULL)
|
||
|
{
|
||
|
// Append the number of files
|
||
|
dwMergeItems += (ha->pHetTable != NULL) ? ha->pHetTable->dwEntryCount
|
||
|
: ha->pHeader->dwBlockTableSize;
|
||
|
// Move to the patched archive
|
||
|
ha = ha->haPatch;
|
||
|
}
|
||
|
|
||
|
// Return the double size of number of items
|
||
|
return (dwMergeItems | 1);
|
||
|
}
|
||
|
|
||
|
static bool FileWasFoundBefore(
|
||
|
TMPQArchive * ha,
|
||
|
TMPQSearch * hs,
|
||
|
TFileEntry * pFileEntry)
|
||
|
{
|
||
|
TFileEntry * pEntry;
|
||
|
char * szRealFileName = pFileEntry->szFileName;
|
||
|
DWORD dwStartIndex;
|
||
|
DWORD dwNameHash;
|
||
|
DWORD dwIndex;
|
||
|
|
||
|
if(hs->pSearchTable != NULL && szRealFileName != NULL)
|
||
|
{
|
||
|
// If we are in patch MPQ, we check if patch prefix matches
|
||
|
// and then trim the patch prefix
|
||
|
if(ha->pPatchPrefix != NULL)
|
||
|
{
|
||
|
// If the patch prefix doesn't fit, we pretend that the file
|
||
|
// was there before and it will be skipped
|
||
|
if(_strnicmp(szRealFileName, ha->pPatchPrefix->szPatchPrefix, ha->pPatchPrefix->nLength))
|
||
|
return true;
|
||
|
|
||
|
szRealFileName += ha->pPatchPrefix->nLength;
|
||
|
}
|
||
|
|
||
|
// Calculate the hash to the table
|
||
|
dwNameHash = ha->pfnHashString(szRealFileName, MPQ_HASH_NAME_A);
|
||
|
dwStartIndex = dwIndex = (dwNameHash % hs->dwSearchTableItems);
|
||
|
|
||
|
// The file might have been found before
|
||
|
// only if this is not the first MPQ being searched
|
||
|
if(ha->haBase != NULL)
|
||
|
{
|
||
|
// Enumerate all entries in the search table
|
||
|
for(;;)
|
||
|
{
|
||
|
// Get the file entry at that position
|
||
|
pEntry = hs->pSearchTable[dwIndex];
|
||
|
if(pEntry == NULL)
|
||
|
break;
|
||
|
|
||
|
if(pEntry->szFileName != NULL)
|
||
|
{
|
||
|
// Does the name match?
|
||
|
if(!_stricmp(pEntry->szFileName, szRealFileName))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Move to the next entry
|
||
|
dwIndex = (dwIndex + 1) % hs->dwSearchTableItems;
|
||
|
if(dwIndex == dwStartIndex)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Put the entry to the table for later use
|
||
|
hs->pSearchTable[dwIndex] = pFileEntry;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static TFileEntry * FindPatchEntry(TMPQArchive * ha, TFileEntry * pFileEntry)
|
||
|
{
|
||
|
TFileEntry * pPatchEntry = pFileEntry;
|
||
|
TFileEntry * pTempEntry;
|
||
|
char szFileName[MAX_PATH+1];
|
||
|
|
||
|
// Can't find patch entry for a file that doesn't have name
|
||
|
if(pFileEntry->szFileName != NULL && pFileEntry->szFileName[0] != 0)
|
||
|
{
|
||
|
// Go while there are patches
|
||
|
while(ha->haPatch != NULL)
|
||
|
{
|
||
|
// Move to the patch archive
|
||
|
ha = ha->haPatch;
|
||
|
szFileName[0] = 0;
|
||
|
|
||
|
// Prepare the prefix for the file name
|
||
|
if(ha->pPatchPrefix && ha->pPatchPrefix->nLength)
|
||
|
StringCopy(szFileName, _countof(szFileName), ha->pPatchPrefix->szPatchPrefix);
|
||
|
StringCat(szFileName, _countof(szFileName), pFileEntry->szFileName);
|
||
|
|
||
|
// Try to find the file there
|
||
|
pTempEntry = GetFileEntryExact(ha, szFileName, 0, NULL);
|
||
|
if(pTempEntry != NULL)
|
||
|
pPatchEntry = pTempEntry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return the found patch entry
|
||
|
return pPatchEntry;
|
||
|
}
|
||
|
|
||
|
static bool DoMPQSearch_FileEntry(
|
||
|
TMPQSearch * hs,
|
||
|
SFILE_FIND_DATA * lpFindFileData,
|
||
|
TMPQArchive * ha,
|
||
|
TMPQHash * pHashEntry,
|
||
|
TFileEntry * pFileEntry)
|
||
|
{
|
||
|
TFileEntry * pPatchEntry;
|
||
|
HANDLE hFile = NULL;
|
||
|
const char * szFileName;
|
||
|
size_t nPrefixLength = (ha->pPatchPrefix != NULL) ? ha->pPatchPrefix->nLength : 0;
|
||
|
DWORD dwBlockIndex;
|
||
|
char szNameBuff[MAX_PATH];
|
||
|
|
||
|
// Is it a file but not a patch file?
|
||
|
if((pFileEntry->dwFlags & hs->dwFlagMask) == MPQ_FILE_EXISTS)
|
||
|
{
|
||
|
// Ignore fake files which are not compressed but have size higher than the archive
|
||
|
if ((pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) == 0 && (pFileEntry->dwFileSize > ha->FileSize))
|
||
|
return false;
|
||
|
|
||
|
// Now we have to check if this file was not enumerated before
|
||
|
if(!FileWasFoundBefore(ha, hs, pFileEntry))
|
||
|
{
|
||
|
// if(pFileEntry != NULL && !_stricmp(pFileEntry->szFileName, "TriggerLibs\\NativeLib.galaxy"))
|
||
|
// DebugBreak();
|
||
|
|
||
|
// Find a patch to this file
|
||
|
// Note: This either succeeds or returns pFileEntry
|
||
|
pPatchEntry = FindPatchEntry(ha, pFileEntry);
|
||
|
|
||
|
// Prepare the block index
|
||
|
dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
|
||
|
if(dwBlockIndex == 569)
|
||
|
szNameBuff[0] = 'F';
|
||
|
|
||
|
// Get the file name. If it's not known, we will create pseudo-name
|
||
|
szFileName = pFileEntry->szFileName;
|
||
|
if(szFileName == NULL)
|
||
|
{
|
||
|
// Open the file by its pseudo-name.
|
||
|
StringCreatePseudoFileName(szNameBuff, _countof(szNameBuff), dwBlockIndex, "xxx");
|
||
|
if(SFileOpenFileEx((HANDLE)hs->ha, szNameBuff, SFILE_OPEN_BASE_FILE, &hFile))
|
||
|
{
|
||
|
SFileGetFileName(hFile, szNameBuff);
|
||
|
szFileName = szNameBuff;
|
||
|
SFileCloseFile(hFile);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the file name is still NULL, we cannot include the file to search results
|
||
|
if(szFileName != NULL)
|
||
|
{
|
||
|
// Check the file name against the wildcard
|
||
|
if(SFileCheckWildCard(szFileName + nPrefixLength, hs->szSearchMask))
|
||
|
{
|
||
|
// Fill the found entry. hash entry and block index are taken from the base MPQ
|
||
|
lpFindFileData->dwHashIndex = HASH_ENTRY_FREE;
|
||
|
lpFindFileData->dwBlockIndex = dwBlockIndex;
|
||
|
lpFindFileData->dwFileSize = pPatchEntry->dwFileSize;
|
||
|
lpFindFileData->dwFileFlags = pPatchEntry->dwFlags;
|
||
|
lpFindFileData->dwCompSize = pPatchEntry->dwCmpSize;
|
||
|
lpFindFileData->lcLocale = 0; // pPatchEntry->lcLocale;
|
||
|
|
||
|
// Fill the filetime
|
||
|
lpFindFileData->dwFileTimeHi = (DWORD)(pPatchEntry->FileTime >> 32);
|
||
|
lpFindFileData->dwFileTimeLo = (DWORD)(pPatchEntry->FileTime);
|
||
|
|
||
|
// Fill-in the entries from hash table entry, if given
|
||
|
if(pHashEntry != NULL)
|
||
|
{
|
||
|
lpFindFileData->dwHashIndex = (DWORD)(pHashEntry - ha->pHashTable);
|
||
|
lpFindFileData->lcLocale = pHashEntry->lcLocale;
|
||
|
}
|
||
|
|
||
|
// Fill the file name and plain file name
|
||
|
StringCopy(lpFindFileData->cFileName, _countof(lpFindFileData->cFileName), szFileName + nPrefixLength);
|
||
|
lpFindFileData->szPlainName = (char *)GetPlainFileName(lpFindFileData->cFileName);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Either not a valid item or was found before
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static DWORD DoMPQSearch_HashTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha)
|
||
|
{
|
||
|
TMPQHash * pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
|
||
|
TMPQHash * pHash;
|
||
|
|
||
|
// Parse the file table
|
||
|
for(pHash = ha->pHashTable + hs->dwNextIndex; pHash < pHashTableEnd; pHash++)
|
||
|
{
|
||
|
// Increment the next index for subsequent search
|
||
|
hs->dwNextIndex++;
|
||
|
|
||
|
// Does this hash table entry point to a proper block table entry?
|
||
|
if(IsValidHashEntry(ha, pHash))
|
||
|
{
|
||
|
// Check if this file entry should be included in the search result
|
||
|
if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, pHash, ha->pFileTable + MPQ_BLOCK_INDEX(pHash)))
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// No more files
|
||
|
return ERROR_NO_MORE_FILES;
|
||
|
}
|
||
|
|
||
|
static DWORD DoMPQSearch_FileTable(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData, TMPQArchive * ha)
|
||
|
{
|
||
|
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
|
||
|
TFileEntry * pFileEntry;
|
||
|
|
||
|
// Parse the file table
|
||
|
for(pFileEntry = ha->pFileTable + hs->dwNextIndex; pFileEntry < pFileTableEnd; pFileEntry++)
|
||
|
{
|
||
|
// Increment the next index for subsequent search
|
||
|
hs->dwNextIndex++;
|
||
|
|
||
|
// Check if this file entry should be included in the search result
|
||
|
if(DoMPQSearch_FileEntry(hs, lpFindFileData, ha, NULL, pFileEntry))
|
||
|
return ERROR_SUCCESS;
|
||
|
}
|
||
|
|
||
|
// No more files
|
||
|
return ERROR_NO_MORE_FILES;
|
||
|
}
|
||
|
|
||
|
// Performs one MPQ search
|
||
|
static DWORD DoMPQSearch(TMPQSearch * hs, SFILE_FIND_DATA * lpFindFileData)
|
||
|
{
|
||
|
TMPQArchive * ha = hs->ha;
|
||
|
DWORD dwErrCode;
|
||
|
|
||
|
// Start searching with base MPQ
|
||
|
while(ha != NULL)
|
||
|
{
|
||
|
// If the archive has hash table, we need to use hash table
|
||
|
// in order to catch hash table index and file locale.
|
||
|
// Note: If multiple hash table entries, point to the same block entry,
|
||
|
// we need, to report them all
|
||
|
dwErrCode = (ha->pHashTable != NULL) ? DoMPQSearch_HashTable(hs, lpFindFileData, ha)
|
||
|
: DoMPQSearch_FileTable(hs, lpFindFileData, ha);
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
return dwErrCode;
|
||
|
|
||
|
// If there is no more patches in the chain, stop it.
|
||
|
// This also keeps hs->ha non-NULL, which is required
|
||
|
// for freeing the handle later
|
||
|
if(ha->haPatch == NULL)
|
||
|
break;
|
||
|
|
||
|
// Move to the next patch in the patch chain
|
||
|
hs->ha = ha = ha->haPatch;
|
||
|
hs->dwNextIndex = 0;
|
||
|
}
|
||
|
|
||
|
// No more files found, return error
|
||
|
return ERROR_NO_MORE_FILES;
|
||
|
}
|
||
|
|
||
|
static void FreeMPQSearch(TMPQSearch *& hs)
|
||
|
{
|
||
|
if(hs != NULL)
|
||
|
{
|
||
|
if(hs->pSearchTable != NULL)
|
||
|
STORM_FREE(hs->pSearchTable);
|
||
|
STORM_FREE(hs);
|
||
|
hs = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Public functions
|
||
|
|
||
|
HANDLE WINAPI SFileFindFirstFile(HANDLE hMpq, const char * szMask, SFILE_FIND_DATA * lpFindFileData, const TCHAR * szListFile)
|
||
|
{
|
||
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
||
|
TMPQSearch * hs = NULL;
|
||
|
size_t nSize = 0;
|
||
|
DWORD dwErrCode = ERROR_SUCCESS;
|
||
|
|
||
|
// Check for the valid parameters
|
||
|
if(!IsValidMpqHandle(hMpq))
|
||
|
dwErrCode = ERROR_INVALID_HANDLE;
|
||
|
if(szMask == NULL || lpFindFileData == NULL)
|
||
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
// Include the listfile into the MPQ's internal listfile
|
||
|
// Note that if the listfile name is NULL, do nothing because the
|
||
|
// internal listfile is always included.
|
||
|
if(dwErrCode == ERROR_SUCCESS && szListFile != NULL && *szListFile != 0)
|
||
|
dwErrCode = SFileAddListFile((HANDLE)ha, szListFile);
|
||
|
|
||
|
// Allocate the structure for MPQ search
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
{
|
||
|
nSize = sizeof(TMPQSearch) + strlen(szMask) + 1;
|
||
|
if((hs = (TMPQSearch *)STORM_ALLOC(char, nSize)) == NULL)
|
||
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Perform the first search
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
{
|
||
|
memset(hs, 0, sizeof(TMPQSearch));
|
||
|
strcpy(hs->szSearchMask, szMask);
|
||
|
hs->dwFlagMask = MPQ_FILE_EXISTS;
|
||
|
hs->ha = ha;
|
||
|
|
||
|
// If the archive is patched archive, we have to create a merge table
|
||
|
// to prevent files being repeated
|
||
|
if(ha->haPatch != NULL)
|
||
|
{
|
||
|
hs->dwSearchTableItems = GetSearchTableItems(ha);
|
||
|
hs->pSearchTable = STORM_ALLOC(TFileEntry *, hs->dwSearchTableItems);
|
||
|
hs->dwFlagMask = MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE;
|
||
|
if(hs->pSearchTable != NULL)
|
||
|
memset(hs->pSearchTable, 0, hs->dwSearchTableItems * sizeof(TFileEntry *));
|
||
|
else
|
||
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Perform first item searching
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
{
|
||
|
dwErrCode = DoMPQSearch(hs, lpFindFileData);
|
||
|
}
|
||
|
|
||
|
// Cleanup
|
||
|
if(dwErrCode != ERROR_SUCCESS)
|
||
|
{
|
||
|
FreeMPQSearch(hs);
|
||
|
SetLastError(dwErrCode);
|
||
|
}
|
||
|
|
||
|
// Return the result value
|
||
|
return (HANDLE)hs;
|
||
|
}
|
||
|
|
||
|
bool WINAPI SFileFindNextFile(HANDLE hFind, SFILE_FIND_DATA * lpFindFileData)
|
||
|
{
|
||
|
TMPQSearch * hs = IsValidSearchHandle(hFind);
|
||
|
DWORD dwErrCode = ERROR_SUCCESS;
|
||
|
|
||
|
// Check the parameters
|
||
|
if(hs == NULL)
|
||
|
dwErrCode = ERROR_INVALID_HANDLE;
|
||
|
if(lpFindFileData == NULL)
|
||
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
dwErrCode = DoMPQSearch(hs, lpFindFileData);
|
||
|
|
||
|
if(dwErrCode != ERROR_SUCCESS)
|
||
|
SetLastError(dwErrCode);
|
||
|
return (dwErrCode == ERROR_SUCCESS);
|
||
|
}
|
||
|
|
||
|
bool WINAPI SFileFindClose(HANDLE hFind)
|
||
|
{
|
||
|
TMPQSearch * hs = IsValidSearchHandle(hFind);
|
||
|
|
||
|
// Check the parameters
|
||
|
if(hs == NULL)
|
||
|
{
|
||
|
SetLastError(ERROR_INVALID_HANDLE);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
FreeMPQSearch(hs);
|
||
|
return true;
|
||
|
}
|