Shipwright/StormLib/src/SBaseCommon.cpp

2046 lines
71 KiB
C++
Raw Normal View History

/*****************************************************************************/
/* SBaseCommon.cpp Copyright (c) Ladislav Zezula 2003 */
/*---------------------------------------------------------------------------*/
/* Common functions for StormLib, used by all SFile*** modules */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 24.03.03 1.00 Lad The first version of SFileCommon.cpp */
/* 19.11.03 1.01 Dan Big endian handling */
/* 12.06.04 1.01 Lad Renamed to SCommon.cpp */
/* 06.09.10 1.01 Lad Renamed to SBaseCommon.cpp */
/*****************************************************************************/
#define __STORMLIB_SELF__
#include "StormLib.h"
#include "StormCommon.h"
char StormLibCopyright[] = "StormLib v " STORMLIB_VERSION_STRING " Copyright Ladislav Zezula 1998-2014";
//-----------------------------------------------------------------------------
// Local variables
DWORD g_dwMpqSignature = ID_MPQ; // Marker for MPQ header
DWORD g_dwHashTableKey = MPQ_KEY_HASH_TABLE; // Key for hash table
DWORD g_dwBlockTableKey = MPQ_KEY_BLOCK_TABLE; // Key for block table
LCID g_lcFileLocale = LANG_NEUTRAL; // File locale
USHORT wPlatform = 0; // File platform
//-----------------------------------------------------------------------------
// Conversion to uppercase/lowercase
// Converts ASCII characters to lowercase
// Converts slash (0x2F) to backslash (0x5C)
unsigned char AsciiToLowerTable[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
// Converts ASCII characters to uppercase
// Converts slash (0x2F) to backslash (0x5C)
unsigned char AsciiToUpperTable[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
// Converts ASCII characters to uppercase
// Does NOT convert slash (0x2F) to backslash (0x5C)
unsigned char AsciiToUpperTable_Slash[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
// 5C (\) -> 2F (/)
unsigned char AsciiToUpperTable_FS2BS[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x2F, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
// 2F (/) -> 5C (\)
unsigned char AsciiToUpperTable_BS2FS[256] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x5C,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
//-----------------------------------------------------------------------------
// Safe string functions (for ANSI builds)
char * StringCopy(char * szTarget, size_t cchTarget, const char * szSource)
{
size_t cchSource = 0;
if(cchTarget > 0)
{
cchSource = strlen(szSource);
if(cchSource >= cchTarget)
cchSource = cchTarget - 1;
memcpy(szTarget, szSource, cchSource);
szTarget[cchSource] = 0;
}
return szTarget + cchSource;
}
void StringCat(char * szTarget, size_t cchTargetMax, const char * szSource)
{
// Get the current length of the target
size_t cchTarget = strlen(szTarget);
// Copy the string to the target
if(cchTarget < cchTargetMax)
{
StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource);
}
}
void StringCreatePseudoFileName(char * szBuffer, size_t cchMaxChars, unsigned int nIndex, const char * szExtension)
{
char * szBufferEnd = szBuffer + cchMaxChars;
// "File"
szBuffer = StringCopy(szBuffer, (szBufferEnd - szBuffer), "File");
// Number
szBuffer = IntToString(szBuffer, szBufferEnd - szBuffer + 1, nIndex, 8);
// Dot
if(szBuffer < szBufferEnd)
*szBuffer++ = '.';
// Extension
while(szExtension[0] == '.')
szExtension++;
StringCopy(szBuffer, (szBufferEnd - szBuffer), szExtension);
}
//-----------------------------------------------------------------------------
// Utility functions (UNICODE) only exist in the ANSI version of the library
// In ANSI builds, TCHAR = char, so we don't need these functions implemented
#ifdef _UNICODE
void StringCopy(TCHAR * szTarget, size_t cchTarget, const char * szSource)
{
if(cchTarget > 0)
{
size_t cchSource = strlen(szSource);
if(cchSource >= cchTarget)
cchSource = cchTarget - 1;
mbstowcs(szTarget, szSource, cchSource);
szTarget[cchSource] = 0;
}
}
void StringCopy(char * szTarget, size_t cchTarget, const TCHAR * szSource)
{
if(cchTarget > 0)
{
size_t cchSource = _tcslen(szSource);
if(cchSource >= cchTarget)
cchSource = cchTarget - 1;
wcstombs(szTarget, szSource, cchSource);
szTarget[cchSource] = 0;
}
}
void StringCopy(TCHAR * szTarget, size_t cchTarget, const TCHAR * szSource)
{
if(cchTarget > 0)
{
size_t cchSource = _tcslen(szSource);
if(cchSource >= cchTarget)
cchSource = cchTarget - 1;
memcpy(szTarget, szSource, cchSource * sizeof(TCHAR));
szTarget[cchSource] = 0;
}
}
void StringCat(TCHAR * szTarget, size_t cchTargetMax, const TCHAR * szSource)
{
// Get the current length of the target
size_t cchTarget = _tcslen(szTarget);
// Copy the string to the target
if(cchTarget < cchTargetMax)
{
StringCopy(szTarget + cchTarget, (cchTargetMax - cchTarget), szSource);
}
}
#endif
//-----------------------------------------------------------------------------
// Storm hashing functions
#define STORM_BUFFER_SIZE 0x500
#define HASH_INDEX_MASK(ha) (ha->pHeader->dwHashTableSize ? (ha->pHeader->dwHashTableSize - 1) : 0)
static DWORD StormBuffer[STORM_BUFFER_SIZE]; // Buffer for the decryption engine
static bool bMpqCryptographyInitialized = false;
void InitializeMpqCryptography()
{
DWORD dwSeed = 0x00100001;
DWORD index1 = 0;
DWORD index2 = 0;
int i;
// Initialize the decryption buffer.
// Do nothing if already done.
if(bMpqCryptographyInitialized == false)
{
for(index1 = 0; index1 < 0x100; index1++)
{
for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
{
DWORD temp1, temp2;
dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
temp1 = (dwSeed & 0xFFFF) << 0x10;
dwSeed = (dwSeed * 125 + 3) % 0x2AAAAB;
temp2 = (dwSeed & 0xFFFF);
StormBuffer[index2] = (temp1 | temp2);
}
}
// Also register both MD5 and SHA1 hash algorithms
register_hash(&md5_desc);
register_hash(&sha1_desc);
// Use LibTomMath as support math library for LibTomCrypt
ltc_mp = ltm_desc;
// Don't do that again
bMpqCryptographyInitialized = true;
}
}
//
// Note: Implementation of this function in WorldEdit.exe and storm.dll
// incorrectly treats the character as signed, which leads to the
// a buffer underflow if the character in the file name >= 0x80:
// The following steps happen when *pbKey == 0xBF and dwHashType == 0x0000
// (calculating hash index)
//
// 1) Result of AsciiToUpperTable_Slash[*pbKey++] is sign-extended to 0xffffffbf
// 2) The "ch" is added to dwHashType (0xffffffbf + 0x0000 => 0xffffffbf)
// 3) The result is used as index to the StormBuffer table,
// thus dereferences a random value BEFORE the begin of StormBuffer.
//
// As result, MPQs containing files with non-ANSI characters will not work between
// various game versions and localizations. Even WorldEdit, after importing a file
// with Korean characters in the name, cannot open the file back.
//
DWORD HashString(const char * szFileName, DWORD dwHashType)
{
LPBYTE pbKey = (BYTE *)szFileName;
DWORD dwSeed1 = 0x7FED7FED;
DWORD dwSeed2 = 0xEEEEEEEE;
DWORD ch;
while(*pbKey != 0)
{
// Convert the input character to uppercase
// Convert slash (0x2F) to backslash (0x5C)
ch = AsciiToUpperTable[*pbKey++];
dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
}
return dwSeed1;
}
DWORD HashStringSlash(const char * szFileName, DWORD dwHashType)
{
LPBYTE pbKey = (BYTE *)szFileName;
DWORD dwSeed1 = 0x7FED7FED;
DWORD dwSeed2 = 0xEEEEEEEE;
DWORD ch;
while(*pbKey != 0)
{
// Convert the input character to uppercase
// DON'T convert slash (0x2F) to backslash (0x5C)
ch = AsciiToUpperTable_FS2BS[*pbKey++];
//ch = AsciiToUpperTable[*pbKey++];
dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
}
return dwSeed1;
}
DWORD HashStringSlash2(const char* szFileName, DWORD dwHashType)
{
LPBYTE pbKey = (BYTE*)szFileName;
DWORD dwSeed1 = 0x7FED7FED;
DWORD dwSeed2 = 0xEEEEEEEE;
DWORD ch;
while (*pbKey != 0)
{
// Convert the input character to uppercase
// DON'T convert slash (0x2F) to backslash (0x5C)
//ch = AsciiToUpperTable_Slash[*pbKey++];
ch = AsciiToUpperTable_BS2FS[*pbKey++];
dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
}
return dwSeed1;
}
DWORD HashStringLower(const char * szFileName, DWORD dwHashType)
{
LPBYTE pbKey = (BYTE *)szFileName;
DWORD dwSeed1 = 0x7FED7FED;
DWORD dwSeed2 = 0xEEEEEEEE;
DWORD ch;
while(*pbKey != 0)
{
// Convert the input character to lower
// DON'T convert slash (0x2F) to backslash (0x5C)
ch = AsciiToLowerTable[*pbKey++];
dwSeed1 = StormBuffer[dwHashType + ch] ^ (dwSeed1 + dwSeed2);
dwSeed2 = ch + dwSeed1 + dwSeed2 + (dwSeed2 << 5) + 3;
}
return dwSeed1;
}
//-----------------------------------------------------------------------------
// Calculates the hash table size for a given amount of files
// Returns the nearest higher power of two.
// If the value is already a power of two, returns the same value
DWORD GetNearestPowerOfTwo(DWORD dwFileCount)
{
dwFileCount --;
dwFileCount |= dwFileCount >> 1;
dwFileCount |= dwFileCount >> 2;
dwFileCount |= dwFileCount >> 4;
dwFileCount |= dwFileCount >> 8;
dwFileCount |= dwFileCount >> 16;
return dwFileCount + 1;
}
/*
DWORD GetNearestPowerOfTwo(DWORD dwFileCount)
{
DWORD dwPowerOfTwo = HASH_TABLE_SIZE_MIN;
// For zero files, there is no hash table needed
if(dwFileCount == 0)
return 0;
// Round the hash table size up to the nearest power of two
// Don't allow the hash table size go over allowed maximum
while(dwPowerOfTwo < HASH_TABLE_SIZE_MAX && dwPowerOfTwo < dwFileCount)
dwPowerOfTwo <<= 1;
return dwPowerOfTwo;
}
*/
//-----------------------------------------------------------------------------
// Calculates a Jenkin's Encrypting and decrypting MPQ file data
ULONGLONG HashStringJenkins(const char * szFileName)
{
LPBYTE pbFileName = (LPBYTE)szFileName;
char szNameBuff[0x108];
size_t nLength = 0;
unsigned int primary_hash = 1;
unsigned int secondary_hash = 2;
// Normalize the file name - convert to uppercase, and convert "/" to "\\".
if(pbFileName != NULL)
{
char * szNamePtr = szNameBuff;
char * szNameEnd = szNamePtr + sizeof(szNameBuff);
// Normalize the file name. Doesn't have to be zero terminated for hashing
while(szNamePtr < szNameEnd && pbFileName[0] != 0)
*szNamePtr++ = (char)AsciiToLowerTable[*pbFileName++];
nLength = szNamePtr - szNameBuff;
}
// Thanks Quantam for finding out what the algorithm is.
// I am really getting old for reversing large chunks of assembly
// that does hashing :-)
hashlittle2(szNameBuff, nLength, &secondary_hash, &primary_hash);
// Combine those 2 together
return ((ULONGLONG)primary_hash << 0x20) | (ULONGLONG)secondary_hash;
}
//-----------------------------------------------------------------------------
// Default flags for (attributes) and (listfile)
DWORD GetDefaultSpecialFileFlags(DWORD dwFileSize, USHORT wFormatVersion)
{
// Fixed for format 1.0
if(wFormatVersion == MPQ_FORMAT_VERSION_1)
return MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY;
// Size-dependent for formats 2.0-4.0
return (dwFileSize > 0x4000) ? (MPQ_FILE_COMPRESS | MPQ_FILE_SECTOR_CRC) : (MPQ_FILE_COMPRESS | MPQ_FILE_SINGLE_UNIT);
}
//-----------------------------------------------------------------------------
// Encrypting/Decrypting MPQ data block
static DWORD EncryptUInt32Unaligned(LPDWORD DataPointer, DWORD i, DWORD dwXorKey)
{
LPBYTE pbDataPointer = (LPBYTE)(DataPointer + i);
LPBYTE pbXorKey = (LPBYTE)(&dwXorKey);
DWORD dwValue32;
// Retrieve the value
dwValue32 = ((DWORD)pbDataPointer[0] << 0x00) |
((DWORD)pbDataPointer[1] << 0x08) |
((DWORD)pbDataPointer[2] << 0x10) |
((DWORD)pbDataPointer[3] << 0x18);
// Perform unaligned XOR
pbDataPointer[0] = (pbDataPointer[0] ^ pbXorKey[0]);
pbDataPointer[1] = (pbDataPointer[1] ^ pbXorKey[1]);
pbDataPointer[2] = (pbDataPointer[2] ^ pbXorKey[2]);
pbDataPointer[3] = (pbDataPointer[3] ^ pbXorKey[3]);
return dwValue32;
}
void EncryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
{
LPDWORD DataPointer = (LPDWORD)pvDataBlock;
DWORD dwValue32;
DWORD dwKey2 = 0xEEEEEEEE;
// Round to DWORDs
dwLength >>= 2;
// We need different approach on non-aligned buffers
if(STORMLIB_DWORD_ALIGNED(DataPointer))
{
for(DWORD i = 0; i < dwLength; i++)
{
// Modify the second key
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
// We can use 32-bit approach, when the buffer is aligned
DataPointer[i] = (dwValue32 = DataPointer[i]) ^ (dwKey1 + dwKey2);
dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
}
}
else
{
for(DWORD i = 0; i < dwLength; i++)
{
// Modify the second key
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
// The data are unaligned. Make sure we don't cause data misalignment error
dwValue32 = EncryptUInt32Unaligned(DataPointer, i, (dwKey1 + dwKey2));
dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
}
}
}
static DWORD DecryptUInt32Unaligned(LPDWORD DataPointer, DWORD i, DWORD dwXorKey)
{
LPBYTE pbDataPointer = (LPBYTE)(DataPointer + i);
LPBYTE pbXorKey = (LPBYTE)(&dwXorKey);
// Perform unaligned XOR
pbDataPointer[0] = (pbDataPointer[0] ^ pbXorKey[0]);
pbDataPointer[1] = (pbDataPointer[1] ^ pbXorKey[1]);
pbDataPointer[2] = (pbDataPointer[2] ^ pbXorKey[2]);
pbDataPointer[3] = (pbDataPointer[3] ^ pbXorKey[3]);
// Retrieve the value
return ((DWORD)pbDataPointer[0] << 0x00) |
((DWORD)pbDataPointer[1] << 0x08) |
((DWORD)pbDataPointer[2] << 0x10) |
((DWORD)pbDataPointer[3] << 0x18);
}
void DecryptMpqBlock(void * pvDataBlock, DWORD dwLength, DWORD dwKey1)
{
LPDWORD DataPointer = (LPDWORD)pvDataBlock;
DWORD dwValue32;
DWORD dwKey2 = 0xEEEEEEEE;
// Round to DWORDs
dwLength >>= 2;
// We need different approach on non-aligned buffers
if(STORMLIB_DWORD_ALIGNED(DataPointer))
{
for(DWORD i = 0; i < dwLength; i++)
{
// Modify the second key
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
// We can use 32-bit approach, when the buffer is aligned
DataPointer[i] = dwValue32 = DataPointer[i] ^ (dwKey1 + dwKey2);
dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
}
}
else
{
for(DWORD i = 0; i < dwLength; i++)
{
// Modify the second key
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
// The data are unaligned. Make sure we don't cause data misalignment error
dwValue32 = DecryptUInt32Unaligned(DataPointer, i, (dwKey1 + dwKey2));
dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
dwKey2 = dwValue32 + dwKey2 + (dwKey2 << 5) + 3;
}
}
}
/**
* Functions tries to get file decryption key. This comes from these facts
*
* - We know the decrypted value of the first DWORD in the encrypted data
* - We know the decrypted value of the second DWORD (at least aproximately)
* - There is only 256 variants of how the second key is modified
*
* The first iteration of dwKey1 and dwKey2 is this:
*
* dwKey2 = 0xEEEEEEEE + StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)]
* dwDecrypted0 = DataBlock[0] ^ (dwKey1 + dwKey2);
*
* This means:
*
* (dwKey1 + dwKey2) = DataBlock[0] ^ dwDecrypted0;
*
*/
DWORD DetectFileKeyBySectorSize(LPDWORD EncryptedData, DWORD dwSectorSize, DWORD dwDecrypted0)
{
DWORD dwDecrypted1Max = dwSectorSize + dwDecrypted0;
DWORD dwKey1PlusKey2;
DWORD DataBlock[2];
// We must have at least 2 DWORDs there to be able to decrypt something
if(dwSectorSize < 0x08)
return 0;
// Get the value of the combined encryption key
dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
// Try all 256 combinations of dwKey1
for(DWORD i = 0; i < 0x100; i++)
{
DWORD dwSaveKey1;
DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
DWORD dwKey2 = 0xEEEEEEEE;
// Modify the second key and decrypt the first DWORD
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
// Did we obtain the same value like dwDecrypted0?
if(DataBlock[0] == dwDecrypted0)
{
// Save this key value. Increment by one because
// we are decrypting sector offset table
dwSaveKey1 = dwKey1 + 1;
// Rotate both keys
dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
// Modify the second key again and decrypt the second DWORD
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
// Now compare the results
if(DataBlock[1] <= dwDecrypted1Max)
return dwSaveKey1;
}
}
// Key not found
return 0;
}
// Function tries to detect file encryption key based on expected file content
// It is the same function like before, except that we know the value of the second DWORD
DWORD DetectFileKeyByKnownContent(void * pvEncryptedData, DWORD dwDecrypted0, DWORD dwDecrypted1)
{
LPDWORD EncryptedData = (LPDWORD)pvEncryptedData;
DWORD dwKey1PlusKey2;
DWORD DataBlock[2];
// Get the value of the combined encryption key
dwKey1PlusKey2 = (EncryptedData[0] ^ dwDecrypted0) - 0xEEEEEEEE;
// Try all 256 combinations of dwKey1
for(DWORD i = 0; i < 0x100; i++)
{
DWORD dwSaveKey1;
DWORD dwKey1 = dwKey1PlusKey2 - StormBuffer[MPQ_HASH_KEY2_MIX + i];
DWORD dwKey2 = 0xEEEEEEEE;
// Modify the second key and decrypt the first DWORD
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
DataBlock[0] = EncryptedData[0] ^ (dwKey1 + dwKey2);
// Did we obtain the same value like dwDecrypted0?
if(DataBlock[0] == dwDecrypted0)
{
// Save this key value
dwSaveKey1 = dwKey1;
// Rotate both keys
dwKey1 = ((~dwKey1 << 0x15) + 0x11111111) | (dwKey1 >> 0x0B);
dwKey2 = DataBlock[0] + dwKey2 + (dwKey2 << 5) + 3;
// Modify the second key again and decrypt the second DWORD
dwKey2 += StormBuffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 0xFF)];
DataBlock[1] = EncryptedData[1] ^ (dwKey1 + dwKey2);
// Now compare the results
if(DataBlock[1] == dwDecrypted1)
return dwSaveKey1;
}
}
// Key not found
return 0;
}
DWORD DetectFileKeyByContent(void * pvEncryptedData, DWORD dwSectorSize, DWORD dwFileSize)
{
DWORD dwFileKey;
// Try to break the file encryption key as if it was a WAVE file
if(dwSectorSize >= 0x0C)
{
dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x46464952, dwFileSize - 8);
if(dwFileKey != 0)
return dwFileKey;
}
// Try to break the encryption key as if it was an EXE file
if(dwSectorSize > 0x40)
{
dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x00905A4D, 0x00000003);
if(dwFileKey != 0)
return dwFileKey;
}
// Try to break the encryption key as if it was a XML file
if(dwSectorSize > 0x04)
{
dwFileKey = DetectFileKeyByKnownContent(pvEncryptedData, 0x6D783F3C, 0x6576206C);
if(dwFileKey != 0)
return dwFileKey;
}
// Not detected, sorry
return 0;
}
DWORD DecryptFileKey(
const char * szFileName,
ULONGLONG MpqPos,
DWORD dwFileSize,
DWORD dwFlags)
{
DWORD dwFileKey;
DWORD dwMpqPos = (DWORD)MpqPos;
// File key is calculated from plain name
szFileName = GetPlainFileName(szFileName);
dwFileKey = HashString(szFileName, MPQ_HASH_FILE_KEY);
// Fix the key, if needed
if(dwFlags & MPQ_FILE_FIX_KEY)
dwFileKey = (dwFileKey + dwMpqPos) ^ dwFileSize;
// Return the key
return dwFileKey;
}
//-----------------------------------------------------------------------------
// Handle validation functions
TMPQArchive * IsValidMpqHandle(HANDLE hMpq)
{
TMPQArchive * ha = (TMPQArchive *)hMpq;
return (ha != NULL && ha->pHeader != NULL && ha->pHeader->dwID == g_dwMpqSignature) ? ha : NULL;
}
TMPQFile * IsValidFileHandle(HANDLE hFile)
{
TMPQFile * hf = (TMPQFile *)hFile;
// Must not be NULL
if(hf != NULL && hf->dwMagic == ID_MPQ_FILE)
{
// Local file handle?
if(hf->pStream != NULL)
return hf;
// Also verify the MPQ handle within the file handle
if(IsValidMpqHandle(hf->ha))
return hf;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Hash table and block table manipulation
// Attempts to search a free hash entry, or an entry whose names and locale matches
TMPQHash * FindFreeHashEntry(TMPQArchive * ha, DWORD dwStartIndex, DWORD dwName1, DWORD dwName2, LCID lcLocale)
{
TMPQHash * pDeletedEntry = NULL; // If a deleted entry was found in the continuous hash range
TMPQHash * pFreeEntry = NULL; // If a free entry was found in the continuous hash range
DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
DWORD dwIndex;
// Set the initial index
dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
// Search the hash table and return the found entries in the following priority:
// 1) <MATCHING_ENTRY>
// 2) <DELETED-ENTRY>
// 3) <FREE-ENTRY>
// 4) NULL
for(;;)
{
TMPQHash * pHash = ha->pHashTable + dwIndex;
// If we found a matching entry, return that one
if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && pHash->lcLocale == lcLocale)
return pHash;
// If we found a deleted entry, remember it but keep searching
if(pHash->dwBlockIndex == HASH_ENTRY_DELETED && pDeletedEntry == NULL)
pDeletedEntry = pHash;
// If we found a free entry, we need to stop searching
if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
{
pFreeEntry = pHash;
break;
}
// Move to the next hash entry.
// If we reached the starting entry, it's failure.
dwIndex = (dwIndex + 1) & dwHashIndexMask;
if(dwIndex == dwStartIndex)
break;
}
// If we found a deleted entry, return that one preferentially
return (pDeletedEntry != NULL) ? pDeletedEntry : pFreeEntry;
}
// Retrieves the first hash entry for the given file.
// Every locale version of a file has its own hash entry
TMPQHash * GetFirstHashEntry(TMPQArchive * ha, const char * szFileName)
{
DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
DWORD dwStartIndex = ha->pfnHashString(szFileName, MPQ_HASH_TABLE_INDEX);
DWORD dwStartIndex2 = HashStringSlash2(szFileName, MPQ_HASH_TABLE_INDEX);
DWORD dwName1 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_A);
DWORD dwName2 = ha->pfnHashString(szFileName, MPQ_HASH_NAME_B);
DWORD dwName1B = HashStringSlash2(szFileName, MPQ_HASH_NAME_A);
DWORD dwName2B = HashStringSlash2(szFileName, MPQ_HASH_NAME_B);
DWORD dwIndex;
bool isSecondTry = false;
// Set the initial index
dwStartIndex = dwIndex = (dwStartIndex & dwHashIndexMask);
// Search the hash table
for(;;)
{
TMPQHash * pHash = ha->pHashTable + dwIndex;
// If the entry matches, we found it.
if ((pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 || (pHash->dwName1 == dwName1B && pHash->dwName2 == dwName2B)) && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
return pHash;
// If that hash entry is a free entry, it means we haven't found the file
if (pHash->dwBlockIndex == HASH_ENTRY_FREE)
{
// We've tried back slashes, now let's try forward slashes
if (!isSecondTry)
{
dwStartIndex = dwIndex = (dwStartIndex2 & dwHashIndexMask);
isSecondTry = true;
continue;
}
else
return NULL;
}
// Move to the next hash entry. Stop searching
// if we got reached the original hash entry
dwIndex = (dwIndex + 1) & dwHashIndexMask;
if(dwIndex == dwStartIndex)
return NULL;
}
}
TMPQHash * GetNextHashEntry(TMPQArchive * ha, TMPQHash * pFirstHash, TMPQHash * pHash)
{
DWORD dwHashIndexMask = HASH_INDEX_MASK(ha);
DWORD dwStartIndex = (DWORD)(pFirstHash - ha->pHashTable);
DWORD dwName1 = pHash->dwName1;
DWORD dwName2 = pHash->dwName2;
DWORD dwIndex = (DWORD)(pHash - ha->pHashTable);
// Now go for any next entry that follows the pHash,
// until either free hash entry was found, or the start entry was reached
for(;;)
{
// Move to the next hash entry. Stop searching
// if we got reached the original hash entry
dwIndex = (dwIndex + 1) & dwHashIndexMask;
if(dwIndex == dwStartIndex)
return NULL;
pHash = ha->pHashTable + dwIndex;
// If the entry matches, we found it.
if(pHash->dwName1 == dwName1 && pHash->dwName2 == dwName2 && MPQ_BLOCK_INDEX(pHash) < ha->dwFileTableSize)
return pHash;
// If that hash entry is a free entry, it means we haven't found the file
if(pHash->dwBlockIndex == HASH_ENTRY_FREE)
return NULL;
}
}
// Allocates an entry in the hash table
TMPQHash * AllocateHashEntry(
TMPQArchive * ha,
TFileEntry * pFileEntry,
LCID lcLocale)
{
TMPQHash * pHash;
DWORD dwStartIndex = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_TABLE_INDEX);
DWORD dwName1 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_A);
DWORD dwName2 = ha->pfnHashString(pFileEntry->szFileName, MPQ_HASH_NAME_B);
// Attempt to find a free hash entry
pHash = FindFreeHashEntry(ha, dwStartIndex, dwName1, dwName2, lcLocale);
if(pHash != NULL)
{
// Fill the free hash entry
pHash->dwName1 = dwName1;
pHash->dwName2 = dwName2;
pHash->lcLocale = (USHORT)lcLocale;
pHash->Platform = 0;
pHash->dwBlockIndex = (DWORD)(pFileEntry - ha->pFileTable);
}
return pHash;
}
// Finds a free space in the MPQ where to store next data
// The free space begins beyond the file that is stored at the fuhrtest
// position in the MPQ. (listfile), (attributes) and (signature) are ignored,
// unless the MPQ is being flushed.
ULONGLONG FindFreeMpqSpace(TMPQArchive * ha)
{
TMPQHeader * pHeader = ha->pHeader;
TFileEntry * pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
TFileEntry * pFileEntry;
ULONGLONG FreeSpacePos = ha->pHeader->dwHeaderSize;
DWORD dwChunkCount;
//static TFileEntry* furthestFile = nullptr;
//TFileEntry* startEntry = furthestFile == nullptr ? ha->pFileTable : furthestFile;
TFileEntry* startEntry = (ha->useFreeSpaceOptimization && ha->lastFreeSpaceEntry != nullptr) ? ha->lastFreeSpaceEntry : ha->pFileTable;
// Parse the entire block table
for(pFileEntry = startEntry; pFileEntry < pFileTableEnd; pFileEntry++)
{
// Only take existing files with nonzero size
if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) && (pFileEntry->dwCmpSize != 0))
{
// If we are not saving MPQ tables, ignore internal MPQ files
if((ha->dwFlags & MPQ_FLAG_SAVING_TABLES) == 0 && IsInternalMpqFileName(pFileEntry->szFileName))
continue;
// If the end of the file is bigger than current MPQ table pos, update it
if((pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > FreeSpacePos)
{
// Get the end of the file data
FreeSpacePos = pFileEntry->ByteOffset + pFileEntry->dwCmpSize;
// Add the MD5 chunks, if present
if(pHeader->dwRawChunkSize != 0 && pFileEntry->dwCmpSize != 0)
{
dwChunkCount = ((pFileEntry->dwCmpSize - 1) / pHeader->dwRawChunkSize) + 1;
FreeSpacePos += dwChunkCount * MD5_DIGEST_SIZE;
}
ha->lastFreeSpaceEntry = pFileEntry;
//if (ha->useFreeSpaceOptimization)
//break;
}
}
}
// Give the free space position to the caller
return FreeSpacePos;
}
//-----------------------------------------------------------------------------
// Common functions - MPQ File
TMPQFile * CreateFileHandle(TMPQArchive * ha, TFileEntry * pFileEntry)
{
TMPQFile * hf;
// Allocate space for TMPQFile
hf = STORM_ALLOC(TMPQFile, 1);
if(hf != NULL)
{
// Fill the file structure
memset(hf, 0, sizeof(TMPQFile));
hf->dwMagic = ID_MPQ_FILE;
hf->pStream = NULL;
hf->ha = ha;
// If the called entered a file entry, we also copy informations from the file entry
if(ha != NULL && pFileEntry != NULL)
{
// Set the raw position and MPQ position
hf->RawFilePos = FileOffsetFromMpqOffset(ha, pFileEntry->ByteOffset);
hf->MpqFilePos = pFileEntry->ByteOffset;
// Set the data size
hf->dwDataSize = pFileEntry->dwFileSize;
hf->pFileEntry = pFileEntry;
}
}
return hf;
}
TMPQFile * CreateWritableHandle(TMPQArchive * ha, DWORD dwFileSize)
{
ULONGLONG FreeMpqSpace;
ULONGLONG TempPos;
TMPQFile * hf;
// We need to find the position in the MPQ where we save the file data
FreeMpqSpace = FindFreeMpqSpace(ha);
// When format V1, the size of the archive cannot exceed 4 GB
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
{
TempPos = FreeMpqSpace +
dwFileSize +
(ha->pHeader->dwHashTableSize * sizeof(TMPQHash)) +
(ha->dwFileTableSize * sizeof(TMPQBlock));
if((TempPos >> 32) != 0)
{
SetLastError(ERROR_DISK_FULL);
return NULL;
}
}
// Allocate the file handle
hf = CreateFileHandle(ha, NULL);
if(hf == NULL)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
// We need to find the position in the MPQ where we save the file data
hf->MpqFilePos = FreeMpqSpace;
hf->bIsWriteHandle = true;
return hf;
}
// Loads a table from MPQ.
// Can be used for hash table, block table, sector offset table or sector checksum table
void * LoadMpqTable(
TMPQArchive * ha,
ULONGLONG ByteOffset,
LPBYTE pbTableHash,
DWORD dwCompressedSize,
DWORD dwTableSize,
DWORD dwKey,
bool * pbTableIsCut)
{
ULONGLONG FileSize = 0;
LPBYTE pbCompressed = NULL;
LPBYTE pbMpqTable;
LPBYTE pbToRead;
DWORD dwBytesToRead = dwCompressedSize;
DWORD dwErrCode = ERROR_SUCCESS;
// Allocate the MPQ table
pbMpqTable = pbToRead = STORM_ALLOC(BYTE, dwTableSize);
if(pbMpqTable != NULL)
{
// Check if the MPQ table is encrypted
if(dwCompressedSize < dwTableSize)
{
// Allocate temporary buffer for holding compressed data
pbCompressed = pbToRead = STORM_ALLOC(BYTE, dwCompressedSize);
if(pbCompressed == NULL)
{
STORM_FREE(pbMpqTable);
return NULL;
}
}
// Get the file offset from which we will read the table
// Note: According to Storm.dll from Warcraft III (version 2002),
// if the hash table position is 0xFFFFFFFF, no SetFilePointer call is done
// and the table is loaded from the current file offset
if(ByteOffset == SFILE_INVALID_POS)
FileStream_GetPos(ha->pStream, &ByteOffset);
// On archives v 1.0, hash table and block table can go beyond EOF.
// Storm.dll reads as much as possible, then fills the missing part with zeros.
// Abused by Spazzler map protector which sets hash table size to 0x00100000
// Abused by NP_Protect in MPQs v4 as well
if(ha->pHeader->wFormatVersion == MPQ_FORMAT_VERSION_1)
{
// Cut the table size
FileStream_GetSize(ha->pStream, &FileSize);
if((ByteOffset + dwBytesToRead) > FileSize)
{
// Fill the extra data with zeros
dwBytesToRead = (DWORD)(FileSize - ByteOffset);
memset(pbMpqTable + dwBytesToRead, 0, (dwTableSize - dwBytesToRead));
// Give the caller information that the table was cut
if(pbTableIsCut != NULL)
pbTableIsCut[0] = true;
}
}
// If everything succeeded, read the raw table from the MPQ
if(FileStream_Read(ha->pStream, &ByteOffset, pbToRead, dwBytesToRead))
{
// Verify the MD5 of the table, if present
if(!VerifyDataBlockHash(pbToRead, dwBytesToRead, pbTableHash))
{
dwErrCode = ERROR_FILE_CORRUPT;
}
}
else
{
dwErrCode = GetLastError();
}
if(dwErrCode == ERROR_SUCCESS)
{
// First of all, decrypt the table
if(dwKey != 0)
{
BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
DecryptMpqBlock(pbToRead, dwCompressedSize, dwKey);
BSWAP_ARRAY32_UNSIGNED(pbToRead, dwCompressedSize);
}
// If the table is compressed, decompress it
if(dwCompressedSize < dwTableSize)
{
int cbOutBuffer = (int)dwTableSize;
int cbInBuffer = (int)dwCompressedSize;
if(!SCompDecompress2(pbMpqTable, &cbOutBuffer, pbCompressed, cbInBuffer))
dwErrCode = GetLastError();
}
// Make sure that the table is properly byte-swapped
BSWAP_ARRAY32_UNSIGNED(pbMpqTable, dwTableSize);
}
// If read failed, free the table and return
if(dwErrCode != ERROR_SUCCESS)
{
STORM_FREE(pbMpqTable);
pbMpqTable = NULL;
}
// Free the compression buffer, if any
if(pbCompressed != NULL)
STORM_FREE(pbCompressed);
}
// Return the MPQ table
return pbMpqTable;
}
unsigned char * AllocateMd5Buffer(
DWORD dwRawDataSize,
DWORD dwChunkSize,
LPDWORD pcbMd5Size)
{
unsigned char * md5_array;
DWORD cbMd5Size;
// Sanity check
assert(dwRawDataSize != 0);
assert(dwChunkSize != 0);
// Calculate how many MD5's we will calculate
cbMd5Size = (((dwRawDataSize - 1) / dwChunkSize) + 1) * MD5_DIGEST_SIZE;
// Allocate space for array or MD5s
md5_array = STORM_ALLOC(BYTE, cbMd5Size);
// Give the size of the MD5 array
if(pcbMd5Size != NULL)
*pcbMd5Size = cbMd5Size;
return md5_array;
}
// Allocates sector buffer and sector offset table
DWORD AllocateSectorBuffer(TMPQFile * hf)
{
TMPQArchive * ha = hf->ha;
// Caller of AllocateSectorBuffer must ensure these
assert(hf->pbFileSector == NULL);
assert(hf->pFileEntry != NULL);
assert(hf->ha != NULL);
// Don't allocate anything if the file has zero size
if(hf->pFileEntry->dwFileSize == 0 || hf->dwDataSize == 0)
return ERROR_SUCCESS;
// Determine the file sector size and allocate buffer for it
hf->dwSectorSize = (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT) ? hf->dwDataSize : ha->dwSectorSize;
hf->pbFileSector = STORM_ALLOC(BYTE, hf->dwSectorSize);
hf->dwSectorOffs = SFILE_INVALID_POS;
// Return result
return (hf->pbFileSector != NULL) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
}
// Allocates sector offset table
DWORD AllocatePatchInfo(TMPQFile * hf, bool bLoadFromFile)
{
TMPQArchive * ha = hf->ha;
DWORD dwLength = sizeof(TPatchInfo);
// The following conditions must be true
assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
assert(hf->pPatchInfo == NULL);
__AllocateAndLoadPatchInfo:
// Allocate space for patch header. Start with default size,
// and if its size if bigger, then we reload them
hf->pPatchInfo = STORM_ALLOC(TPatchInfo, 1);
if(hf->pPatchInfo == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Do we have to load the patch header from the file ?
if(bLoadFromFile)
{
// Load the patch header
if(!FileStream_Read(ha->pStream, &hf->RawFilePos, hf->pPatchInfo, dwLength))
{
// Free the patch info
STORM_FREE(hf->pPatchInfo);
hf->pPatchInfo = NULL;
return GetLastError();
}
// Perform necessary swapping
hf->pPatchInfo->dwLength = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwLength);
hf->pPatchInfo->dwFlags = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwFlags);
hf->pPatchInfo->dwDataSize = BSWAP_INT32_UNSIGNED(hf->pPatchInfo->dwDataSize);
// Verify the size of the patch header
// If it's not default size, we have to reload them
if(hf->pPatchInfo->dwLength > dwLength)
{
// Free the patch info
dwLength = hf->pPatchInfo->dwLength;
STORM_FREE(hf->pPatchInfo);
hf->pPatchInfo = NULL;
// If the length is out of all possible ranges, fail the operation
if(dwLength > 0x400)
return ERROR_FILE_CORRUPT;
goto __AllocateAndLoadPatchInfo;
}
// Patch file data size according to the patch header
hf->dwDataSize = hf->pPatchInfo->dwDataSize;
}
else
{
memset(hf->pPatchInfo, 0, dwLength);
}
// Save the final length to the patch header
hf->pPatchInfo->dwLength = dwLength;
hf->pPatchInfo->dwFlags = 0x80000000;
return ERROR_SUCCESS;
}
// Allocates sector offset table
DWORD AllocateSectorOffsets(TMPQFile * hf, bool bLoadFromFile)
{
TMPQArchive * ha = hf->ha;
TFileEntry * pFileEntry = hf->pFileEntry;
DWORD dwSectorOffsLen;
bool bSectorOffsetTableCorrupt = false;
// Caller of AllocateSectorOffsets must ensure these
assert(hf->SectorOffsets == NULL);
assert(hf->pFileEntry != NULL);
assert(hf->dwDataSize != 0);
assert(hf->ha != NULL);
// If the file is stored as single unit, just set number of sectors to 1
if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
{
hf->dwSectorCount = 1;
return ERROR_SUCCESS;
}
// Calculate the number of data sectors
// Note that this doesn't work if the file size is zero
hf->dwSectorCount = ((hf->dwDataSize - 1) / hf->dwSectorSize) + 1;
// Calculate the number of file sectors
dwSectorOffsLen = (hf->dwSectorCount + 1) * sizeof(DWORD);
// If MPQ_FILE_SECTOR_CRC flag is set, there will either be extra DWORD
// or an array of MD5's. Either way, we read at least 4 bytes more
// in order to save additional read from the file.
if(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC)
dwSectorOffsLen += sizeof(DWORD);
// Only allocate and load the table if the file is compressed
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
{
__LoadSectorOffsets:
// Allocate the sector offset table
hf->SectorOffsets = STORM_ALLOC(DWORD, (dwSectorOffsLen / sizeof(DWORD)));
if(hf->SectorOffsets == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Only read from the file if we are supposed to do so
if(bLoadFromFile)
{
ULONGLONG RawFilePos = hf->RawFilePos;
// Append the length of the patch info, if any
if(hf->pPatchInfo != NULL)
{
if((RawFilePos + hf->pPatchInfo->dwLength) < RawFilePos)
return ERROR_FILE_CORRUPT;
RawFilePos += hf->pPatchInfo->dwLength;
}
// Load the sector offsets from the file
if(!FileStream_Read(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
{
// Free the sector offsets
STORM_FREE(hf->SectorOffsets);
hf->SectorOffsets = NULL;
return GetLastError();
}
// Swap the sector positions
BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
// Decrypt loaded sector positions if necessary
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
{
// If we don't know the file key, try to find it.
if(hf->dwFileKey == 0)
{
hf->dwFileKey = DetectFileKeyBySectorSize(hf->SectorOffsets, ha->dwSectorSize, dwSectorOffsLen);
if(hf->dwFileKey == 0)
{
STORM_FREE(hf->SectorOffsets);
hf->SectorOffsets = NULL;
return ERROR_UNKNOWN_FILE_KEY;
}
}
// Decrypt sector positions
DecryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
}
//
// Validate the sector offset table
//
// Note: Some MPQ protectors put the actual file data before the sector offset table.
// In this case, the sector offsets are negative (> 0x80000000).
//
for(DWORD i = 0; i < hf->dwSectorCount; i++)
{
DWORD dwSectorOffset1 = hf->SectorOffsets[i+1];
DWORD dwSectorOffset0 = hf->SectorOffsets[i];
// Every following sector offset must be bigger than the previous one
if(dwSectorOffset1 <= dwSectorOffset0)
{
bSectorOffsetTableCorrupt = true;
break;
}
// The sector size must not be bigger than compressed file size
// Edit: Yes, but apparently, in original Storm.dll, the compressed
// size is not checked anywhere. However, we need to do this check
// in order to sector offset table malformed by MPQ protectors
if((dwSectorOffset1 - dwSectorOffset0) > ha->dwSectorSize)
{
bSectorOffsetTableCorrupt = true;
break;
}
}
// If data corruption detected, free the sector offset table
if(bSectorOffsetTableCorrupt)
{
STORM_FREE(hf->SectorOffsets);
hf->SectorOffsets = NULL;
return ERROR_FILE_CORRUPT;
}
//
// There may be various extra DWORDs loaded after the sector offset table.
// They are mostly empty on WoW release MPQs, but on MPQs from PTR,
// they contain random non-zero data. Their meaning is unknown.
//
// These extra values are, however, include in the dwCmpSize in the file
// table. We cannot ignore them, because compacting archive would fail
//
if(hf->SectorOffsets[0] > dwSectorOffsLen)
{
// MPQ protectors put some ridiculous values there. We must limit the extra bytes
if(hf->SectorOffsets[0] > (dwSectorOffsLen + 0x400))
return ERROR_FILE_CORRUPT;
// Free the old sector offset table
dwSectorOffsLen = hf->SectorOffsets[0];
STORM_FREE(hf->SectorOffsets);
goto __LoadSectorOffsets;
}
}
else
{
memset(hf->SectorOffsets, 0, dwSectorOffsLen);
hf->SectorOffsets[0] = dwSectorOffsLen;
}
}
return ERROR_SUCCESS;
}
DWORD AllocateSectorChecksums(TMPQFile * hf, bool bLoadFromFile)
{
TMPQArchive * ha = hf->ha;
TFileEntry * pFileEntry = hf->pFileEntry;
ULONGLONG RawFilePos;
DWORD dwCompressedSize = 0;
DWORD dwExpectedSize;
DWORD dwCrcOffset; // Offset of the CRC table, relative to file offset in the MPQ
DWORD dwCrcSize;
// Caller of AllocateSectorChecksums must ensure these
assert(hf->SectorChksums == NULL);
assert(hf->SectorOffsets != NULL);
assert(hf->pFileEntry != NULL);
assert(hf->ha != NULL);
// Single unit files don't have sector checksums
if(pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT)
return ERROR_SUCCESS;
// Caller must ensure that we are only called when we have sector checksums
assert(pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
//
// Older MPQs store an array of CRC32's after
// the raw file data in the MPQ.
//
// In newer MPQs, the (since Cataclysm BETA) the (attributes) file
// contains additional 32-bit values beyond the sector table.
// Their number depends on size of the (attributes), but their
// meaning is unknown. They are usually zeroed in retail game files,
// but contain some sort of checksum in BETA MPQs
//
// Does the size of the file table match with the CRC32-based checksums?
dwExpectedSize = (hf->dwSectorCount + 2) * sizeof(DWORD);
if(hf->SectorOffsets[0] != 0 && hf->SectorOffsets[0] == dwExpectedSize)
{
// If we are not loading from the MPQ file, we just allocate the sector table
// In that case, do not check any sizes
if(bLoadFromFile == false)
{
hf->SectorChksums = STORM_ALLOC(DWORD, hf->dwSectorCount);
if(hf->SectorChksums == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Fill the checksum table with zeros
memset(hf->SectorChksums, 0, hf->dwSectorCount * sizeof(DWORD));
return ERROR_SUCCESS;
}
else
{
// Is there valid size of the sector checksums?
if(hf->SectorOffsets[hf->dwSectorCount + 1] >= hf->SectorOffsets[hf->dwSectorCount])
dwCompressedSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount];
// Ignore cases when the length is too small or too big.
if(dwCompressedSize < sizeof(DWORD) || dwCompressedSize > hf->dwSectorSize)
return ERROR_SUCCESS;
// Calculate offset of the CRC table
dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
dwCrcOffset = hf->SectorOffsets[hf->dwSectorCount];
RawFilePos = CalculateRawSectorOffset(hf, dwCrcOffset);
// Now read the table from the MPQ
hf->SectorChksums = (DWORD *)LoadMpqTable(ha, RawFilePos, NULL, dwCompressedSize, dwCrcSize, 0, NULL);
if(hf->SectorChksums == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
}
}
// If the size doesn't match, we ignore sector checksums
// assert(false);
return ERROR_SUCCESS;
}
DWORD WritePatchInfo(TMPQFile * hf)
{
TMPQArchive * ha = hf->ha;
TPatchInfo * pPatchInfo = hf->pPatchInfo;
// The caller must make sure that this function is only called
// when the following is true.
assert(hf->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE);
assert(pPatchInfo != NULL);
BSWAP_ARRAY32_UNSIGNED(pPatchInfo, 3 * sizeof(DWORD));
if(!FileStream_Write(ha->pStream, &hf->RawFilePos, pPatchInfo, sizeof(TPatchInfo)))
return GetLastError();
return ERROR_SUCCESS;
}
DWORD WriteSectorOffsets(TMPQFile * hf)
{
TMPQArchive * ha = hf->ha;
TFileEntry * pFileEntry = hf->pFileEntry;
ULONGLONG RawFilePos = hf->RawFilePos;
DWORD dwSectorOffsLen;
// The caller must make sure that this function is only called
// when the following is true.
assert(hf->pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK);
assert(hf->SectorOffsets != NULL);
dwSectorOffsLen = hf->SectorOffsets[0];
// If file is encrypted, sector positions are also encrypted
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
EncryptMpqBlock(hf->SectorOffsets, dwSectorOffsLen, hf->dwFileKey - 1);
BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
// Adjust sector offset table position, if we also have patch info
if(hf->pPatchInfo != NULL)
RawFilePos += hf->pPatchInfo->dwLength;
// Write sector offsets to the archive
if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, dwSectorOffsLen))
return GetLastError();
// Not necessary, as the sector checksums
// are going to be freed when this is done.
// BSWAP_ARRAY32_UNSIGNED(hf->SectorOffsets, dwSectorOffsLen);
return ERROR_SUCCESS;
}
DWORD WriteSectorChecksums(TMPQFile * hf)
{
TMPQArchive * ha = hf->ha;
ULONGLONG RawFilePos;
TFileEntry * pFileEntry = hf->pFileEntry;
LPBYTE pbCompressed;
DWORD dwCompressedSize = 0;
DWORD dwErrCode = ERROR_SUCCESS;
DWORD dwCrcSize;
int nOutSize;
// The caller must make sure that this function is only called
// when the following is true.
assert(hf->pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC);
assert(hf->SectorOffsets != NULL);
assert(hf->SectorChksums != NULL);
// If the MPQ has MD5 of each raw data chunk,
// we leave sector offsets empty
if(ha->pHeader->dwRawChunkSize != 0)
{
hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount];
return ERROR_SUCCESS;
}
// Calculate size of the checksum array
dwCrcSize = hf->dwSectorCount * sizeof(DWORD);
// Allocate buffer for compressed sector CRCs.
pbCompressed = STORM_ALLOC(BYTE, dwCrcSize);
if(pbCompressed == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Perform the compression
BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
nOutSize = (int)dwCrcSize;
SCompCompress(pbCompressed, &nOutSize, hf->SectorChksums, (int)dwCrcSize, MPQ_COMPRESSION_ZLIB, 0, 0);
dwCompressedSize = (DWORD)nOutSize;
// Write the sector CRCs to the archive
RawFilePos = hf->RawFilePos + hf->SectorOffsets[hf->dwSectorCount];
if(hf->pPatchInfo != NULL)
RawFilePos += hf->pPatchInfo->dwLength;
if(!FileStream_Write(ha->pStream, &RawFilePos, pbCompressed, dwCompressedSize))
dwErrCode = GetLastError();
// Not necessary, as the sector checksums
// are going to be freed when this is done.
// BSWAP_ARRAY32_UNSIGNED(hf->SectorChksums, dwCrcSize);
// Store the sector CRCs
hf->SectorOffsets[hf->dwSectorCount + 1] = hf->SectorOffsets[hf->dwSectorCount] + dwCompressedSize;
pFileEntry->dwCmpSize += dwCompressedSize;
STORM_FREE(pbCompressed);
return dwErrCode;
}
DWORD WriteMemDataMD5(
TFileStream * pStream,
ULONGLONG RawDataOffs,
void * pvRawData,
DWORD dwRawDataSize,
DWORD dwChunkSize,
LPDWORD pcbTotalSize)
{
unsigned char * md5_array;
unsigned char * md5;
LPBYTE pbRawData = (LPBYTE)pvRawData;
DWORD dwBytesRemaining = dwRawDataSize;
DWORD dwMd5ArraySize = 0;
DWORD dwErrCode = ERROR_SUCCESS;
// Allocate buffer for array of MD5
md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
if(md5_array == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// For every file chunk, calculate MD5
while(dwBytesRemaining != 0)
{
// Get the remaining number of bytes to read
dwChunkSize = STORMLIB_MIN(dwBytesRemaining, dwChunkSize);
// Calculate MD5
CalculateDataBlockHash(pbRawData, dwChunkSize, md5);
md5 += MD5_DIGEST_SIZE;
// Move offset and size
dwBytesRemaining -= dwChunkSize;
pbRawData += dwChunkSize;
}
// Write the array od MD5's to the file
RawDataOffs += dwRawDataSize;
if(!FileStream_Write(pStream, &RawDataOffs, md5_array, dwMd5ArraySize))
dwErrCode = GetLastError();
// Give the caller the size of the MD5 array
if(pcbTotalSize != NULL)
*pcbTotalSize = dwRawDataSize + dwMd5ArraySize;
// Free buffers and exit
STORM_FREE(md5_array);
return dwErrCode;
}
// Writes the MD5 for each chunk of the raw file data
DWORD WriteMpqDataMD5(
TFileStream * pStream,
ULONGLONG RawDataOffs,
DWORD dwRawDataSize,
DWORD dwChunkSize)
{
unsigned char * md5_array;
unsigned char * md5;
LPBYTE pbFileChunk;
DWORD dwMd5ArraySize = 0;
DWORD dwToRead = dwRawDataSize;
DWORD dwErrCode = ERROR_SUCCESS;
// Allocate buffer for array of MD5
md5_array = md5 = AllocateMd5Buffer(dwRawDataSize, dwChunkSize, &dwMd5ArraySize);
if(md5_array == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// Allocate space for file chunk
pbFileChunk = STORM_ALLOC(BYTE, dwChunkSize);
if(pbFileChunk == NULL)
{
STORM_FREE(md5_array);
return ERROR_NOT_ENOUGH_MEMORY;
}
// For every file chunk, calculate MD5
while(dwRawDataSize != 0)
{
// Get the remaining number of bytes to read
dwToRead = STORMLIB_MIN(dwRawDataSize, dwChunkSize);
// Read the chunk
if(!FileStream_Read(pStream, &RawDataOffs, pbFileChunk, dwToRead))
{
dwErrCode = GetLastError();
break;
}
// Calculate MD5
CalculateDataBlockHash(pbFileChunk, dwToRead, md5);
md5 += MD5_DIGEST_SIZE;
// Move offset and size
RawDataOffs += dwToRead;
dwRawDataSize -= dwToRead;
}
// Write the array od MD5's to the file
if(dwErrCode == ERROR_SUCCESS)
{
if(!FileStream_Write(pStream, NULL, md5_array, dwMd5ArraySize))
dwErrCode = GetLastError();
}
// Free buffers and exit
STORM_FREE(pbFileChunk);
STORM_FREE(md5_array);
return dwErrCode;
}
// Frees the structure for MPQ file
void FreeFileHandle(TMPQFile *& hf)
{
if(hf != NULL)
{
// If we have patch file attached to this one, free it first
if(hf->hfPatch != NULL)
FreeFileHandle(hf->hfPatch);
// Then free all buffers allocated in the file structure
if(hf->pbFileData != NULL)
STORM_FREE(hf->pbFileData);
if(hf->pPatchInfo != NULL)
STORM_FREE(hf->pPatchInfo);
if(hf->SectorOffsets != NULL)
STORM_FREE(hf->SectorOffsets);
if(hf->SectorChksums != NULL)
STORM_FREE(hf->SectorChksums);
if(hf->pbFileSector != NULL)
STORM_FREE(hf->pbFileSector);
if(hf->pStream != NULL)
FileStream_Close(hf->pStream);
STORM_FREE(hf);
hf = NULL;
}
}
// Frees the MPQ archive
void FreeArchiveHandle(TMPQArchive *& ha)
{
if(ha != NULL)
{
// First of all, free the patch archive, if any
if(ha->haPatch != NULL)
FreeArchiveHandle(ha->haPatch);
// Free the patch prefix, if any
if(ha->pPatchPrefix != NULL)
STORM_FREE(ha->pPatchPrefix);
// Close the file stream
FileStream_Close(ha->pStream);
ha->pStream = NULL;
// Free the file names from the file table
if(ha->pFileTable != NULL)
{
for(DWORD i = 0; i < ha->dwFileTableSize; i++)
{
if(ha->pFileTable[i].szFileName != NULL)
STORM_FREE(ha->pFileTable[i].szFileName);
ha->pFileTable[i].szFileName = NULL;
}
// Then free all buffers allocated in the archive structure
STORM_FREE(ha->pFileTable);
}
if(ha->pHashTable != NULL)
STORM_FREE(ha->pHashTable);
if(ha->pHetTable != NULL)
FreeHetTable(ha->pHetTable);
STORM_FREE(ha);
ha = NULL;
}
}
bool IsInternalMpqFileName(const char * szFileName)
{
if(szFileName != NULL && szFileName[0] == '(')
{
if(!_stricmp(szFileName, LISTFILE_NAME) ||
!_stricmp(szFileName, ATTRIBUTES_NAME) ||
!_stricmp(szFileName, SIGNATURE_NAME))
{
return true;
}
}
return false;
}
// Verifies if the file name is a pseudo-name
bool IsPseudoFileName(const char * szFileName, DWORD * pdwFileIndex)
{
DWORD dwFileIndex = 0;
if(szFileName != NULL)
{
// Must be "File########.ext"
if(!_strnicmp(szFileName, "File", 4))
{
// Check 8 digits
for(int i = 4; i < 4+8; i++)
{
if(szFileName[i] < '0' || szFileName[i] > '9')
return false;
dwFileIndex = (dwFileIndex * 10) + (szFileName[i] - '0');
}
// An extension must follow
if(szFileName[12] == '.')
{
if(pdwFileIndex != NULL)
*pdwFileIndex = dwFileIndex;
return true;
}
}
}
// Not a pseudo-name
return false;
}
//-----------------------------------------------------------------------------
// Functions calculating and verifying the MD5 signature
bool IsValidMD5(LPBYTE pbMd5)
{
LPDWORD Md5 = (LPDWORD)pbMd5;
return ((Md5 != NULL) && (Md5[0] | Md5[1] | Md5[2] | Md5[3])) ? true : false;
}
bool IsValidSignature(LPBYTE pbSignature)
{
LPDWORD Signature = (LPDWORD)pbSignature;
DWORD SigValid = 0;
for(int i = 0; i < MPQ_WEAK_SIGNATURE_SIZE / sizeof(DWORD); i++)
SigValid |= Signature[i];
return (SigValid != 0) ? true : false;
}
bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5)
{
hash_state md5_state;
BYTE md5_digest[MD5_DIGEST_SIZE];
bool bResult = true;
// Don't verify the block if the MD5 is not valid.
if(IsValidMD5(expected_md5))
{
// Calculate the MD5 of the data block
md5_init(&md5_state);
md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
md5_done(&md5_state, md5_digest);
// Does the MD5's match?
bResult = (memcmp(md5_digest, expected_md5, MD5_DIGEST_SIZE) == 0);
}
return bResult;
}
void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash)
{
hash_state md5_state;
md5_init(&md5_state);
md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock);
md5_done(&md5_state, md5_hash);
}
//-----------------------------------------------------------------------------
// Swapping functions
#ifndef STORMLIB_LITTLE_ENDIAN
// Swaps a signed 16-bit integer
int16_t SwapInt16(uint16_t val)
{
return (val << 8) | ((val >> 8) & 0xFF);
}
// Swaps an unsigned 16-bit integer
uint16_t SwapUInt16(uint16_t val)
{
return (val << 8) | (val >> 8 );
}
// Swaps a signed 32-bit integer
int32_t SwapInt32(uint32_t val)
{
val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF );
return (val << 16) | ((val >> 16) & 0xFFFF);
}
// Swaps an unsigned 32-bit integer
uint32_t SwapUInt32(uint32_t val)
{
val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF );
return (val << 16) | (val >> 16);
}
// Swaps a signed 64-bit integer
int64_t SwapInt64(uint64_t val)
{
val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
}
// Swaps an unsigned 64-bit integer
uint64_t SwapUInt64(uint64_t val)
{
val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
return (val << 32) | (val >> 32);
}
// Swaps array of unsigned 16-bit integers
void ConvertUInt16Buffer(void * ptr, size_t length)
{
uint16_t * buffer = (uint16_t *)ptr;
uint32_t nElements = (uint32_t)(length / sizeof(uint16_t));
while(nElements-- > 0)
{
*buffer = SwapUInt16(*buffer);
buffer++;
}
}
// Swaps array of unsigned 32-bit integers
void ConvertUInt32Buffer(void * ptr, size_t length)
{
uint32_t * buffer = (uint32_t *)ptr;
uint32_t nElements = (uint32_t)(length / sizeof(uint32_t));
while(nElements-- > 0)
{
*buffer = SwapUInt32(*buffer);
buffer++;
}
}
// Swaps array of unsigned 64-bit integers
void ConvertUInt64Buffer(void * ptr, size_t length)
{
uint64_t * buffer = (uint64_t *)ptr;
uint32_t nElements = (uint32_t)(length / sizeof(uint64_t));
while(nElements-- > 0)
{
*buffer = SwapUInt64(*buffer);
buffer++;
}
}
// Swaps the TMPQHeader structure
void ConvertTMPQHeader(void *header, uint16_t version)
{
TMPQHeader * theHeader = (TMPQHeader *)header;
// Swap header part version 1
if(version >= MPQ_FORMAT_VERSION_1)
{
theHeader->dwID = SwapUInt32(theHeader->dwID);
theHeader->dwHeaderSize = SwapUInt32(theHeader->dwHeaderSize);
theHeader->dwArchiveSize = SwapUInt32(theHeader->dwArchiveSize);
theHeader->wFormatVersion = SwapUInt16(theHeader->wFormatVersion);
theHeader->wSectorSize = SwapUInt16(theHeader->wSectorSize);
theHeader->dwHashTablePos = SwapUInt32(theHeader->dwHashTablePos);
theHeader->dwBlockTablePos = SwapUInt32(theHeader->dwBlockTablePos);
theHeader->dwHashTableSize = SwapUInt32(theHeader->dwHashTableSize);
theHeader->dwBlockTableSize = SwapUInt32(theHeader->dwBlockTableSize);
}
if(version >= MPQ_FORMAT_VERSION_2)
{
theHeader->HiBlockTablePos64 = SwapUInt64(theHeader->HiBlockTablePos64);
theHeader->wHashTablePosHi = SwapUInt16(theHeader->wHashTablePosHi);
theHeader->wBlockTablePosHi = SwapUInt16(theHeader->wBlockTablePosHi);
}
if(version >= MPQ_FORMAT_VERSION_3)
{
theHeader->ArchiveSize64 = SwapUInt64(theHeader->ArchiveSize64);
theHeader->BetTablePos64 = SwapUInt64(theHeader->BetTablePos64);
theHeader->HetTablePos64 = SwapUInt64(theHeader->HetTablePos64);
}
if(version >= MPQ_FORMAT_VERSION_4)
{
theHeader->HashTableSize64 = SwapUInt64(theHeader->HashTableSize64);
theHeader->BlockTableSize64 = SwapUInt64(theHeader->BlockTableSize64);
theHeader->HiBlockTableSize64 = SwapUInt64(theHeader->HiBlockTableSize64);
theHeader->HetTableSize64 = SwapUInt64(theHeader->HetTableSize64);
theHeader->BetTableSize64 = SwapUInt64(theHeader->BetTableSize64);
}
}
#endif // STORMLIB_LITTLE_ENDIAN