/*****************************************************************************/ /* 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; }