diff options
Diffstat (limited to 'lib/StormLib/src/SFileOpenFileEx.cpp')
| -rw-r--r-- | lib/StormLib/src/SFileOpenFileEx.cpp | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/lib/StormLib/src/SFileOpenFileEx.cpp b/lib/StormLib/src/SFileOpenFileEx.cpp new file mode 100644 index 0000000..5dc2dbf --- /dev/null +++ b/lib/StormLib/src/SFileOpenFileEx.cpp @@ -0,0 +1,418 @@ +/*****************************************************************************/ +/* 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 PtrFileLocales. 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 * PtrFileLocales, + 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(PtrFileLocales != NULL && dwLocales < dwMaxLocales) + *PtrFileLocales++ = SFILE_MAKE_LCID(pHash->Locale, pHash->Platform); + 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 = GetFileEntryLocale(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 = GetFileEntryLocale(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; +} |
