mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-14 13:35:07 -05:00
419 lines
14 KiB
C++
419 lines
14 KiB
C++
|
/*****************************************************************************/
|
||
|
/* SFileOpenFileEx.cpp Copyright (c) Ladislav Zezula 2003 */
|
||
|
/*---------------------------------------------------------------------------*/
|
||
|
/* Description : */
|
||
|
/*---------------------------------------------------------------------------*/
|
||
|
/* Date Ver Who Comment */
|
||
|
/* -------- ---- --- ------- */
|
||
|
/* xx.xx.99 1.00 Lad The first version of SFileOpenFileEx.cpp */
|
||
|
/*****************************************************************************/
|
||
|
|
||
|
#define __STORMLIB_SELF__
|
||
|
#include "StormLib.h"
|
||
|
#include "StormCommon.h"
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Local functions */
|
||
|
/*****************************************************************************/
|
||
|
|
||
|
static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex)
|
||
|
{
|
||
|
TMPQHash * pHashTableEnd;
|
||
|
TMPQHash * pHash;
|
||
|
DWORD dwFirstIndex = HASH_ENTRY_FREE;
|
||
|
|
||
|
// Should only be called if the archive has hash table
|
||
|
assert(ha->pHashTable != NULL);
|
||
|
|
||
|
// Multiple hash table entries can point to the file table entry.
|
||
|
// We need to search all of them
|
||
|
pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
|
||
|
for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++)
|
||
|
{
|
||
|
if(MPQ_BLOCK_INDEX(pHash) == dwFileIndex)
|
||
|
{
|
||
|
// Duplicate hash entry found
|
||
|
if(dwFirstIndex != HASH_ENTRY_FREE)
|
||
|
return HASH_ENTRY_FREE;
|
||
|
dwFirstIndex = (DWORD)(pHash - ha->pHashTable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return the hash table entry index
|
||
|
return dwFirstIndex;
|
||
|
}
|
||
|
|
||
|
static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer)
|
||
|
{
|
||
|
TMPQNamePrefix * pPrefix;
|
||
|
|
||
|
// Are there patches in the current MPQ?
|
||
|
if(ha->dwFlags & MPQ_FLAG_PATCH)
|
||
|
{
|
||
|
// The patch prefix must be already known here
|
||
|
assert(ha->pPatchPrefix != NULL);
|
||
|
pPrefix = ha->pPatchPrefix;
|
||
|
|
||
|
// The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY"
|
||
|
// We need to remove the "OldWorld\\" prefix
|
||
|
if(!_strnicmp(szFileName, "OldWorld\\", 9))
|
||
|
szFileName += 9;
|
||
|
|
||
|
// Create the file name from the known patch entry
|
||
|
memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength);
|
||
|
strcpy(szBuffer + pPrefix->nLength, szFileName);
|
||
|
szFileName = szBuffer;
|
||
|
}
|
||
|
|
||
|
return szFileName;
|
||
|
}
|
||
|
|
||
|
static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile)
|
||
|
{
|
||
|
TFileStream * pStream;
|
||
|
TMPQFile * hf = NULL;
|
||
|
TCHAR szFileNameT[MAX_PATH];
|
||
|
|
||
|
// Convert the file name to UNICODE (if needed)
|
||
|
StringCopy(szFileNameT, _countof(szFileNameT), szFileName);
|
||
|
|
||
|
// Open the file and create the TMPQFile structure
|
||
|
pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY);
|
||
|
if(pStream != NULL)
|
||
|
{
|
||
|
// Allocate and initialize file handle
|
||
|
hf = CreateFileHandle(NULL, NULL);
|
||
|
if(hf != NULL)
|
||
|
{
|
||
|
hf->pStream = pStream;
|
||
|
*PtrFile = hf;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FileStream_Close(pStream);
|
||
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
}
|
||
|
}
|
||
|
*PtrFile = NULL;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile)
|
||
|
{
|
||
|
TMPQArchive * haBase = NULL;
|
||
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
||
|
TFileEntry * pFileEntry;
|
||
|
TMPQFile * hfPatch; // Pointer to patch file
|
||
|
TMPQFile * hfBase = NULL; // Pointer to base open file
|
||
|
TMPQFile * hf = NULL;
|
||
|
HANDLE hPatchFile;
|
||
|
char szNameBuffer[MAX_PATH];
|
||
|
|
||
|
// First of all, find the latest archive where the file is in base version
|
||
|
// (i.e. where the original, unpatched version of the file exists)
|
||
|
while(ha != NULL)
|
||
|
{
|
||
|
// If the file is there, then we remember the archive
|
||
|
pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL);
|
||
|
if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0)
|
||
|
haBase = ha;
|
||
|
|
||
|
// Move to the patch archive
|
||
|
ha = ha->haPatch;
|
||
|
}
|
||
|
|
||
|
// If we couldn't find the base file in any of the patches, it doesn't exist
|
||
|
if((ha = haBase) != NULL)
|
||
|
{
|
||
|
// Now open the base file
|
||
|
if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase))
|
||
|
{
|
||
|
// The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE
|
||
|
assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0);
|
||
|
hf = hfBase;
|
||
|
|
||
|
// Now open all patches and attach them on top of the base file
|
||
|
for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch)
|
||
|
{
|
||
|
// Prepare the file name with a correct prefix
|
||
|
if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile))
|
||
|
{
|
||
|
// Remember the new version
|
||
|
hfPatch = (TMPQFile *)hPatchFile;
|
||
|
|
||
|
// We should not find patch file
|
||
|
assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0);
|
||
|
|
||
|
// Attach the patch to the base file
|
||
|
hf->hfPatch = hfPatch;
|
||
|
hf = hfPatch;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
||
|
}
|
||
|
|
||
|
// Give the updated base MPQ
|
||
|
if(PtrFile != NULL)
|
||
|
*PtrFile = (HANDLE)hfBase;
|
||
|
return (hfBase != NULL);
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Public functions */
|
||
|
/*****************************************************************************/
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// SFileEnumLocales enums all locale versions within MPQ.
|
||
|
// Functions fills all available language identifiers on a file into the buffer
|
||
|
// pointed by plcLocales. There must be enough entries to copy the localed,
|
||
|
// otherwise the function returns ERROR_INSUFFICIENT_BUFFER.
|
||
|
|
||
|
DWORD WINAPI SFileEnumLocales(
|
||
|
HANDLE hMpq,
|
||
|
const char * szFileName,
|
||
|
LCID * PtrLocales,
|
||
|
LPDWORD PtrMaxLocales,
|
||
|
DWORD dwSearchScope)
|
||
|
{
|
||
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
||
|
TMPQHash * pFirstHash;
|
||
|
TMPQHash * pHash;
|
||
|
DWORD dwFileIndex = 0;
|
||
|
DWORD dwMaxLocales;
|
||
|
DWORD dwLocales = 0;
|
||
|
|
||
|
// Test the parameters
|
||
|
if(!IsValidMpqHandle(hMpq))
|
||
|
return ERROR_INVALID_HANDLE;
|
||
|
if(szFileName == NULL || *szFileName == 0)
|
||
|
return ERROR_INVALID_PARAMETER;
|
||
|
if(ha->pHashTable == NULL)
|
||
|
return ERROR_NOT_SUPPORTED;
|
||
|
if(PtrMaxLocales == NULL)
|
||
|
return ERROR_INVALID_PARAMETER;
|
||
|
if(IsPseudoFileName(szFileName, &dwFileIndex))
|
||
|
return ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
// Keep compiler happy
|
||
|
dwMaxLocales = PtrMaxLocales[0];
|
||
|
dwSearchScope = dwSearchScope;
|
||
|
|
||
|
// Parse all files with that name
|
||
|
pFirstHash = pHash = GetFirstHashEntry(ha, szFileName);
|
||
|
while(pHash != NULL)
|
||
|
{
|
||
|
// Put the locales to the buffer
|
||
|
if(PtrLocales != NULL && dwLocales < dwMaxLocales)
|
||
|
*PtrLocales++ = pHash->lcLocale;
|
||
|
dwLocales++;
|
||
|
|
||
|
// Get the next locale
|
||
|
pHash = GetNextHashEntry(ha, pFirstHash, pHash);
|
||
|
}
|
||
|
|
||
|
// Give the caller the number of locales and return
|
||
|
PtrMaxLocales[0] = dwLocales;
|
||
|
return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// SFileOpenFileEx
|
||
|
//
|
||
|
// hMpq - Handle of opened MPQ archive
|
||
|
// szFileName - Name of file to open
|
||
|
// dwSearchScope - Where to search
|
||
|
// PtrFile - Pointer to store opened file handle
|
||
|
|
||
|
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile)
|
||
|
{
|
||
|
TMPQArchive * ha = IsValidMpqHandle(hMpq);
|
||
|
TFileEntry * pFileEntry = NULL;
|
||
|
TMPQFile * hf = NULL;
|
||
|
DWORD dwHashIndex = HASH_ENTRY_FREE;
|
||
|
DWORD dwFileIndex = 0;
|
||
|
DWORD dwErrCode = ERROR_SUCCESS;
|
||
|
bool bOpenByIndex = false;
|
||
|
|
||
|
// Don't accept NULL pointer to file handle
|
||
|
if(szFileName == NULL || *szFileName == 0)
|
||
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
// When opening a file from MPQ, the handle must be valid
|
||
|
if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL)
|
||
|
dwErrCode = ERROR_INVALID_HANDLE;
|
||
|
|
||
|
// When not checking for existence, the pointer to file handle must be valid
|
||
|
if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL)
|
||
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
||
|
|
||
|
// Prepare the file opening
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
{
|
||
|
switch(dwSearchScope)
|
||
|
{
|
||
|
case SFILE_OPEN_FROM_MPQ:
|
||
|
case SFILE_OPEN_BASE_FILE:
|
||
|
case SFILE_OPEN_CHECK_EXISTS:
|
||
|
|
||
|
// If this MPQ has no patches, open the file from this MPQ directly
|
||
|
if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE)
|
||
|
{
|
||
|
pFileEntry = GetFileEntryLocale2(ha, szFileName, g_lcFileLocale, &dwHashIndex);
|
||
|
}
|
||
|
|
||
|
// If this MPQ is a patched archive, open the file as patched
|
||
|
else
|
||
|
{
|
||
|
return OpenPatchedFile(hMpq, szFileName, PtrFile);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SFILE_OPEN_ANY_LOCALE:
|
||
|
|
||
|
// This open option is reserved for opening MPQ internal listfile.
|
||
|
// No argument validation. Tries to open file with neutral locale first,
|
||
|
// then any other available.
|
||
|
pFileEntry = GetFileEntryLocale2(ha, szFileName, 0, &dwHashIndex);
|
||
|
break;
|
||
|
|
||
|
case SFILE_OPEN_LOCAL_FILE:
|
||
|
|
||
|
// Open a local file
|
||
|
return OpenLocalFile(szFileName, PtrFile);
|
||
|
|
||
|
default:
|
||
|
|
||
|
// Don't accept any other value
|
||
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check whether the file really exists in the MPQ
|
||
|
if(dwErrCode == ERROR_SUCCESS)
|
||
|
{
|
||
|
// If we didn't find the file, try to open it using pseudo file name ("File
|
||
|
if (pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
|
||
|
{
|
||
|
// Check the pseudo-file name ("File00000001.ext")
|
||
|
if ((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true)
|
||
|
{
|
||
|
// Get the file entry for the file
|
||
|
if (dwFileIndex < ha->dwFileTableSize)
|
||
|
{
|
||
|
pFileEntry = ha->pFileTable + dwFileIndex;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Still not found?
|
||
|
if (pFileEntry == NULL)
|
||
|
{
|
||
|
dwErrCode = ERROR_FILE_NOT_FOUND;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Perform some checks of invalid files
|
||
|
if (pFileEntry != NULL)
|
||
|
{
|
||
|
// MPQ protectors use insanely amount of fake files, often with very high size.
|
||
|
// We won't open any files whose compressed size is bigger than archive size
|
||
|
// If the file is not compressed, its size cannot be bigger than archive size
|
||
|
if ((pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) == 0 && (pFileEntry->dwFileSize > ha->FileSize))
|
||
|
{
|
||
|
dwErrCode = ERROR_FILE_CORRUPT;
|
||
|
pFileEntry = NULL;
|
||
|
}
|
||
|
|
||
|
// Ignore unknown loading flags (example: MPQ_2016_v1_WME4_4.w3x)
|
||
|
// if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS)
|
||
|
// {
|
||
|
// dwErrCode = ERROR_NOT_SUPPORTED;
|
||
|
// pFileEntry = NULL;
|
||
|
// }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Did the caller just wanted to know if the file exists?
|
||
|
if(dwErrCode == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS)
|
||
|
{
|
||
|
// Allocate file handle
|
||
|
hf = CreateFileHandle(ha, pFileEntry);
|
||
|
if(hf != NULL)
|
||
|
{
|
||
|
// Get the hash index for the file
|
||
|
if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE)
|
||
|
dwHashIndex = FindHashIndex(ha, dwFileIndex);
|
||
|
if(dwHashIndex != HASH_ENTRY_FREE)
|
||
|
hf->pHashEntry = ha->pHashTable + dwHashIndex;
|
||
|
hf->dwHashIndex = dwHashIndex;
|
||
|
|
||
|
// If the MPQ has sector CRC enabled, enable if for the file
|
||
|
if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC)
|
||
|
hf->bCheckSectorCRCs = true;
|
||
|
|
||
|
// If we know the real file name, copy it to the file entry
|
||
|
if(bOpenByIndex == false)
|
||
|
{
|
||
|
// If there is no file name yet, allocate it
|
||
|
AllocateFileName(ha, pFileEntry, szFileName);
|
||
|
|
||
|
// If the file is encrypted, we should detect the file key
|
||
|
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
|
||
|
{
|
||
|
hf->dwFileKey = DecryptFileKey(szFileName,
|
||
|
pFileEntry->ByteOffset,
|
||
|
pFileEntry->dwFileSize,
|
||
|
pFileEntry->dwFlags);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Give the file entry
|
||
|
if(PtrFile != NULL)
|
||
|
PtrFile[0] = hf;
|
||
|
|
||
|
// Return error code
|
||
|
if(dwErrCode != ERROR_SUCCESS)
|
||
|
SetLastError(dwErrCode);
|
||
|
return (dwErrCode == ERROR_SUCCESS);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// SFileHasFile
|
||
|
//
|
||
|
// hMpq - Handle of opened MPQ archive
|
||
|
// szFileName - Name of file to look for
|
||
|
|
||
|
bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName)
|
||
|
{
|
||
|
return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// bool WINAPI SFileCloseFile(HANDLE hFile);
|
||
|
|
||
|
bool WINAPI SFileCloseFile(HANDLE hFile)
|
||
|
{
|
||
|
TMPQFile * hf = (TMPQFile *)hFile;
|
||
|
|
||
|
if(!IsValidFileHandle(hFile))
|
||
|
{
|
||
|
SetLastError(ERROR_INVALID_HANDLE);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Free the structure
|
||
|
FreeFileHandle(hf);
|
||
|
return true;
|
||
|
}
|