int SFileAddFile_Finish(TMPQFile * hf) { TMPQArchive * ha = hf->ha; int nError = ERROR_SUCCESS; // Verify if the caller wrote the file properly if(hf->pBlock != NULL) { if(hf->dwFilePos != hf->pBlock->dwFSize) { nError = ERROR_CAN_NOT_COMPLETE; hf->bErrorOccured = true; } } // If all previous operations succeeded, we can update the MPQ if(!hf->bErrorOccured) { // Increment the block table size, if needed if(hf->dwBlockIndex >= ha->pHeader->dwBlockTableSize) ha->pHeader->dwBlockTableSize++; // Add the file into listfile also // Don't bother checking result, it either succeeds or not. SListFileCreateNode(ha, hf->szFileName, hf->pHash); // // Note: Don't recalculate position of MPQ tables at this point. // We merely set the flag that indicates that the MPQ tables // have been changed, and SaveMpqTables will do the work when closing the archive. // // Call the user callback, if any if(AddFileCB != NULL) AddFileCB(pvUserData, hf->pBlock->dwFSize, hf->pBlock->dwFSize, true); } else { if(hf != NULL) { // Clear the hash table entry and block table entry if(hf->pHash != NULL) memset(hf->pHash, 0xFF, sizeof(TMPQHash)); if(hf->pBlock != NULL) memset(hf->pBlock, 0, sizeof(TMPQBlock)); if(hf->pBlockEx != NULL) hf->pBlockEx->wFilePosHigh = 0; } } // Schedule to saving MPQ tables regardless of success or error ha->dwFlags |= MPQ_FLAG_CHANGED; // Clear the add file callback FreeMPQFile(hf); pvUserData = NULL; AddFileCB = NULL; return nError; }
bool WINAPI SFileCloseFile(HANDLE hFile) { TMPQFile * hf = (TMPQFile *)hFile; if (!IsValidFileHandle(hf)) { SetLastError(ERROR_INVALID_HANDLE); return false; } // Free the structure FreeMPQFile(hf); return true; }
bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HANDLE * phFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; TMPQFile * hfPatch; // Pointer to patch file TMPQFile * hfBase = NULL; // Pointer to base open file TMPQFile * hfLast; // The highest file in the chain that is not patch file TMPQFile * hf; HANDLE hPatchFile; char szPatchFileName[MAX_PATH]; // Keep this flag here for future updates dwReserved = dwReserved; // First of all, try to open the original version of the file in any of the patch chain while(ha != NULL) { // Construct the name of the patch file strcpy(szPatchFileName, ha->szPatchPrefix); strcat(szPatchFileName, szFileName); if (SFileOpenFileEx((HANDLE)ha, szPatchFileName, SFILE_OPEN_FROM_MPQ, phFile)) { hfBase = (TMPQFile *)(*phFile); break; } // Move to the next file in the patch chain ha = ha->haPatch; } // If we couldn't find the file in any of the patches, it doesn't exist hf = hfLast = hfBase; if (hf == NULL) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } // At this point, we require that the open file is not a patch if (hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { FreeMPQFile(hf); SetLastError(ERROR_FILE_NOT_FOUND); return false; } // Move to the patch MPQ ha = ha->haPatch; // Now keep going in the patch chain and open every patch file that is there while(ha != NULL) { // Construct patch file name strcpy(szPatchFileName, ha->szPatchPrefix); strcat(szPatchFileName, szFileName); if (SFileOpenFileEx((HANDLE)ha, szPatchFileName, SFILE_OPEN_FROM_MPQ, &hPatchFile)) { // Remember the new version hfPatch = (TMPQFile *)hPatchFile; // If we encountered a full replacement of the file, // we have to remember the highest full file if ((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) hfLast = hfPatch; // Set current patch tobase file and move on hf->hfPatchFile = hfPatch; hf = hfPatch; } // Move to the next patch in the chain ha = ha->haPatch; } // Now we need to free all files that are below the highest unpatched version while(hfBase != hfLast) { TMPQFile * hfNext = hfBase->hfPatchFile; // Free the file below hfBase->hfPatchFile = NULL; FreeMPQFile(hfBase); // Move the base to the next file hfBase = hfNext; } // Give the updated base MPQ if (phFile != NULL) *phFile = (HANDLE)hfBase; return true; }
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; TFileEntry * pFileEntry = NULL; TMPQFile * hf = NULL; DWORD dwBlockIndex = 0; // Found table index int nError = ERROR_SUCCESS; // Don't accept NULL pointer to file handle if (phFile == NULL) nError = ERROR_INVALID_PARAMETER; // Prepare the file opening if (nError == ERROR_SUCCESS) { switch(dwSearchScope) { case SFILE_OPEN_PATCHED_FILE: // We want to open the updated version of the file return OpenPatchedFile(hMpq, szFileName, 0, phFile); case SFILE_OPEN_FROM_MPQ: if (!IsValidMpqHandle(ha)) { nError = ERROR_INVALID_HANDLE; break; } if (szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } // First of all, check the name as-is pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if (pFileEntry != NULL) break; // If the file doesn't exist in the MPQ, check file pseudo-name ("FileXXXXXXXX.ext") if (!IsPseudoFileName(szFileName, &dwBlockIndex)) { nError = ERROR_FILE_NOT_FOUND; break; } // Set the file name to the file index and fall through szFileName = (const char *)(DWORD_PTR)dwBlockIndex; dwSearchScope = SFILE_OPEN_BY_INDEX; // No break here, fall through. case SFILE_OPEN_BY_INDEX: if (!IsValidMpqHandle(ha)) { nError = ERROR_INVALID_HANDLE; break; } // Set handle size to be sizeof(TMPQFile) + length of FileXXXXXXXX.xxx pFileEntry = GetFileEntryByIndex(ha, (DWORD)(DWORD_PTR)szFileName); if (pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; 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. dwSearchScope = SFILE_OPEN_FROM_MPQ; pFileEntry = GetFileEntryAny(ha, szFileName); if (pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; break; case SFILE_OPEN_LOCAL_FILE: if (szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } return OpenLocalFile(szFileName, phFile); default: // Don't accept any other value nError = ERROR_INVALID_PARAMETER; break; } // Quick return if something failed if (nError != ERROR_SUCCESS) { SetLastError(nError); return false; } } // Test if the file was not already deleted. if (nError == ERROR_SUCCESS) { if ((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) nError = ERROR_FILE_NOT_FOUND; if (pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS) nError = ERROR_NOT_SUPPORTED; } // Allocate file handle if (nError == ERROR_SUCCESS) { if ((hf = ALLOCMEM(TMPQFile, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize file handle if (nError == ERROR_SUCCESS) { memset(hf, 0, sizeof(TMPQFile)); hf->pFileEntry = pFileEntry; hf->dwMagic = ID_MPQ_FILE; hf->ha = ha; hf->MpqFilePos = pFileEntry->ByteOffset; hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; hf->dwDataSize = pFileEntry->dwFileSize; hf->dwHashIndex = pFileEntry->dwHashIndex; hf->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable); // If the MPQ has sector CRC enabled, enable if for the file if (ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC) hf->bCheckSectorCRCs = true; // Decrypt file key. Cannot be used if the file is given by index if (dwSearchScope == SFILE_OPEN_FROM_MPQ) { if (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) { hf->dwFileKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags); } } else { // If the file is encrypted and not compressed, we cannot detect the file key if (!SFileGetFileName(hf, NULL)) nError = GetLastError(); } } // If the file is actually a patch file, we have to load the patch file header if (nError == ERROR_SUCCESS && pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { assert(hf->PatchInfo == NULL); nError = AllocatePatchInfo(hf, true); } // Cleanup if (nError != ERROR_SUCCESS) { SetLastError(nError); FreeMPQFile(hf); } *phFile = hf; return (nError == ERROR_SUCCESS); }
static int CopyMpqFiles(TMPQArchive * ha, LPDWORD pFileKeys, TFileStream * pNewStream) { TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize; TFileEntry * pFileEntry; TMPQFile * hf = NULL; int nError = ERROR_SUCCESS; // Walk through all files and write them to the destination MPQ archive for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++) { // Copy all the file sectors // Only do that when the file has nonzero size if ((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && pFileEntry->dwFileSize != 0) { // Allocate structure for the MPQ file hf = CreateMpqFile(ha); if (hf == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Store file entry hf->pFileEntry = pFileEntry; // Set the raw file position hf->MpqFilePos = pFileEntry->ByteOffset; hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; // Set the file decryption key hf->dwFileKey = pFileKeys[pFileEntry - ha->pFileTable]; hf->dwDataSize = pFileEntry->dwFileSize; // If the file is a patch file, load the patch header if (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { nError = AllocatePatchInfo(hf, true); if (nError != ERROR_SUCCESS) break; } // Allocate buffers for file sector and sector offset table nError = AllocateSectorBuffer(hf); if (nError != ERROR_SUCCESS) break; // Also allocate sector offset table and sector checksum table nError = AllocateSectorOffsets(hf, true); if (nError != ERROR_SUCCESS) break; // Also load sector checksums, if any if (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC) { nError = AllocateSectorChecksums(hf, false); if (nError != ERROR_SUCCESS) break; } // Copy all file sectors nError = CopyMpqFileSectors(ha, hf, pNewStream); if (nError != ERROR_SUCCESS) break; // Free buffers. This also sets "hf" to NULL. FreeMPQFile(hf); } } // Cleanup and exit if (hf != NULL) FreeMPQFile(hf); return nError; }
bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * phFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; TFileEntry * pFileEntry = NULL; TMPQFile * hf = NULL; DWORD dwFileIndex = 0; bool bOpenByIndex = false; int nError = ERROR_SUCCESS; // Don't accept NULL pointer to file handle if(phFile == NULL) nError = ERROR_INVALID_PARAMETER; // Prepare the file opening if(nError == ERROR_SUCCESS) { switch(dwSearchScope) { case SFILE_OPEN_FROM_MPQ: case SFILE_OPEN_BASE_FILE: if(!IsValidMpqHandle(ha)) { nError = ERROR_INVALID_HANDLE; break; } if(szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } // Check the pseudo-file name if(IsPseudoFileName(szFileName, &dwFileIndex)) { pFileEntry = GetFileEntryByIndex(ha, dwFileIndex); bOpenByIndex = true; if(pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; } else { // If this MPQ is a patched archive, open the file as patched if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE) { // Otherwise, open the file from *this* MPQ pFileEntry = GetFileEntryLocale(ha, szFileName, lcFileLocale); if(pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; } else { return OpenPatchedFile(hMpq, szFileName, 0, phFile); } } 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 = GetFileEntryAny(ha, szFileName); if(pFileEntry == NULL) nError = ERROR_FILE_NOT_FOUND; break; case SFILE_OPEN_LOCAL_FILE: if(szFileName == NULL || *szFileName == 0) { nError = ERROR_INVALID_PARAMETER; break; } return OpenLocalFile(szFileName, phFile); default: // Don't accept any other value nError = ERROR_INVALID_PARAMETER; break; } // Quick return if something failed if(nError != ERROR_SUCCESS) { SetLastError(nError); return false; } } // Test if the file was not already deleted. if(nError == ERROR_SUCCESS) { if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) nError = ERROR_FILE_NOT_FOUND; if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS) nError = ERROR_NOT_SUPPORTED; } // Allocate file handle if(nError == ERROR_SUCCESS) { if((hf = STORM_ALLOC(TMPQFile, 1)) == NULL) nError = ERROR_NOT_ENOUGH_MEMORY; } // Initialize file handle if(nError == ERROR_SUCCESS) { memset(hf, 0, sizeof(TMPQFile)); hf->pFileEntry = pFileEntry; hf->dwMagic = ID_MPQ_FILE; hf->ha = ha; hf->MpqFilePos = pFileEntry->ByteOffset; hf->RawFilePos = ha->MpqPos + hf->MpqFilePos; hf->dwDataSize = pFileEntry->dwFileSize; // 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(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 { // Try to auto-detect the file name if(!SFileGetFileName(hf, NULL)) nError = GetLastError(); } } // If the file is actually a patch file, we have to load the patch file header if(nError == ERROR_SUCCESS && pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) { assert(hf->pPatchInfo == NULL); nError = AllocatePatchInfo(hf, true); } // Cleanup if(nError != ERROR_SUCCESS) { SetLastError(nError); FreeMPQFile(hf); } *phFile = hf; return (nError == ERROR_SUCCESS); }
// Renames the file within the archive. bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName) { TMPQArchive * ha = (TMPQArchive *)hMpq; TMPQBlock * pBlock; TMPQHash * pOldHash = NULL; // Hash entry for the original file TMPQHash * pNewHash = NULL; // Hash entry for the renamed file TMPQFile * hf; DWORD dwOldBlockIndex = 0; LCID lcSaveLocale = 0; int nError = ERROR_SUCCESS; // Test the valid parameters if(nError == ERROR_SUCCESS) { if(!IsValidMpqHandle(ha)) nError = ERROR_INVALID_HANDLE; if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0) nError = ERROR_INVALID_PARAMETER; } if(nError == ERROR_SUCCESS) { // Do not allow to rename files in MPQ open for read only if(ha->dwFlags & MPQ_FLAG_READ_ONLY) nError = ERROR_ACCESS_DENIED; // Do not allow to rename any of the internal files // Also do not allow to rename any of files to an internal file if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName)) nError = ERROR_INTERNAL_FILE; } // Get the hash table entry for the original file if(nError == ERROR_SUCCESS) { if((pOldHash = GetHashEntryExact(ha, szFileName, lcFileLocale)) == NULL) nError = ERROR_FILE_NOT_FOUND; } // Test if the file already exists in the archive if(nError == ERROR_SUCCESS) { if((pNewHash = GetHashEntryExact(ha, szNewFileName, lcFileLocale)) != NULL) nError = ERROR_ALREADY_EXISTS; } // We have to know the decryption key, otherwise we cannot re-crypt // the file after renaming if(nError == ERROR_SUCCESS) { // Save block table index and remove the hash table entry dwOldBlockIndex = pOldHash->dwBlockIndex; lcSaveLocale = pOldHash->lcLocale; pBlock = ha->pBlockTable + dwOldBlockIndex; // If the file is encrypted, we have to re-crypt the file content // with the new decryption key if(pBlock->dwFlags & MPQ_FILE_ENCRYPTED) { hf = CreateMpqFile(ha, "<renaming>"); if(hf != NULL) { hf->pHash = pOldHash; hf->pBlock = ha->pBlockTable + dwOldBlockIndex; hf->pBlockEx = ha->pExtBlockTable + dwOldBlockIndex; nError = RecryptFileData(ha, hf, szFileName, szNewFileName); FreeMPQFile(hf); } else { nError = ERROR_NOT_ENOUGH_MEMORY; } } } // Get the hash table entry for the renamed file if(nError == ERROR_SUCCESS) { SListFileRemoveNode(ha, pOldHash); pOldHash->dwName1 = 0xFFFFFFFF; pOldHash->dwName2 = 0xFFFFFFFF; pOldHash->lcLocale = 0xFFFF; pOldHash->wPlatform = 0xFFFF; pOldHash->dwBlockIndex = HASH_ENTRY_DELETED; // Note that this should always succeed; even if the hash table // was full, one entry was freed before. if((pNewHash = FindFreeHashEntry(ha, szNewFileName)) == NULL) nError = ERROR_CAN_NOT_COMPLETE; } // Save the block index and clear the hash entry if(nError == ERROR_SUCCESS) { // Copy the block table index pNewHash->dwBlockIndex = dwOldBlockIndex; pNewHash->lcLocale = (USHORT)lcSaveLocale; ha->dwFlags |= MPQ_FLAG_CHANGED; // Create new name node for the listfile nError = SListFileCreateNode(ha, szNewFileName, pNewHash); } // Resolve error and return if(nError != ERROR_SUCCESS) SetLastError(nError); return (nError == ERROR_SUCCESS); }
bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, DWORD dwReserved, HANDLE * phFile) { TMPQArchive * ha = (TMPQArchive *)hMpq; TMPQFile * hfPatch; // Pointer to patch file TMPQFile * hfBase = NULL; // Pointer to base open file TMPQFile * hfLast = NULL; // The highest file in the chain that is not patch file TMPQFile * hf = NULL; HANDLE hPatchFile; char szPrefixBuffer[MAX_PATH]; // Keep this flag here for future updates dwReserved = dwReserved; // First of all, try to open the original version of the file in any of the patch chain while(ha != NULL) { // Prepare the file name with a correct prefix if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) { // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE if((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) { hf = hfLast = hfBase; break; } SFileCloseFile((HANDLE)hfBase); } // Move to the next file in the patch chain ha = ha->haPatch; } // If we couldn't find the file in any of the patches, it doesn't exist if(hf == NULL) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } // Now keep going in the patch chain and open every patch file that is there for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) { // Prepare the file name with a correct prefix if(SFileOpenFileEx((HANDLE)ha, GetPrefixedName(ha, szFileName, szPrefixBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) { // Remember the new version hfPatch = (TMPQFile *)hPatchFile; // If we encountered a full replacement of the file, // we have to remember the highest full file if((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) hfLast = hfPatch; // Set current patch to base file and move on hf->hfPatchFile = hfPatch; hf = hfPatch; } } // Now we need to free all files that are below the highest unpatched version while(hfBase != hfLast) { TMPQFile * hfNext = hfBase->hfPatchFile; // Free the file below hfBase->hfPatchFile = NULL; FreeMPQFile(hfBase); // Move the base to the next file hfBase = hfNext; } // Give the updated base MPQ if(phFile != NULL) *phFile = (HANDLE)hfBase; return true; }
static int CopyMpqFiles(TMPQArchive * ha, DWORD * pFileKeys, TFileStream * pNewStream) { TMPQBlockEx * pBlockEx = ha->pExtBlockTable; TMPQBlock * pBlockEnd = ha->pBlockTable + ha->pHeader->dwBlockTableSize; TMPQBlock * pBlock; TMPQFile * hf = NULL; int nError = ERROR_SUCCESS; // Walk through all files and write them to the destination MPQ archive for(pBlock = ha->pBlockTable; pBlock < pBlockEnd; pBlock++, pBlockEx++) { // Copy all the file sectors // Only do that when the file has nonzero size if((pBlock->dwFlags & MPQ_FILE_EXISTS) && pBlock->dwFSize != 0) { // Allocate structure for the MPQ file hf = CreateMpqFile(ha, "<compacting>"); if(hf == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Store block positions hf->pBlockEx = pBlockEx; hf->pBlock = pBlock; // Set the raw file position hf->MpqFilePos.HighPart = hf->pBlockEx->wFilePosHigh; hf->MpqFilePos.LowPart = hf->pBlock->dwFilePos; hf->RawFilePos.QuadPart = ha->MpqPos.QuadPart + hf->MpqFilePos.LowPart; // Set the file decryption key hf->dwFileKey = pFileKeys[pBlock - ha->pBlockTable]; // Allocate buffers for file sector and sector offset table nError = AllocateSectorBuffer(hf); if(nError != ERROR_SUCCESS) break; // Also allocate sector offset table and sector checksum table nError = AllocateSectorOffsets(hf, true); if(nError != ERROR_SUCCESS) break; if(pBlock->dwFlags & MPQ_FILE_SECTOR_CRC) { nError = AllocateSectorChecksums(hf, false); if(nError != ERROR_SUCCESS) break; } // Copy all file sectors nError = CopyMpqFileSectors(ha, hf, pNewStream); if(nError != ERROR_SUCCESS) break; // Free buffers. This also sets "hf" to NULL. FreeMPQFile(hf); } } // Cleanup and exit if(hf != NULL) FreeMPQFile(hf); return nError; }