/*****************************************************************************/ /* 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 "SCommon.h" /*****************************************************************************/ /* Local functions */ /*****************************************************************************/ // TODO: Test for archives > 4GB static BOOL OpenLocalFile(const char * szFileName, HANDLE * phFile) { TMPQFile * hf = NULL; HANDLE hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if(hFile != INVALID_HANDLE_VALUE) { // Allocate and initialize file handle size_t nHandleSize = sizeof(TMPQFile) + strlen(szFileName); if((hf = (TMPQFile *)ALLOCMEM(char, nHandleSize)) != NULL) { memset(hf, 0, nHandleSize); strcpy(hf->szFileName, szFileName); hf->hFile = hFile; *phFile = hf; return TRUE; } else SetLastError(ERROR_NOT_ENOUGH_MEMORY); } *phFile = NULL; return FALSE; } // TODO: Test for archives > 4GB static void FreeMPQFile(TMPQFile *& hf) { if(hf != NULL) { if(hf->hFile != INVALID_HANDLE_VALUE) CloseHandle(hf->hFile); if(hf->pdwBlockPos != NULL) FREEMEM(hf->pdwBlockPos); if(hf->pbFileBuffer != NULL) FREEMEM(hf->pbFileBuffer); FREEMEM(hf); hf = 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. // TODO: Test for archives > 4GB int WINAPI SFileEnumLocales( HANDLE hMPQ, const char * szFileName, LCID * plcLocales, DWORD * pdwMaxLocales, DWORD dwSearchScope) { TMPQArchive * ha = (TMPQArchive *)hMPQ; TMPQHash * pHash = NULL; TMPQHash * pHashEnd = NULL; DWORD dwLocales = 0; int nError = ERROR_SUCCESS; // Test the parameters if(nError == ERROR_SUCCESS) { if(!IsValidMpqHandle(ha) || pdwMaxLocales == NULL) nError = ERROR_INVALID_PARAMETER; if(dwSearchScope == SFILE_OPEN_BY_INDEX && (DWORD_PTR)szFileName > ha->pHeader->dwBlockTableSize) nError = ERROR_INVALID_PARAMETER; if(dwSearchScope != SFILE_OPEN_BY_INDEX && *szFileName == 0) nError = ERROR_INVALID_PARAMETER; } // Retrieve the hash entry for the required file if(nError == ERROR_SUCCESS) { pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; if(dwSearchScope == SFILE_OPEN_BY_INDEX) { for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) { if(pHash->dwBlockIndex == (DWORD_PTR)szFileName) break; } if(pHash == pHashEnd) pHash = NULL; } else pHash = GetHashEntry(ha, szFileName); } // If the file was not found, sorry if(nError == ERROR_SUCCESS) { if(pHash == NULL) nError = ERROR_FILE_NOT_FOUND; } // Count the entries which correspond to the same file name if(nError == ERROR_SUCCESS) { TMPQHash * pSaveHash = pHash; DWORD dwName1 = pHash->dwName1; DWORD dwName2 = pHash->dwName2; // For nameless access, return 1 locale always if(dwSearchScope == SFILE_OPEN_BY_INDEX) dwLocales++; else { while(pHash < pHashEnd && pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2) { dwLocales++; pHash++; } } pHash = pSaveHash; } // Test if there is enough space to copy the locales if(nError == ERROR_SUCCESS) { DWORD dwMaxLocales = *pdwMaxLocales; *pdwMaxLocales = dwLocales; if(dwMaxLocales < dwLocales) nError = ERROR_INSUFFICIENT_BUFFER; } // Fill all the locales if(nError == ERROR_SUCCESS) { for(DWORD i = 0; i < dwLocales; i++, pHash++) *plcLocales++ = (LCID)pHash->lcLocale; } return nError; } //----------------------------------------------------------------------------- // SFileHasFile // // hMPQ - Handle of opened MPQ archive // szFileName - Name of file to look for // TODO: Test for archives > 4GB BOOL WINAPI SFileHasFile(HANDLE hMPQ, char * szFileName) { TMPQArchive * ha = (TMPQArchive *)hMPQ; int nError = ERROR_SUCCESS; if(nError == ERROR_SUCCESS) { if(ha == NULL) nError = ERROR_INVALID_PARAMETER; if(*szFileName == 0) nError = ERROR_INVALID_PARAMETER; } // Prepare the file opening if(nError == ERROR_SUCCESS) { if(GetHashEntryEx(ha, szFileName, lcLocale) == NULL) { nError = ERROR_FILE_NOT_FOUND; } } // Cleanup if(nError != ERROR_SUCCESS) { SetLastError(nError); } return (nError == ERROR_SUCCESS); } //----------------------------------------------------------------------------- // SFileOpenFileEx // // hMPQ - Handle of opened MPQ archive // szFileName - Name of file to open // dwSearchScope - Where to search // phFile - Pointer to store opened file handle // TODO: Test for archives > 4GB BOOL WINAPI SFileOpenFileEx(HANDLE hMPQ, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile) { LARGE_INTEGER FilePos; TMPQArchive * ha = (TMPQArchive *)hMPQ; TMPQFile * hf = NULL; TMPQHash * pHash = NULL; // Hash table index TMPQBlock * pBlock = NULL; // File block TMPQBlockEx * pBlockEx = NULL; DWORD dwHashIndex = 0; // Hash table index DWORD dwBlockIndex = (DWORD)-1; // Found table index size_t nHandleSize = 0; // Memory space necessary to allocate TMPQHandle int nError = ERROR_SUCCESS; #ifdef _DEBUG // Due to increasing numbers of files in MPQs, I had to change the behavior // of opening by file index. Now, the SFILE_OPEN_BY_INDEX value of dwSearchScope // must be entered. This check will allow to find code places that are incompatible // with the new behavior. if(dwSearchScope != SFILE_OPEN_BY_INDEX && szFileName != NULL) { assert((DWORD_PTR)szFileName > 0x10000); } #endif if(nError == ERROR_SUCCESS) { if(ha == NULL && dwSearchScope == SFILE_OPEN_FROM_MPQ) nError = ERROR_INVALID_PARAMETER; if(phFile == NULL) nError = ERROR_INVALID_PARAMETER; if(dwSearchScope == SFILE_OPEN_BY_INDEX && (DWORD_PTR)szFileName > ha->pHeader->dwBlockTableSize) nError = ERROR_INVALID_PARAMETER; if(dwSearchScope != SFILE_OPEN_BY_INDEX && (szFileName == NULL || *szFileName == 0)) nError = ERROR_INVALID_PARAMETER; } // Prepare the file opening if(nError == ERROR_SUCCESS) { // When the file is given by number, ... if(dwSearchScope == SFILE_OPEN_BY_INDEX) { TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; // Set handle size to be sizeof(TMPQFile) + length of FileXXXXXXXX.xxx nHandleSize = sizeof(TMPQFile) + 20; for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++) { if((DWORD_PTR)szFileName == pHash->dwBlockIndex) { dwHashIndex = (DWORD)(pHash - ha->pHashTable); dwBlockIndex = pHash->dwBlockIndex; break; } } } else { // If we have to open a disk file if(dwSearchScope == SFILE_OPEN_LOCAL_FILE) return OpenLocalFile(szFileName, phFile); nHandleSize = sizeof(TMPQFile) + strlen(szFileName); if((pHash = GetHashEntryEx(ha, szFileName, lcLocale)) != NULL) { dwHashIndex = (DWORD)(pHash - ha->pHashTable); dwBlockIndex = pHash->dwBlockIndex; } } } // Get block index from file name and test it if(nError == ERROR_SUCCESS) { // If index was not found, or is greater than number of files, exit. if(dwBlockIndex == (DWORD)-1 || dwBlockIndex > ha->pHeader->dwBlockTableSize) nError = ERROR_FILE_NOT_FOUND; } // Get block and test if the file was not already deleted. if(nError == ERROR_SUCCESS) { // Get both block tables and file position pBlockEx = ha->pExtBlockTable + dwBlockIndex; pBlock = ha->pBlockTable + dwBlockIndex; FilePos.HighPart = pBlockEx->wFilePosHigh; FilePos.LowPart = pBlock->dwFilePos; if(FilePos.QuadPart > ha->MpqSize.QuadPart || pBlock->dwCSize > ha->MpqSize.QuadPart) nError = ERROR_FILE_CORRUPT; if((pBlock->dwFlags & MPQ_FILE_EXISTS) == 0) nError = ERROR_FILE_NOT_FOUND; if(pBlock->dwFlags & ~MPQ_FILE_VALID_FLAGS) nError = ERROR_NOT_SUPPORTED; } // Allocate file handle if(nError == ERROR_SUCCESS) { if((hf = (TMPQFile *)ALLOCMEM(char, nHandleSize)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize file handle if(nError == ERROR_SUCCESS) { memset(hf, 0, nHandleSize); hf->hFile = INVALID_HANDLE_VALUE; hf->ha = ha; hf->pBlockEx = pBlockEx; hf->pBlock = pBlock; hf->nBlocks = (hf->pBlock->dwFSize + ha->dwBlockSize - 1) / ha->dwBlockSize; hf->pHash = pHash; hf->MpqFilePos.HighPart = pBlockEx->wFilePosHigh; hf->MpqFilePos.LowPart = pBlock->dwFilePos; hf->MpqFilePos.QuadPart += ha->MpqPos.QuadPart; hf->dwHashIndex = dwHashIndex; hf->dwFileIndex = dwBlockIndex; // Allocate buffers for decompression. if(hf->pBlock->dwFlags & MPQ_FILE_COMPRESSED) { // Allocate buffer for block positions. At the begin of file are stored // DWORDs holding positions of each block relative from begin of file in the archive // As for newer MPQs, there may be one additional entry in the block table // (if the MPQ_FILE_HAS_EXTRA flag is set). // Allocate the buffer to include this DWORD as well if((hf->pdwBlockPos = ALLOCMEM(DWORD, hf->nBlocks + 2)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Decrypt file seed. Cannot be used if the file is given by index if(dwSearchScope != SFILE_OPEN_BY_INDEX) { if(hf->pBlock->dwFlags & MPQ_FILE_ENCRYPTED) { const char * szTemp = strrchr(szFileName, '\\'); strcpy(hf->szFileName, szFileName); if(szTemp != NULL) szFileName = szTemp + 1; hf->dwSeed1 = DecryptFileSeed((char *)szFileName); if(hf->pBlock->dwFlags & MPQ_FILE_FIXSEED) { hf->dwSeed1 = (hf->dwSeed1 + hf->pBlock->dwFilePos) ^ hf->pBlock->dwFSize; } } } else { // If the file is encrypted and not compressed, we cannot detect the file seed if(SFileGetFileName(hf, hf->szFileName) == FALSE) nError = GetLastError(); } } // Cleanup if(nError != ERROR_SUCCESS) { FreeMPQFile(hf); SetLastError(nError); } *phFile = hf; return (nError == ERROR_SUCCESS); } //----------------------------------------------------------------------------- // BOOL SFileCloseFile(HANDLE hFile); // TODO: Test for archives > 4GB BOOL WINAPI SFileCloseFile(HANDLE hFile) { TMPQFile * hf = (TMPQFile *)hFile; if(!IsValidFileHandle(hf)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // Set the last accessed file in the archive if(hf->ha != NULL) hf->ha->pLastFile = NULL; // Free the structure FreeMPQFile(hf); return TRUE; }