mirror of
https://github.com/mangosfour/server.git
synced 2025-12-14 07:37:01 +00:00
497 lines
18 KiB
C++
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;
|
|
}
|
|
|