server/contrib/vmap_extractor_v2/stormlib/SFileOpenArchive.cpp

497 lines
18 KiB
C++

/*****************************************************************************/
/* SFileOpenArchive.cpp Copyright Ladislav Zezula 1999 */
/* */
/* Author : Ladislav Zezula */
/* E-mail : ladik@zezula.net */
/* WWW : www.zezula.net */
/*---------------------------------------------------------------------------*/
/* Archive functions of Storm.dll */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* xx.xx.xx 1.00 Lad The first version of SFileOpenArchive.cpp */
/* 19.11.03 1.01 Dan Big endian handling */
/*****************************************************************************/
#define __STORMLIB_SELF__
#include "StormLib.h"
#include "SCommon.h"
/*****************************************************************************/
/* Local functions */
/*****************************************************************************/
static BOOL IsAviFile(TMPQHeader * pHeader)
{
DWORD * AviHdr = (DWORD *)pHeader;
// Test for 'RIFF', 'AVI ' or 'LIST'
return (AviHdr[0] == 'FFIR' && AviHdr[2] == ' IVA' && AviHdr[3] == 'TSIL');
}
// This function gets the right positions of the hash table and the block table.
// TODO: Test for archives > 4GB
static int RelocateMpqTablePositions(TMPQArchive * ha)
{
TMPQHeader2 * pHeader = ha->pHeader;
LARGE_INTEGER FileSize;
LARGE_INTEGER TempSize;
// Get the size of the file
FileSize.LowPart = GetFileSize(ha->hFile, (LPDWORD)&FileSize.HighPart);
// Set the proper hash table position
ha->HashTablePos.HighPart = pHeader->wHashTablePosHigh;
ha->HashTablePos.LowPart = pHeader->dwHashTablePos;
ha->HashTablePos.QuadPart += ha->MpqPos.QuadPart;
if(ha->HashTablePos.QuadPart > FileSize.QuadPart)
return ERROR_BAD_FORMAT;
// Set the proper block table position
ha->BlockTablePos.HighPart = pHeader->wBlockTablePosHigh;
ha->BlockTablePos.LowPart = pHeader->dwBlockTablePos;
ha->BlockTablePos.QuadPart += ha->MpqPos.QuadPart;
if(ha->BlockTablePos.QuadPart > FileSize.QuadPart)
return ERROR_BAD_FORMAT;
// Set the proper position of the extended block table
if(pHeader->ExtBlockTablePos.QuadPart != 0)
{
ha->ExtBlockTablePos = pHeader->ExtBlockTablePos;
ha->ExtBlockTablePos.QuadPart += ha->MpqPos.QuadPart;
if(ha->ExtBlockTablePos.QuadPart > FileSize.QuadPart)
return ERROR_BAD_FORMAT;
}
// Size of MPQ archive is computed as the biggest of
// (EndOfBlockTable, EndOfHashTable, EndOfExtBlockTable)
TempSize.QuadPart = ha->HashTablePos.QuadPart + (pHeader->dwHashTableSize * sizeof(TMPQHash));
if(TempSize.QuadPart > ha->MpqSize.QuadPart)
ha->MpqSize = TempSize;
TempSize.QuadPart = ha->BlockTablePos.QuadPart + (pHeader->dwBlockTableSize * sizeof(TMPQBlock));
if(TempSize.QuadPart > ha->MpqSize.QuadPart)
ha->MpqSize = TempSize;
TempSize.QuadPart = ha->ExtBlockTablePos.QuadPart + (pHeader->dwBlockTableSize * sizeof(TMPQBlockEx));
if(TempSize.QuadPart > ha->MpqSize.QuadPart)
ha->MpqSize = TempSize;
// MPQ size does not include the bytes before MPQ header
ha->MpqSize.QuadPart -= ha->MpqPos.QuadPart;
return ERROR_SUCCESS;
}
/*****************************************************************************/
/* Public functions */
/*****************************************************************************/
//-----------------------------------------------------------------------------
// SFileGetLocale and SFileSetLocale
// Set the locale for all neewly opened archives and files
LCID WINAPI SFileGetLocale()
{
return lcLocale;
}
LCID WINAPI SFileSetLocale(LCID lcNewLocale)
{
lcLocale = lcNewLocale;
return lcLocale;
}
//-----------------------------------------------------------------------------
// SFileOpenArchiveEx (not a public function !!!)
//
// szFileName - MPQ archive file name to open
// dwPriority - When SFileOpenFileEx called, this contains the search priority for searched archives
// dwFlags - If contains MPQ_OPEN_NO_LISTFILE, then the internal list file will not be used.
// phMPQ - Pointer to store open archive handle
BOOL SFileOpenArchiveEx(
const char * szMpqName,
DWORD dwPriority,
DWORD dwFlags,
HANDLE * phMPQ,
DWORD dwAccessMode)
{
LARGE_INTEGER TempPos;
TMPQArchive * ha = NULL; // Archive handle
HANDLE hFile = INVALID_HANDLE_VALUE;// Opened archive file handle
DWORD dwMaxBlockIndex = 0; // Maximum value of block entry
DWORD dwBlockTableSize = 0; // Block table size.
DWORD dwTransferred; // Number of bytes read
DWORD dwBytes = 0; // Number of bytes to read
int nError = ERROR_SUCCESS;
// Check the right parameters
if(nError == ERROR_SUCCESS)
{
if(szMpqName == NULL || *szMpqName == 0 || phMPQ == NULL)
nError = ERROR_INVALID_PARAMETER;
}
// Ensure that StormBuffer is allocated
if(nError == ERROR_SUCCESS)
nError = PrepareStormBuffer();
// Open the MPQ archive file
if(nError == ERROR_SUCCESS)
{
hFile = CreateFile(szMpqName, dwAccessMode, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if(hFile == INVALID_HANDLE_VALUE)
nError = GetLastError();
}
// Allocate the MPQhandle
if(nError == ERROR_SUCCESS)
{
if((ha = ALLOCMEM(TMPQArchive, 1)) == NULL)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
// Initialize handle structure and allocate structure for MPQ header
if(nError == ERROR_SUCCESS)
{
memset(ha, 0, sizeof(TMPQArchive));
strncpy(ha->szFileName, szMpqName, strlen(szMpqName));
ha->hFile = hFile;
ha->dwPriority = dwPriority;
ha->pHeader = &ha->Header;
ha->pListFile = NULL;
hFile = INVALID_HANDLE_VALUE;
}
// Find the offset of MPQ header within the file
if(nError == ERROR_SUCCESS)
{
LARGE_INTEGER SearchPos = {0};
LARGE_INTEGER MpqPos = {0};
DWORD dwHeaderID;
for(;;)
{
// Invalidate the MPQ ID and read the eventual header
SetFilePointer(ha->hFile, MpqPos.LowPart, &MpqPos.HighPart, FILE_BEGIN);
ReadFile(ha->hFile, ha->pHeader, sizeof(TMPQHeader2), &dwTransferred, NULL);
dwHeaderID = BSWAP_INT32_UNSIGNED(ha->pHeader->dwID);
// Special check : Some MPQs are actually AVI files, only with
// changed extension.
if(MpqPos.QuadPart == 0 && IsAviFile(ha->pHeader))
{
nError = ERROR_AVI_FILE;
break;
}
// If different number of bytes read, break the loop
if(dwTransferred != sizeof(TMPQHeader2))
{
nError = ERROR_BAD_FORMAT;
break;
}
// If there is the MPQ shunt signature, process it
if(dwHeaderID == ID_MPQ_SHUNT && ha->pShunt == NULL)
{
// Fill the shunt header
ha->ShuntPos = MpqPos;
ha->pShunt = &ha->Shunt;
memcpy(ha->pShunt, ha->pHeader, sizeof(TMPQShunt));
BSWAP_TMPQSHUNT(ha->pShunt);
// Set the MPQ pos and repeat the search
MpqPos.QuadPart = SearchPos.QuadPart + ha->pShunt->dwHeaderPos;
continue;
}
// There must be MPQ header signature
if(dwHeaderID == ID_MPQ)
{
BSWAP_TMPQHEADER(ha->pHeader);
// Save the position where the MPQ header has been found
ha->MpqPos = MpqPos;
// If valid signature has been found, break the loop
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
{
// W3M Map Protectors set some garbage value into the "dwHeaderSize"
// field of MPQ header. This value is apparently ignored by Storm.dll
if(ha->pHeader->dwHeaderSize != sizeof(TMPQHeader) &&
ha->pHeader->dwHeaderSize != sizeof(TMPQHeader2))
{
ha->dwFlags |= MPQ_FLAG_PROTECTED;
ha->pHeader->dwHeaderSize = sizeof(TMPQHeader);
}
if(ha->pHeader->dwHashTablePos < ha->pHeader->dwArchiveSize &&
ha->pHeader->dwBlockTablePos < ha->pHeader->dwArchiveSize)
{
break;
}
}
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_2)
{
break;
}
nError = ERROR_NOT_SUPPORTED;
break;
}
// If a MPQ shunt already has been found,
// and no MPQ header was at potision pointed by the shunt,
// then the archive is corrupt
if(ha->pShunt != NULL)
{
nError = ERROR_BAD_FORMAT;
break;
}
// Move to the next possible offset
SearchPos.QuadPart += 0x200;
MpqPos = SearchPos;
}
}
// Relocate tables position
if(nError == ERROR_SUCCESS)
{
// Clear the fields not supported in older formats
if(ha->pHeader->wFormatVersion < MPQ_FORMAT_VERSION_2)
{
ha->pHeader->ExtBlockTablePos.QuadPart = 0;
ha->pHeader->wBlockTablePosHigh = 0;
ha->pHeader->wHashTablePosHigh = 0;
}
ha->dwBlockSize = (0x200 << ha->pHeader->wBlockSize);
nError = RelocateMpqTablePositions(ha);
}
// Allocate buffers
if(nError == ERROR_SUCCESS)
{
//
// Note that the block table should be as large as the hash table
// (For later file additions).
//
// I have found a MPQ which has the block table larger than
// the hash table. We should avoid buffer overruns caused by that.
//
dwBlockTableSize = max(ha->pHeader->dwHashTableSize, ha->pHeader->dwBlockTableSize);
ha->pHashTable = ALLOCMEM(TMPQHash, ha->pHeader->dwHashTableSize);
ha->pBlockTable = ALLOCMEM(TMPQBlock, dwBlockTableSize);
ha->pExtBlockTable = ALLOCMEM(TMPQBlockEx, dwBlockTableSize);
ha->pbBlockBuffer = ALLOCMEM(BYTE, ha->dwBlockSize);
if(!ha->pHashTable || !ha->pBlockTable || !ha->pExtBlockTable || !ha->pbBlockBuffer)
nError = ERROR_NOT_ENOUGH_MEMORY;
}
// Read the hash table into memory
if(nError == ERROR_SUCCESS)
{
dwBytes = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
SetFilePointer(ha->hFile, ha->HashTablePos.LowPart, &ha->HashTablePos.HighPart, FILE_BEGIN);
ReadFile(ha->hFile, ha->pHashTable, dwBytes, &dwTransferred, NULL);
if(dwTransferred != dwBytes)
nError = ERROR_FILE_CORRUPT;
}
// Decrypt hash table and check if it is correctly decrypted
if(nError == ERROR_SUCCESS)
{
TMPQHash * pHashEnd = ha->pHashTable + ha->pHeader->dwHashTableSize;
TMPQHash * pHash;
// We have to convert the hash table from LittleEndian
BSWAP_ARRAY32_UNSIGNED((DWORD *)ha->pHashTable, (dwBytes / sizeof(DWORD)));
DecryptHashTable((DWORD *)ha->pHashTable, (BYTE *)"(hash table)", (ha->pHeader->dwHashTableSize * 4));
// Check hash table if is correctly decrypted
for(pHash = ha->pHashTable; pHash < pHashEnd; pHash++)
{
// Note: Some MPQs from World of Warcraft have wPlatform set to 0x0100.
// If not free or deleted hash entry, check for valid values
if(pHash->dwBlockIndex < HASH_ENTRY_DELETED)
{
// The block index should not be larger than size of the block table
if(pHash->dwBlockIndex > ha->pHeader->dwBlockTableSize)
{
nError = ERROR_BAD_FORMAT;
break;
}
// Remember the highest block table entry
if(pHash->dwBlockIndex > dwMaxBlockIndex)
dwMaxBlockIndex = pHash->dwBlockIndex;
}
}
}
// Now, read the block table
if(nError == ERROR_SUCCESS)
{
memset(ha->pBlockTable, 0, dwBlockTableSize * sizeof(TMPQBlock));
dwBytes = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock);
SetFilePointer(ha->hFile, ha->BlockTablePos.LowPart, &ha->BlockTablePos.HighPart, FILE_BEGIN);
ReadFile(ha->hFile, ha->pBlockTable, dwBytes, &dwTransferred, NULL);
// We have to convert every DWORD in ha->block from LittleEndian
BSWAP_ARRAY32_UNSIGNED((DWORD *)ha->pBlockTable, dwBytes / sizeof(DWORD));
if(dwTransferred != dwBytes)
nError = ERROR_FILE_CORRUPT;
}
// Decrypt block table.
// Some MPQs don't have Decrypted block table, e.g. cracked Diablo version
// We have to check if block table is really encrypted
if(nError == ERROR_SUCCESS)
{
TMPQBlock * pBlockEnd = ha->pBlockTable + ha->pHeader->dwBlockTableSize;
TMPQBlock * pBlock = ha->pBlockTable;
BOOL bBlockTableEncrypted = FALSE;
// Verify all blocks entries in the table
// The loop usually stops at the first entry
while(pBlock < pBlockEnd)
{
// The lower 8 bits of the MPQ flags are always zero.
// Note that this may change in next MPQ versions
if(pBlock->dwFlags & 0x000000FF)
{
bBlockTableEncrypted = TRUE;
break;
}
// Move to the next block table entry
pBlock++;
}
if(bBlockTableEncrypted)
{
DecryptBlockTable((DWORD *)ha->pBlockTable,
(BYTE *)"(block table)",
(ha->pHeader->dwBlockTableSize * 4));
}
}
// Now, read the extended block table.
// For V1 archives, we still will maintain the extended block table
// (it will be filled with zeros)
// TODO: Test with >4GB
if(nError == ERROR_SUCCESS)
{
memset(ha->pExtBlockTable, 0, dwBlockTableSize * sizeof(TMPQBlockEx));
if(ha->pHeader->ExtBlockTablePos.QuadPart != 0)
{
dwBytes = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlockEx);
SetFilePointer(ha->hFile,
ha->ExtBlockTablePos.LowPart,
&ha->ExtBlockTablePos.HighPart,
FILE_BEGIN);
ReadFile(ha->hFile, ha->pExtBlockTable, dwBytes, &dwTransferred, NULL);
// We have to convert every DWORD in ha->block from LittleEndian
BSWAP_ARRAY16_UNSIGNED((USHORT *)ha->pExtBlockTable, dwBytes / sizeof(USHORT));
// The extended block table is not encrypted (so far)
if(dwTransferred != dwBytes)
nError = ERROR_FILE_CORRUPT;
}
}
// Verify the both block tables (If the MPQ file is not protected)
if(nError == ERROR_SUCCESS && (ha->dwFlags & MPQ_FLAG_PROTECTED) == 0)
{
TMPQBlockEx * pBlockEx = ha->pExtBlockTable;
TMPQBlock * pBlockEnd = ha->pBlockTable + dwMaxBlockIndex + 1;
TMPQBlock * pBlock = ha->pBlockTable;
// If the MPQ file is not protected,
// we will check if all sizes in the block table is correct.
// Note that we will not relocate the block table (change from previous versions)
for(; pBlock < pBlockEnd; pBlock++, pBlockEx++)
{
if(pBlock->dwFlags & MPQ_FILE_EXISTS)
{
// Get the 64-bit file position
TempPos.HighPart = pBlockEx->wFilePosHigh;
TempPos.LowPart = pBlock->dwFilePos;
if(TempPos.QuadPart > ha->MpqSize.QuadPart || pBlock->dwCSize > ha->MpqSize.QuadPart)
{
nError = ERROR_BAD_FORMAT;
break;
}
}
}
}
// If the user didn't specified otherwise,
// include the internal listfile to the TMPQArchive structure
if((dwFlags & MPQ_OPEN_NO_LISTFILE) == 0)
{
if(nError == ERROR_SUCCESS)
SListFileCreateListFile(ha);
// Add the internal listfile
if(nError == ERROR_SUCCESS)
SFileAddListFile((HANDLE)ha, NULL);
}
// Cleanup and exit
if(nError != ERROR_SUCCESS)
{
FreeMPQArchive(ha);
if(hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
SetLastError(nError);
}
else
{
if(pFirstOpen == NULL)
pFirstOpen = ha;
}
*phMPQ = ha;
return (nError == ERROR_SUCCESS);
}
BOOL WINAPI SFileOpenArchive(const char * szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE * phMPQ)
{
return SFileOpenArchiveEx(szMpqName, dwPriority, dwFlags, phMPQ, GENERIC_READ);
}
//-----------------------------------------------------------------------------
// BOOL SFileCloseArchive(HANDLE hMPQ);
//
// TODO: Test for archives > 4GB
BOOL WINAPI SFileCloseArchive(HANDLE hMPQ)
{
TMPQArchive * ha = (TMPQArchive *)hMPQ;
if(!IsValidMpqHandle(ha))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if(ha->dwFlags & MPQ_FLAG_CHANGED)
{
SListFileSaveToMpq(ha);
SaveMPQTables(ha);
}
FreeMPQArchive(ha);
return TRUE;
}