/*****************************************************************************/ /* StormLibTest.cpp Copyright (c) Ladislav Zezula 2003 */ /*---------------------------------------------------------------------------*/ /* Test module for StormLib */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ /* 25.03.03 1.00 Lad The first version of StormLibTest.cpp */ /*****************************************************************************/ #define _CRT_SECURE_NO_DEPRECATE #define __INCLUDE_CRYPTOGRAPHY__ #define __STORMLIB_SELF__ // Don't use StormLib.lib #include #ifdef _MSC_VER #include #endif #include "../src/StormLib.h" #include "../src/StormCommon.h" #ifdef _MSC_VER #pragma warning(disable: 4505) // 'XXX' : unreferenced local function has been removed #pragma comment(lib, "winmm.lib") #endif #ifndef ERROR_BAD_LENGTH #define ERROR_INVALID_DATA 13L #define ERROR_BAD_LENGTH 24L #endif //------------------------------------------------------------------------------ // Defines #ifdef PLATFORM_WINDOWS #define WORK_PATH_ROOT "E:\\Multimedia\\MPQs\\" #endif #ifdef PLATFORM_LINUX #define WORK_PATH_ROOT "/home/ladik/MPQs/" #endif #ifdef PLATFORM_MAC #define WORK_PATH_ROOT "/Users/sam/StormLib/test" #endif #ifndef LANG_CZECH #define LANG_CZECH 0x0405 #endif #define MPQ_SECTOR_SIZE 0x1000 #define MAKE_PATH(path) _T(WORK_PATH_ROOT) _T(path) #define MAKE_PATHA(path) (WORK_PATH_ROOT path) //----------------------------------------------------------------------------- // Unicode MPQ names /* Czech */ static const wchar_t szUnicodeName1[] = {0x010C, 0x0065, 0x0073, 0x006B, 0x00FD, _T('.'), _T('m'), _T('p'), _T('q'), 0}; /* Russian */ static const wchar_t szUnicodeName2[] = {0x0420, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, _T('.'), _T('m'), _T('p'), _T('q'), 0}; /* Greece */ static const wchar_t szUnicodeName3[] = {0x03B5, 0x03BB, 0x03BB, 0x03B7, 0x03BD, 0x03B9, 0x03BA, 0x03AC, _T('.'), _T('m'), _T('p'), _T('q'), 0}; /* Chinese */ static const wchar_t szUnicodeName4[] = {0x65E5, 0x672C, 0x8A9E, _T('.'), _T('m'), _T('p'), _T('q'), 0}; /* Japanese */ static const wchar_t szUnicodeName5[] = {0x7B80, 0x4F53, 0x4E2D, 0x6587, _T('.'), _T('m'), _T('p'), _T('q'), 0}; /* Arabic */ static const wchar_t szUnicodeName6[] = {0x0627, 0x0644, 0x0639, 0x0639, 0x0631, 0x0628, 0x064A, 0x0629, _T('.'), _T('m'), _T('p'), _T('q'), 0}; //----------------------------------------------------------------------------- // Constants static const TCHAR * szWorkDir = MAKE_PATH("Work"); static unsigned int AddFlags[] = { // Compression Encryption Fixed key Single Unit Sector CRC 0 | 0 | 0 | 0 | 0, 0 | MPQ_FILE_ENCRYPTED | 0 | 0 | 0, 0 | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0, 0 | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0, 0 | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0, 0 | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_IMPLODE | 0 | 0 | 0 | 0, MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | 0 | 0, MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0, MPQ_FILE_IMPLODE | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_IMPLODE | 0 | 0 | 0 | MPQ_FILE_SECTOR_CRC, MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | 0 | 0 | MPQ_FILE_SECTOR_CRC, MPQ_FILE_IMPLODE | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | MPQ_FILE_SECTOR_CRC, MPQ_FILE_COMPRESS | 0 | 0 | 0 | 0, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | 0 | 0, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | 0, MPQ_FILE_COMPRESS | 0 | 0 | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | MPQ_FILE_SINGLE_UNIT | 0, MPQ_FILE_COMPRESS | 0 | 0 | 0 | MPQ_FILE_SECTOR_CRC, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | 0 | 0 | MPQ_FILE_SECTOR_CRC, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED | MPQ_FILE_FIX_KEY | 0 | MPQ_FILE_SECTOR_CRC, 0xFFFFFFFF }; //----------------------------------------------------------------------------- // Local testing functions static void clreol() { #ifdef PLATFORM_WINDOWS CONSOLE_SCREEN_BUFFER_INFO ScreenInfo; HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); LPTSTR szConsoleLine; int nConsoleChars; int i = 0; GetConsoleScreenBufferInfo(hConsole, &ScreenInfo); nConsoleChars = (ScreenInfo.srWindow.Right - ScreenInfo.srWindow.Left); if(nConsoleChars > 0) { szConsoleLine = new TCHAR[nConsoleChars + 3]; if(szConsoleLine != NULL) { szConsoleLine[i++] = '\r'; for(; i < nConsoleChars; i++) szConsoleLine[i] = ' '; szConsoleLine[i++] = '\r'; szConsoleLine[i] = 0; _tprintf(szConsoleLine); delete [] szConsoleLine; } } #endif // PLATFORM_WINDOWS } static void PrintfTA(const TCHAR * szFormat, const TCHAR * szStrT, const char * szStrA, int lcLocale = 0) { TCHAR * szTemp; TCHAR szBuffer[MAX_PATH]; // Convert ANSI string to TCHAR for(szTemp = szBuffer; *szStrA != 0; szTemp++, szStrA++) szTemp[0] = szStrA[0]; szTemp[0] = 0; _tprintf(szFormat, szStrT, szBuffer, lcLocale); } static void MergeLocalPath(TCHAR * szBuffer, const TCHAR * szPart1, const char * szPart2) { // Copy directory name while(*szPart1 != 0) *szBuffer++ = *szPart1++; // Add separator *szBuffer++ = _T('/'); // Copy file name while(*szPart2 != 0) *szBuffer++ = *szPart2++; // Terminate the string *szBuffer = 0; } int GetFirstDiffer(void * ptr1, void * ptr2, int nSize) { char * buff1 = (char *)ptr1; char * buff2 = (char *)ptr2; int nDiffer; for(nDiffer = 0; nDiffer < nSize; nDiffer++) { if(*buff1++ != *buff2++) return nDiffer; } return -1; } static void WINAPI CompactCB(void * /* lpParam */, DWORD dwWork, ULONGLONG BytesDone, ULONGLONG TotalBytes) { clreol(); _tprintf(_T("%u of %u "), (DWORD)BytesDone, (DWORD)TotalBytes); switch(dwWork) { case CCB_CHECKING_FILES: _tprintf(_T("Checking files in archive ...\r")); break; case CCB_CHECKING_HASH_TABLE: _tprintf(_T("Checking hash table ...\r")); break; case CCB_COPYING_NON_MPQ_DATA: _tprintf(_T("Copying non-MPQ data ...\r")); break; case CCB_COMPACTING_FILES: _tprintf(_T("Compacting archive ...\r")); break; case CCB_CLOSING_ARCHIVE: _tprintf(_T("Closing archive ...\r")); break; } } static void GenerateRandomDataBlock(LPBYTE pbBuffer, DWORD cbBuffer) { LPBYTE pbBufferEnd = pbBuffer + cbBuffer; LPBYTE pbPtr = pbBuffer; DWORD cbBytesToPut = 0; BYTE ByteToPut = 0; bool bRandomData = false; while(pbPtr < pbBufferEnd) { // If there are no bytes to put, we will generate new byte and length if(cbBytesToPut == 0) { bRandomData = false; switch(rand() % 10) { case 0: // A short sequence of zeros cbBytesToPut = rand() % 0x08; ByteToPut = 0; break; case 1: // A long sequence of zeros cbBytesToPut = rand() % 0x80; ByteToPut = 0; break; case 2: // A short sequence of non-zeros cbBytesToPut = rand() % 0x08; ByteToPut = (BYTE)(rand() % 0x100); break; case 3: // A long sequence of non-zeros cbBytesToPut = rand() % 0x80; ByteToPut = (BYTE)(rand() % 0x100); break; case 4: // A short random data cbBytesToPut = rand() % 0x08; bRandomData = true; break; case 5: // A long random data cbBytesToPut = rand() % 0x80; bRandomData = true; break; default: // A single random byte cbBytesToPut = 1; ByteToPut = (BYTE)(rand() % 0x100); break; } } // Generate random byte, if needed if(bRandomData) ByteToPut = (BYTE)(rand() % 0x100); // Put next byte to the output buffer *pbPtr++ = ByteToPut; cbBytesToPut--; } } static bool CompareArchivedFiles(const char * szFileName, HANDLE hFile1, HANDLE hFile2, DWORD dwBlockSize) { LPBYTE pbBuffer1 = NULL; LPBYTE pbBuffer2 = NULL; DWORD dwRead1; // Number of bytes read (Storm.dll) DWORD dwRead2; // Number of bytes read (StormLib) bool bResult1 = false; // Result from Storm.dll bool bResult2 = false; // Result from StormLib bool bResult = true; int nDiff; szFileName = szFileName; // Allocate buffers pbBuffer1 = new BYTE[dwBlockSize]; pbBuffer2 = new BYTE[dwBlockSize]; for(;;) { // Read the file's content by both methods and compare the result memset(pbBuffer1, 0, dwBlockSize); memset(pbBuffer2, 0, dwBlockSize); bResult1 = SFileReadFile(hFile1, pbBuffer1, dwBlockSize, &dwRead1, NULL); bResult2 = SFileReadFile(hFile2, pbBuffer2, dwBlockSize, &dwRead2, NULL); if(bResult1 != bResult2) { _tprintf(_T("Different results from SFileReadFile, Mpq1 %u, Mpq2 %u\n"), bResult1, bResult2); bResult = false; break; } // Test the number of bytes read if(dwRead1 != dwRead2) { _tprintf(_T("Different bytes read from SFileReadFile, Mpq1 %u, Mpq2 %u\n"), dwRead1, dwRead2); bResult = false; break; } // No more bytes ==> OK if(dwRead1 == 0) break; // Test the content if((nDiff = GetFirstDiffer(pbBuffer1, pbBuffer2, dwRead1)) != -1) { bResult = false; break; } } delete [] pbBuffer2; delete [] pbBuffer1; return bResult; } // Random read version static bool CompareArchivedFilesRR(const char * /* szFileName */, HANDLE hFile1, HANDLE hFile2, DWORD dwBlockSize) { const char * szPositions[3] = {"FILE_BEGIN ", "FILE_CURRENT", "FILE_END "}; LPBYTE pbBuffer1 = NULL; LPBYTE pbBuffer2 = NULL; DWORD dwFileSize1; // File size (Storm.dll) DWORD dwFileSize2; // File size (StormLib) DWORD dwRead1; // Number of bytes read (Storm.dll) DWORD dwRead2; // Number of bytes read (StormLib) bool bResult1 = false; // Result from Storm.dll bool bResult2 = false; // Result from StormLib int nError = ERROR_SUCCESS; // Test the file size dwFileSize1 = SFileGetFileSize(hFile1, NULL); dwFileSize2 = SFileGetFileSize(hFile2, NULL); if(dwFileSize1 != dwFileSize2) { _tprintf(_T("Different size from SFileGetFileSize (file1: %u, file2: %u)\n"), dwFileSize1, dwFileSize2); return false; } if(dwFileSize1 != 0) { for(int i = 0; i < 10000; i++) { DWORD dwRandom = rand() * rand(); DWORD dwMoveMethod = dwRandom % 3; DWORD dwPosition = dwRandom % dwFileSize1; DWORD dwToRead = dwRandom % dwBlockSize; // Also test negative seek if(rand() & 1) { int nPosition = (int)dwPosition; dwPosition = (DWORD)(-nPosition); } // Allocate buffers pbBuffer1 = new BYTE[dwToRead]; pbBuffer2 = new BYTE[dwToRead]; // Set the file pointer _tprintf(_T("RndRead (%u): pos %8i from %s, size %u ...\r"), i, dwPosition, szPositions[dwMoveMethod], dwToRead); dwRead1 = SFileSetFilePointer(hFile1, dwPosition, NULL, dwMoveMethod); dwRead2 = SFileSetFilePointer(hFile2, dwPosition, NULL, dwMoveMethod); if(dwRead1 != dwRead2) { _tprintf(_T("Difference returned by SFileSetFilePointer (file1: %u, file2: %u)\n"), dwRead1, dwRead2); nError = ERROR_CAN_NOT_COMPLETE; break; } // Read the file's content by both methods and compare the result bResult1 = SFileReadFile(hFile1, pbBuffer1, dwToRead, &dwRead1, NULL); bResult2 = SFileReadFile(hFile2, pbBuffer2, dwToRead, &dwRead2, NULL); if(bResult1 != bResult2) { _tprintf(_T("Different results from SFileReadFile (file1: %u, file2: %u)\n\n"), bResult1, bResult2); nError = ERROR_CAN_NOT_COMPLETE; break; } // Test the number of bytes read if(dwRead1 != dwRead2) { _tprintf(_T("Different bytes read from SFileReadFile (file1: %u, file2: %u)\n\n"), dwRead1, dwRead2); nError = ERROR_CAN_NOT_COMPLETE; break; } // Test the content if(dwRead1 != 0 && memcmp(pbBuffer1, pbBuffer2, dwRead1)) { _tprintf(_T("Different data content from SFileReadFile\n")); nError = ERROR_CAN_NOT_COMPLETE; break; } delete [] pbBuffer2; delete [] pbBuffer1; } } clreol(); return (nError == ERROR_SUCCESS) ? true : false; } //----------------------------------------------------------------------------- // Opening local file static int TestOpenLocalFile(const char * szFileName) { HANDLE hFile; char szRetrievedName[MAX_PATH]; if(SFileOpenFileEx(NULL, szFileName, SFILE_OPEN_LOCAL_FILE, &hFile)) { SFileGetFileName(hFile, szRetrievedName); SFileCloseFile(hFile); } return ERROR_SUCCESS; } //----------------------------------------------------------------------------- // Partial file reading static int TestPartFileRead(const TCHAR * szFileName) { ULONGLONG ByteOffset; ULONGLONG FileSize = 0; TFileStream * pStream; BYTE BigBuffer[0x7000]; BYTE Buffer[0x100]; int nError = ERROR_SUCCESS; // Open the partial file pStream = FileStream_OpenFile(szFileName, false); if(pStream == NULL) nError = GetLastError(); // Get the size of the stream if(nError == ERROR_SUCCESS) { if(!FileStream_GetSize(pStream, &FileSize)) nError = GetLastError(); } // Read the last 0x7000 bytes if(nError == ERROR_SUCCESS) { ByteOffset = FileSize - sizeof(BigBuffer); if(!FileStream_Read(pStream, &ByteOffset, BigBuffer, sizeof(BigBuffer))) nError = GetLastError(); } // Read the last 0x100 bytes if(nError == ERROR_SUCCESS) { ByteOffset = FileSize - sizeof(Buffer); if(!FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer))) nError = GetLastError(); } // Read 0x100 bytes from position (FileSize - 0xFF) if(nError == ERROR_SUCCESS) { ByteOffset = FileSize - sizeof(Buffer) + 1; if(!FileStream_Read(pStream, &ByteOffset, Buffer, sizeof(Buffer))) nError = GetLastError(); } FileStream_Close(pStream); return nError; } //----------------------------------------------------------------------------- // Compare Huffmann decompression struct TFileData { DWORD dwFileSize; BYTE FileData[1]; }; static TFileData * ReadFileContent(const TCHAR * szMpqName, const char * szFileName) { TFileData * pFileData = NULL; HANDLE hMpq = NULL; HANDLE hFile = NULL; DWORD dwSearchScope = (szMpqName == NULL) ? SFILE_OPEN_LOCAL_FILE : SFILE_OPEN_FROM_MPQ; DWORD dwBytesRead = 0; DWORD dwFileSize = 0; // Open the MPQ, if any if(szMpqName != NULL) { if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) return NULL; } // Open the file if(SFileOpenFileEx(hMpq, szFileName, dwSearchScope, &hFile)) { dwFileSize = SFileGetFileSize(hFile, NULL); pFileData = (TFileData *)(new BYTE[sizeof(TFileData) + dwFileSize]); if(pFileData != NULL) { SFileReadFile(hFile, pFileData->FileData, dwFileSize, &dwBytesRead, NULL); pFileData->dwFileSize = dwFileSize; } SFileCloseFile(hFile); } if(hMpq != NULL) SFileCloseArchive(hMpq); return pFileData; } static TFileData * CompressFileContent(TFileData * pDecompressed, BYTE Compression1, BYTE Compression2) { TFileData * pRecompressed; LPBYTE pbOutBuffer; LPBYTE pbInBuffer; DWORD dwBytesRemaining; int cbOutBuffer; int cbInBuffer; // Allocate buffer for compressed data pRecompressed = (TFileData *)(new BYTE[sizeof(TFileData) + pDecompressed->dwFileSize]); if(pRecompressed != NULL) { // Set the source and target dwBytesRemaining = pDecompressed->dwFileSize; pbInBuffer = pDecompressed->FileData; pbOutBuffer = pRecompressed->FileData; while(dwBytesRemaining != 0) { // Perform the compression cbOutBuffer = cbInBuffer = (int)STORMLIB_MIN(dwBytesRemaining, 0x1000); SCompCompress((char *)pbOutBuffer, &cbOutBuffer, (char *)pbInBuffer, cbInBuffer, Compression1, 0x00, 0); assert(cbOutBuffer < cbInBuffer); // Move buffers dwBytesRemaining -= cbInBuffer; pbOutBuffer += cbOutBuffer; pbInBuffer += cbInBuffer; Compression1 = Compression2; } // Put the size of the decompressed part pRecompressed->dwFileSize = (DWORD)(pbOutBuffer - pRecompressed->FileData); } return pRecompressed; } static int WriteFileContent(const TCHAR * szFileName, TFileData * pFileData) { TFileStream * pLocalFile = NULL; int nError = ERROR_SUCCESS; // Create the local file if(nError == ERROR_SUCCESS) { pLocalFile = FileStream_CreateFile(szFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pLocalFile == NULL) nError = GetLastError(); } // Write the file data if(nError == ERROR_SUCCESS) { if(!FileStream_Write(pLocalFile, NULL, pFileData->FileData, pFileData->dwFileSize)) nError = GetLastError(); } // Close handles and return if(pLocalFile != NULL) FileStream_Close(pLocalFile); return nError; } static int CompareHuffmanCompressions7() { TFileData * pDecompressed1 = NULL; TFileData * pDecompressed2 = NULL; TFileData * pRecompressed1 = NULL; TFileData * pRecompressed2 = NULL; int nDifference; int nError = ERROR_SUCCESS; // Load the decompressed data if(nError == ERROR_SUCCESS) { pDecompressed1 = ReadFileContent(MAKE_PATH("BroodWar.mpq"), "music\\prdyroom.wav"); pDecompressed2 = ReadFileContent(NULL, MAKE_PATHA("mpq_decompressed.wav")); if(pDecompressed1 != NULL && pDecompressed2 != NULL) { // Compare decompressed size if(pDecompressed1->dwFileSize == pDecompressed2->dwFileSize) { // Compare the data nDifference = GetFirstDiffer(pDecompressed1->FileData, pDecompressed2->FileData, pDecompressed1->dwFileSize); if(nDifference != -1) { _tprintf(_T("Different decompressed data at offset %08X\n\n"), nDifference); nError = ERROR_BAD_FORMAT; } else { _tprintf(_T("Huffmann decompression OK\n")); } } else { _tprintf(_T("Different decompressed size: %u <-> %u\n"), pDecompressed1->dwFileSize, pDecompressed2->dwFileSize); nError = ERROR_BAD_LENGTH; } } else { _tprintf(_T("Failed to real one of the compressed files.\n")); nError = ERROR_CAN_NOT_COMPLETE; } } // Get the recompressed data if(nError == ERROR_SUCCESS) { pRecompressed1 = CompressFileContent(pDecompressed1, MPQ_COMPRESSION_PKWARE, MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_HUFFMANN); pRecompressed2 = ReadFileContent(NULL, MAKE_PATHA("mpq_recompressed.bin")); if(pRecompressed1 != NULL && pRecompressed2 != NULL) { // Comprare decompressed size if(pRecompressed1->dwFileSize == pRecompressed2->dwFileSize) { // Compare the data nDifference = GetFirstDiffer(pRecompressed1->FileData, pRecompressed2->FileData, pRecompressed1->dwFileSize); if(nDifference != -1) { _tprintf(_T("Different recompressed data at offset %08X\n"), nDifference); nError = ERROR_BAD_FORMAT; } else { _tprintf(_T("Huffmann recompression OK\n")); } } else { _tprintf(_T("Different recompressed size: %u <-> %u\n"), pRecompressed1->dwFileSize, pRecompressed2->dwFileSize); nError = ERROR_BAD_LENGTH; } } else { _tprintf(_T("Failed to compress the file or read the compressed muster.\n")); nError = ERROR_CAN_NOT_COMPLETE; } } assert(nError == ERROR_SUCCESS); if(pRecompressed1 != NULL) delete [] pRecompressed1; if(pRecompressed2 != NULL) delete [] pRecompressed2; if(pDecompressed1 != NULL) delete [] pDecompressed1; if(pDecompressed2 != NULL) delete [] pDecompressed2; return ERROR_SUCCESS; } // Just some binary data static unsigned char Decompressed[2048] = { 0x2E, 0x56, 0xF8, 0x44, 0xB0, 0xE2, 0x58, 0x20, 0x44, 0xBC, 0xE2, 0x46, 0x00, 0x44, 0x4A, 0x1D, 0x59, 0x44, 0xC4, 0xE3, 0x00, 0x00, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x34, 0xE2, 0x01, 0x01, 0x01, 0x6C, 0x44, 0xB4, 0xE2, 0x58, 0x01, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x58, 0x44, 0x04, 0xB4, 0xE0, 0x58, 0x40, 0x44, 0xC0, 0xC3, 0x58, 0x58, 0x08, 0x8B, 0x8A, 0x4B, 0xDE, 0xF6, 0x80, 0x92, 0x5B, 0xDC, 0x2F, 0xA1, 0x78, 0x10, 0x10, 0x65, 0x1C, 0x32, 0x8A, 0xA2, 0xF4, 0x02, 0xBC, 0x02, 0x02, 0x9A, 0x7A, 0x06, 0x9A, 0x9E, 0x01, 0xF4, 0x38, 0x20, 0xB2, 0xA2, 0x5C, 0xA2, 0x01, 0x9C, 0x8E, 0xF4, 0x06, 0xB2, 0x98, 0x80, 0x80, 0xF4, 0x08, 0x84, 0x8C, 0x7E, 0x40, 0x06, 0x18, 0xEA, 0x66, 0x10, 0x3E, 0x3E, 0x08, 0x20, 0xAE, 0xF6, 0x10, 0x2A, 0xB4, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0x04, 0xB4, 0xE2, 0x58, 0x80, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x10, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0x08, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x80, 0x80, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x00, 0x00, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x01, 0x01, 0x01, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x02, 0x02, 0x44, 0xB4, 0xE2, 0x20, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x40, 0x40, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x01, 0x58, 0x44, 0x14, 0x68, 0x58, 0x44, 0x2C, 0xE0, 0x5C, 0x01, 0x01, 0x01, 0x44, 0x20, 0x85, 0x3B, 0xD3, 0xB4, 0xE2, 0x02, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x20, 0x44, 0x75, 0xE3, 0x44, 0x08, 0x47, 0xA2, 0xE0, 0x5C, 0x76, 0xB4, 0x00, 0xBA, 0x7E, 0x44, 0x40, 0xB4, 0xB2, 0x4B, 0x44, 0xB4, 0x08, 0x08, 0xE2, 0x58, 0x44, 0xB4, 0xC2, 0x58, 0x01, 0x44, 0xB4, 0xC2, 0x80, 0x58, 0x44, 0xB4, 0x62, 0x10, 0x7E, 0x44, 0xB4, 0xE2, 0xD8, 0x10, 0x44, 0xB4, 0x20, 0xC2, 0x58, 0x44, 0xB4, 0xE6, 0x58, 0x80, 0x80, 0x44, 0xBE, 0xE2, 0x58, 0x44, 0x04, 0xB4, 0xE2, 0x58, 0x44, 0xBE, 0xE2, 0x58, 0x44, 0x10, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x04, 0x82, 0x63, 0x44, 0xB4, 0xEA, 0x58, 0x80, 0x80, 0x44, 0xAE, 0x6E, 0x42, 0x10, 0x44, 0xB0, 0xE2, 0x58, 0x44, 0x01, 0x01, 0xB4, 0xE2, 0x78, 0x44, 0xB4, 0x62, 0x58, 0x04, 0x44, 0xB4, 0xE2, 0x78, 0x44, 0xB4, 0x01, 0x01, 0xC2, 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x58, 0x44, 0x94, 0x02, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x80, 0x80, 0xE2, 0x58, 0x44, 0x2C, 0xDC, 0x63, 0x44, 0x02, 0x02, 0xDC, 0xE4, 0x58, 0x44, 0xB4, 0x02, 0x02, 0xE2, 0x63, 0x44, 0x64, 0xD8, 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x04, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0x00, 0xE2, 0x58, 0x44, 0x02, 0xB4, 0xE2, 0x58, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x10, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x00, 0x00, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x80, 0x80, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x2C, 0x01, 0x01, 0xBC, 0x63, 0x44, 0x0C, 0xE2, 0x58, 0x44, 0x20, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x20, 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x01, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x00, 0x00, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x58, 0x44, 0xB4, 0xE2, 0x80, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x08, 0x08, 0xE2, 0x58, 0x44, 0xE8, 0x0A, 0x92, 0xB4, 0x04, 0x04, 0x5C, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x62, 0x44, 0xB4, 0xC2, 0x01, 0x01, 0x58, 0x44, 0xB4, 0xBE, 0x08, 0x41, 0x44, 0xB4, 0xEA, 0x80, 0x80, 0x58, 0x44, 0x14, 0x68, 0xDE, 0x20, 0x1C, 0x7C, 0x20, 0x20, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0xF5, 0xE2, 0x01, 0x58, 0x84, 0xE8, 0x06, 0xBE, 0xA0, 0x72, 0xE2, 0x02, 0x02, 0x58, 0x44, 0xB4, 0x82, 0x58, 0x44, 0xB4, 0xE2, 0x63, 0x20, 0x20, 0x44, 0xB4, 0x82, 0x58, 0x44, 0xB4, 0x86, 0x02, 0x02, 0x41, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x01, 0x01, 0x58, 0x40, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xF5, 0xE2, 0x58, 0x08, 0x08, 0x84, 0xB4, 0xE2, 0x58, 0x08, 0x44, 0xB4, 0xE2, 0x04, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x10, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x20, 0x44, 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x40, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x80, 0x80, 0xE2, 0x58, 0x44, 0xB4, 0x20, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0x20, 0xB4, 0xE2, 0x58, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x20, 0x58, 0x44, 0xB4, 0x04, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x10, 0x58, 0x44, 0xB4, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x44, 0xB4, 0x40, 0xE2, 0x58, 0x44, 0xB4, 0x08, 0xE2, 0x58, 0x80, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x80, 0x80, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x04, 0x58, 0x44, 0xB4, 0xE2, 0x08, 0x58, 0x44, 0xB4, 0x04, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x02, 0x02, 0x02, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x04, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x10, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x10, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x80, 0x80, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x40, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x80, 0x80, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x20, 0x44, 0xB4, 0xE2, 0x01, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x04, 0x04, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x04, 0x04, 0x58, 0x44, 0xB4, 0x02, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x04, 0x04, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x08, 0x08, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x02, 0x02, 0x02, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0x01, 0x01, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x10, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0x01, 0xE2, 0x58, 0x44, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x08, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x01, 0x01, 0xE2, 0x04, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x01, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x08, 0x08, 0x44, 0x02, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x20, 0x20, 0x58, 0x44, 0xB4, 0x08, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x58, 0x20, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x08, 0x08, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x01, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x00, 0x00, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x40, 0x44, 0xB4, 0x20, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x10, 0x10, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x10, 0x10, 0xB4, 0xE2, 0x58, 0x44, 0x01, 0xB4, 0xE2, 0x58, 0x10, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x02, 0x02, 0x02, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x02, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x00, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x08, 0x08, 0x58, 0x44, 0x80, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0xE2, 0x40, 0x58, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0xB4, 0x20, 0x20, 0xE2, 0x58, 0x04, 0x44, 0xB4, 0xE2, 0x58, 0x44, 0x20, 0xB4, 0xE2, 0x58, 0x44, 0xC4, 0xEB, 0x05, 0x10, 0x10, 0xFF, 0xB5, 0x42, 0x90, 0xBA, 0xDE, 0x20, 0xE2, 0x58, 0x44, 0x40, 0xB4, 0x2A, 0x40, 0x4A, 0x0F, 0xB4, 0x80, 0xE2, 0x58, 0x44, 0xD2, 0x40, 0x62, 0x4B, 0x55, 0x14, 0x68, 0x02, 0xDE, 0x9A, 0x00, 0x6E, 0x02, 0x9A, 0x82, 0x5C, 0x86, 0x00, 0x58, 0x62, 0x0B, 0xBA, 0x7F, 0xA9, 0x00, 0x00, 0x00, 0xFF, 0xCC, 0x38, 0x71, 0xD8, 0x90, 0x4C, 0x01, 0x01, 0x27, 0xB2, 0x6B, 0x10, 0x30, 0x43, 0xC4, 0x9B, 0xB1, 0x1E, 0x45, 0x02, 0x02, 0x34, 0xDF, 0x72, 0x36, 0x00, 0xF5, 0xDC, 0xCE, 0x7A, 0x01, 0x93, 0xEF, 0x4F, 0xB0, 0xDA, 0x22, 0xA4, 0xBB, 0x01, 0x01, 0xC5, 0x47, 0x27, 0x50, 0x80, 0x23, 0x7B, 0xC1, 0x5F, 0x00, 0x13, 0x47, 0xC1, 0x52, 0x46, 0xD9, 0xAB, 0xB6, 0x20, 0x20, 0x8F, 0x04, 0xEF, 0xD5, 0x9E, 0x2B, 0x73, 0x5F, 0x91, 0xF4, 0x40, 0x40, 0x9C, 0xB2, 0x9B, 0x76, 0xDE, 0xDF, 0x90, 0x01, 0x01, 0xFC, 0x27, 0x9C, 0x66, 0x17, 0x6B, 0xB1, 0x5E, 0x00, 0x00, 0xEC, 0x8E, 0x0A, 0xEF, 0x1B, 0xFA, 0xE3, 0xF8, 0xDF, 0x08, 0x08, 0x8E, 0x38, 0x77, 0x24, 0x40, 0xCA, 0x10, 0x2B, 0x10, 0x9D, 0xF7, 0x8E, 0x67, 0xD1, 0x20, 0xEF, 0xF2, 0x94, 0x8F, 0xFC, 0x70, 0xF8, 0x3F, 0x01, 0x01, 0x01, 0xF7, 0xBF, 0x6B, 0xD1, 0x50, 0xA5, 0x10, 0x66, 0x6A, 0x6F, 0xB0, 0x69, 0x68, 0x21, 0xAA, 0x01, 0x01, 0x17, 0x00, 0x48, 0x85, 0x6A, 0xBB, 0xAA, 0x51, 0x0F, 0x01, 0xCE, 0xBB, 0x08, 0x08, 0x1C, 0x2D, 0x84, 0x04, 0x4A, 0xBD, 0x01, 0x7E, 0x88, 0x04, 0x04, 0xE4, 0x29, 0x3E, 0x6C, 0xD0, 0x10, 0x49, 0xA7, 0x93, 0x09, 0xA7, 0x52, 0x48, 0x01, 0x01, 0x4A, 0x24, 0xA5, 0x5E, 0xBE, 0x04, 0x0C, 0x77, 0x01, 0x23, 0xE0, 0x5D, 0xFB, 0x02, 0xD0, 0x1F, 0xD2, 0x3A, 0xFC, 0x5F, 0x08, 0x9A, 0xF2, 0x20, 0x77, 0x54, 0x95, 0x22, 0xD2, 0x32, 0x17, 0x9D, 0x02, 0x02, 0x6B, 0xC0, 0xD6, 0x5B, 0xF4, 0xE5, 0x50, 0x00, 0x00, 0x9A, 0xF7, 0xC6, 0xD6, 0x02, 0x5F, 0x8D, 0x71, 0x80, 0xD8, 0xD2, 0x87, 0x40, 0x7C, 0xF3, 0xB2, 0xBC, 0x84, 0x4B, 0x83, 0xCE, 0x80, 0x80, 0xE3, 0x71, 0x89, 0xAF, 0xE2, 0x01, 0xF8, 0x1B, 0x86, 0x66, 0x4F, 0x40, 0x0F, 0x73, 0x4C, 0x4E, 0xF3, 0x0B, 0x01, 0x01, 0x18, 0x7F, 0x04, 0xBD, 0x7D, 0x51, 0x3C, 0xEB, 0x5B, 0x04, 0xC6, 0xFE, 0x7B, 0x9B, 0x2F, 0x86, 0x62, 0x02, 0x02, 0x56, 0x35, 0x20, 0x05, 0xE6, 0x1C, 0x4C, 0x68, 0x01, 0x29, 0xEA, 0xB0, 0xD3, 0xB0, 0xB5, 0xFE, 0x40, 0x40, 0x9D, 0xE0, 0xC4, 0x6B, 0xF8, 0x6C, 0x00, 0x00, 0xE4, 0x74, 0x6A, 0x95, 0x04, 0x7A, 0xDC, 0x8C, 0x1B, 0x02, 0xBB, 0x8C, 0x29, 0x1F, 0x01, 0xC6, 0xA7, 0x9C, 0x9C, 0xC7, 0x02, 0xB8, 0xD1, 0x10, 0x73, 0xA5, 0x95, 0x92, 0xCC, 0xE8, 0x10, 0x67, 0x14, 0x39, 0xB3, 0x22, 0xB2, 0xAB, 0x00, 0x00, 0xF1, 0x5E, 0x56, 0x90, 0x5F, 0x9D, 0xC5, 0x00, 0x00, 0xA4, 0xA3, 0x36, 0x20, 0xAF, 0x4E, 0x5A, 0xB3, 0x96, 0x41, 0xDE, 0x04, 0x04, 0x77, 0x92, 0x16, 0x8D, 0x02, 0x2D, 0xB9, 0xCA, 0xC2, 0x40, 0xEF, 0x88, 0x5D, 0x5F, 0x81, 0x93, 0xF7, 0x5A, 0x08, 0x08, 0xE3, 0xA3, 0x37, 0xE4, 0x10, 0xE1, 0xEA, 0x8B, 0xDE, 0x02, 0xAC, 0x2D, 0xFE, 0x9C, 0x09, 0x05, 0xBF, 0xA4, 0x40, 0x40, 0xF0, 0x02, 0x92, 0xBB, 0x5D, 0x42, 0x9D, 0x83, 0xF5, 0x2C, 0x40, 0x40, 0x3A, 0xE4, 0x52, 0x62, 0x36, 0x53, 0x8E, 0x40, 0x40, 0x71, 0x91, 0xB7, 0x3E, 0xC0, 0x55, 0x88, 0x1C, 0x40, 0x40, 0xEF, 0x3F, 0xB4, 0x70, 0xF8, 0xE9, 0x2A, 0x7A, 0x4D, 0x02, 0x02, 0xC0, 0x9B, 0x1C, 0x05, 0x08, 0xE2, 0x6C, 0x5E, 0x80, 0xDC, 0x2A, 0xC0, 0x91, 0x05, 0x10, 0x10, 0x1B, 0x85, 0x09, 0x47, 0xBC, 0x27, 0x38, 0x02, 0x04, 0x04, 0xCC, 0x7F, 0x55, 0x1A, 0xF0, 0x06, 0x02, 0xDF, 0xC6, 0xF8, 0xB5, 0x18, 0xB5, 0xF1, 0x97, 0x08, 0x08, 0x08, 0x20, 0xF5, 0x1F, 0xCD, 0x02, 0x20, 0x07, 0xC5, 0x98, 0x44, 0x57, 0x21, 0x00, 0x24, 0xBC, 0x78, 0x10, 0xD6, 0xA9, 0xCC, 0xD1, 0xC4, 0x20, 0x20, 0x30, 0x61, 0x51, 0xEC, 0x5F, 0x80, 0x06, 0xAE, 0x5F, 0x6E, 0x32, 0x2E, 0x8B, 0x01, 0x01, 0x8F, 0xC5, 0x9D, 0x4F, 0x5E, 0x00, 0x07, 0x6A, 0x02, 0xF9, 0xF6, 0x9B, 0xA1, 0x20, 0xF6, 0x8B, 0x19, 0xCA, 0x6D, 0x7E, 0x01, 0xBE, 0x7D, 0x40, 0x18, 0x2D, 0xC2, 0xD5, 0xF8, 0x14, 0x83, 0x49, 0x80, 0x80, 0x1A, 0x7E, 0xDC, 0x6F, 0x0F, 0xB1, 0xA4, 0x04, 0x04, 0x13, 0x0A, 0x2E, 0xF0, 0x08, 0x38, 0x74, 0xAB, 0x40, 0x13, 0xBE, 0xC5, 0x20, 0xFD, 0xFB, 0xC5, 0xDD, 0x37, 0xF3, 0x47, 0xB6, 0x10, 0x10, 0xE0, 0x01, 0x7C, 0xB3, 0xE8, 0x80, 0x95, 0x91, 0xF7, 0x00, 0x63, 0x01, 0x01, 0xFE, 0xBA, 0x86, 0xBE, 0xF0, 0xA8, 0x20, 0xBE, 0xA3, 0x20, 0x90, 0x5A, 0x84, 0xB4, 0x9F, 0x63, 0x20, 0xC2, 0x8E, 0x3A, 0x38, 0xFD, 0xB7, 0x1D, 0x20, 0x20, 0x26, 0x02, 0xDA, 0xDA, 0xA5, 0xFC, 0xA3, 0x65, 0x08, 0x88, 0xF6, 0xCD, 0xAF, 0x81, 0x98, 0x90, 0x10, 0x10, 0xED, 0x7D, 0x2B, 0x74, 0xF5, 0x0B, 0x08, 0x08, 0xE8, 0xB3, 0x92, 0x04, 0x7D, 0xE5, 0x7E, 0x36, 0x48, 0x02, 0xC2, 0x0D, 0x93, 0x4C, 0x20, 0x02, 0xC8, 0x73, 0x81, 0xC5 }; static int CompareHuffmanCompressions0() { unsigned char * pbDecompressed = Decompressed; unsigned char * pbCompressed; unsigned char * pbUncompressed; int cbOutLength; int cbInLength; int nLength = sizeof(Decompressed); int nError = ERROR_NOT_ENOUGH_MEMORY; // Allocate buffers for compressed buffer pbCompressed = new unsigned char[nLength]; if(pbCompressed != NULL) { // Perform the compression cbInLength = cbOutLength = nLength; SCompCompress(pbCompressed, &cbOutLength, pbDecompressed, nLength, MPQ_COMPRESSION_HUFFMANN, 0x00, 0); assert(cbOutLength < cbInLength); // Allocate space for decompressed buffer pbUncompressed = new unsigned char[nLength]; if(pbUncompressed != NULL) { // Perform the decompression cbInLength = cbOutLength; cbOutLength = nLength; SCompDecompress(pbUncompressed, &cbOutLength, pbCompressed, cbInLength); // Compare length if(cbOutLength == nLength) { if(!memcmp(pbUncompressed, pbDecompressed, nLength)) { _tprintf(_T("Huffmann compression OK\n")); nError = ERROR_SUCCESS; } else { _tprintf(_T("Error: Decompressed data are not the same like the compressed ones\n")); nError = ERROR_INVALID_DATA; } } else { _tprintf(_T("Error: Length of the uncompressed data is different from original length\n")); nError = ERROR_BAD_LENGTH; } delete [] pbUncompressed; } delete [] pbCompressed; } assert(nError == ERROR_SUCCESS); return ERROR_SUCCESS; } //----------------------------------------------------------------------------- // Compare PKLIB decompression FILE * data_file; BYTE pbCompressed1[] = {0x00, 0x04, 0x00, 0x00, 0x04, 0xF0, 0x1F, 0x7B, 0x01, 0xFF}; BYTE pbCompressed2[] = {0x00, 0x04, 0x00, 0x00, 0x04, 0xF0, 0x1F, 0x00, 0x00, 0x04, 0xFC, 0x03}; static int ComparePklibCompressions() { TFileStream * pStream; ULONGLONG ByteOffset = 0; ULONGLONG FileSize = 0; unsigned char * pbRawData; unsigned char * pbCompressed; unsigned char * pbDecompressed; int cbOutBuffer; int cbInBuffer; pStream = FileStream_OpenFile(_T("doc\\data_to_compress.dat"), BASE_PROVIDER_FILE | STREAM_PROVIDER_LINEAR); if(pStream != NULL) { FileStream_GetSize(pStream, &FileSize); cbOutBuffer = (int)FileSize; pbRawData = new unsigned char[cbOutBuffer]; pbCompressed = new unsigned char[cbOutBuffer]; pbDecompressed = new unsigned char[cbOutBuffer]; if(pbRawData && pbCompressed && pbDecompressed) { FileStream_Read(pStream, &ByteOffset, pbRawData, (DWORD)FileSize); SCompImplode(pbCompressed, &cbOutBuffer, pbRawData, (DWORD)FileSize); cbInBuffer = cbOutBuffer; cbOutBuffer = (int)FileSize; SCompExplode(pbDecompressed, &cbOutBuffer, pbCompressed, cbInBuffer); } delete [] pbDecompressed; delete [] pbCompressed; delete [] pbRawData; FileStream_Close(pStream); } return ERROR_SUCCESS; } //----------------------------------------------------------------------------- // Compare LZMA decompression #ifdef PLATFORM_WINDOWS typedef void * (*ALLOC_MEMORY)(size_t); typedef void (*FREE_MEMORY)(void *); typedef int (GIVE_DATA)(void *); extern "C" int starcraft_decompress_lzma(char * pbInBuffer, int cbInBuffer, char * pbOutBuffer, int cbOutBuffer, int * pcbOutBuffer, ALLOC_MEMORY pfnAllocMemory, FREE_MEMORY pfnFreeMemory); extern "C" int starcraft_compress_lzma(char * pbInBuffer, int cbInBuffer, int dummy1, char * pbOutBuffer, int cbOutBuffer, int dummy2, int * pcbOutBuffer, ALLOC_MEMORY pfnAllocMemory, FREE_MEMORY pfnFreeMemory, GIVE_DATA pfnGiveData); void Compress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer, int *, int); int Decompress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer); extern "C" void * operator_new(size_t sz) { return malloc(sz); } void * Memory_Allocate(size_t byte_size) { return malloc(byte_size); } void Memory_Free(void * address) { if(address != NULL) free(address); } int GiveData(void *) { return 0; } static int StarcraftCompress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer) { return starcraft_compress_lzma(pbInBuffer, cbInBuffer, 0, pbOutBuffer, *pcbOutBuffer, 0, pcbOutBuffer, Memory_Allocate, Memory_Free, GiveData); } static int StarcraftDecompress_LZMA(char * pbOutBuffer, int * pcbOutBuffer, char * pbInBuffer, int cbInBuffer) { return starcraft_decompress_lzma(pbInBuffer, cbInBuffer, pbOutBuffer, *pcbOutBuffer, pcbOutBuffer, Memory_Allocate, Memory_Free); } static int CompareLzmaCompressions(int nSectorSize) { LPBYTE pbCompressed1 = NULL; // Compressed by our code LPBYTE pbCompressed2 = NULL; // Compressed by Blizzard's code LPBYTE pbDecompressed1 = NULL; // Decompressed by our code LPBYTE pbDecompressed2 = NULL; // Decompressed by Blizzard's code LPBYTE pbOriginalData = NULL; int nError = ERROR_SUCCESS; // Allocate buffers // Must allocate twice blocks due to probable bug in Storm.dll. // Storm.dll corrupts stack when uncompresses data with PKWARE DCL // and no compression occurs. pbDecompressed1 = new BYTE [nSectorSize]; pbDecompressed2 = new BYTE [nSectorSize]; pbCompressed1 = new BYTE [nSectorSize]; pbCompressed2 = new BYTE [nSectorSize]; pbOriginalData = new BYTE[nSectorSize]; if(!pbDecompressed1 || !pbDecompressed2 || !pbCompressed1 || !pbCompressed2 || !pbOriginalData) nError = ERROR_NOT_ENOUGH_MEMORY; if(nError == ERROR_SUCCESS) { for(int i = 0; i < 100000; i++) { int nDcmpLength1; int nDcmpLength2; int nCmpLength1; int nCmpLength2; int nDiff; clreol(); _tprintf(_T("Testing compression of sector %u\r"), i + 1); // Generate random data sector GenerateRandomDataBlock(pbOriginalData, nSectorSize); // Compress the sector by both methods nCmpLength1 = nCmpLength2 = nSectorSize; // Compress_LZMA((char *)pbCompressed1, &nCmpLength1, (char *)pbOriginalData, nSectorSize, 0, 0); StarcraftCompress_LZMA((char *)pbCompressed1, &nCmpLength2, (char *)pbOriginalData, nSectorSize); __TryToDecompress: // Only test decompression when the compression actually succeeded if(nCmpLength1 < nSectorSize) { // Decompress both data nDcmpLength2 = nDcmpLength1 = nSectorSize; // Decompress_LZMA((char *)pbDecompressed1, &nDcmpLength1, (char *)pbCompressed1, nCmpLength1); StarcraftDecompress_LZMA((char *)pbDecompressed2, &nDcmpLength2, (char *)pbCompressed1, nCmpLength1); // Compare the length of the output data if(nDcmpLength1 != nDcmpLength2) { _tprintf(_T("Difference in compressed blocks lengths (%u vs %u)\n"), nDcmpLength1, nDcmpLength2); goto __TryToDecompress; } // Compare the output if((nDiff = GetFirstDiffer(pbDecompressed1, pbDecompressed2, nDcmpLength1)) != -1) { _tprintf(_T("Difference in decompressed blocks (offset 0x%08X)\n"), nDiff); goto __TryToDecompress; } // Check for data overflow if(pbDecompressed1[nSectorSize] != 0xFD || pbDecompressed1[nSectorSize] != 0xFD) { _tprintf(_T("Damage after decompressed sector !!!\n")); goto __TryToDecompress; } // Compare the decompressed data against original data if((nDiff = GetFirstDiffer(pbDecompressed1, pbOriginalData, nDcmpLength1)) != -1) { _tprintf(_T("Difference between original data and decompressed data (offset 0x%08X)\n"), nDiff); goto __TryToDecompress; } } } } // Cleanup if(pbOriginalData != NULL) delete [] pbOriginalData; if(pbCompressed2 != NULL) delete [] pbCompressed2; if(pbCompressed1 != NULL) delete [] pbCompressed1; if(pbDecompressed2 != NULL) delete [] pbDecompressed2; if(pbDecompressed1 != NULL) delete [] pbDecompressed1; clreol(); return nError; } #endif // PLATFORM_WINDOWS //----------------------------------------------------------------------------- // Compression method test static int TestSectorCompress(int nSectorSize) { LPBYTE pbDecompressed = NULL; LPBYTE pbCompressed = NULL; LPBYTE pbOriginal = NULL; int nError = ERROR_SUCCESS; // Allocate buffers pbDecompressed = new BYTE[nSectorSize]; pbCompressed = new BYTE[nSectorSize]; pbOriginal = new BYTE[nSectorSize]; if(!pbDecompressed || !pbCompressed || !pbOriginal) nError = ERROR_NOT_ENOUGH_MEMORY; if(nError == ERROR_SUCCESS) { for(int i = 0; i < 100000; i++) { int nOriginalLength = nSectorSize % (rand() + 1); int nCompressedLength; int nDecompressedLength; int nCmp = MPQ_COMPRESSION_SPARSE | MPQ_COMPRESSION_ZLIB | MPQ_COMPRESSION_BZIP2 | MPQ_COMPRESSION_PKWARE; int nDiff; clreol(); _tprintf(_T("Testing compression of sector %u\r"), i + 1); // Generate random data sector GenerateRandomDataBlock(pbOriginal, nOriginalLength); if(nOriginalLength == 0x123) nOriginalLength = 0; __TryAgain: // Compress the sector nCompressedLength = nOriginalLength; SCompCompress((char *)pbCompressed, &nCompressedLength, (char *)pbOriginal, nOriginalLength, nCmp, 0, -1); // SCompImplode((char *)pbCompressed, &nCompressedLength, (char *)pbOriginal, nOriginalLength); // When the method was unable to compress data, // the compressed data must be identical to original data if(nCompressedLength == nOriginalLength) { if((nDiff = GetFirstDiffer(pbCompressed, pbOriginal, nOriginalLength)) != -1) { _tprintf(_T("Compression error: Fail when unable to compress the data (Offset 0x%08X).\n"), nDiff); goto __TryAgain; } } // Uncompress the sector nDecompressedLength = nOriginalLength; SCompDecompress((char *)pbDecompressed, &nDecompressedLength, (char *)pbCompressed, nCompressedLength); // SCompExplode((char *)pbDecompressed, &nDecompressedLength, (char *)pbCompressed, nCompressedLength); // Check the decompressed length against original length if(nDecompressedLength != nOriginalLength) { _tprintf(_T("Length of uncompressed data does not agree with original data length !!!\n")); goto __TryAgain; } // Check decompressed block against original block if((nDiff = GetFirstDiffer(pbDecompressed, pbOriginal, nOriginalLength)) != -1) { _tprintf(_T("Decompressed sector does not agree with the original data !!! (Offset 0x%08X)\n"), nDiff); goto __TryAgain; } } } // Cleanup delete [] pbOriginal; delete [] pbCompressed; delete [] pbDecompressed; clreol(); return nError; } static int TestArchiveOpenAndClose(const TCHAR * szMpqName) { // const char * szFileName = "../Bin/Config/Setting/ErroeString.str"; const char * szFileName = "Scp\\LifeSkills.csv"; // const char * szFileName = "File00000000.xxx"; TMPQArchive * ha = NULL; HANDLE hFile1 = NULL; // HANDLE hFile2 = NULL; HANDLE hMpq = NULL; int nError = ERROR_SUCCESS; if(nError == ERROR_SUCCESS) { _tprintf(_T("Opening archive %s ...\n"), szMpqName); if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) nError = GetLastError(); ha = (TMPQArchive *)hMpq; } if(nError == ERROR_SUCCESS) { SFileAddListFile(hMpq, "c:\\Tools32\\Listfiles\\ListFile.txt"); } // Verify the raw data in the archive if(nError == ERROR_SUCCESS) { // Try to open a file if(!SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_FROM_MPQ, &hFile1)) { nError = GetLastError(); printf("%s - file not found in the MPQ\n", szFileName); } } // Dummy read from the file if(nError == ERROR_SUCCESS) { DWORD dwBytesRead = 0; BYTE Buffer[0x1000]; char szNameBuff[MAX_PATH]; SFileGetFileName(hFile1, szNameBuff); SFileSetFilePointer(hFile1, 0x1000, NULL, FILE_BEGIN); SFileReadFile(hFile1, Buffer, sizeof(Buffer), &dwBytesRead, NULL); } // Verify the MPQ listfile #ifdef _MSC_VER if(nError == ERROR_SUCCESS) { SFileExtractFile(hMpq, szFileName, _T("E:\\extracted.wav"), 0); PlaySound(_T("E:\\extracted.wav"), NULL, SND_FILENAME); } #endif if(hFile1 != NULL) SFileCloseFile(hFile1); if(hMpq != NULL) SFileCloseArchive(hMpq); return nError; } static int TestAddFilesToArchive(const TCHAR * szMpqName) { HANDLE hFile; HANDLE hMpq; LPCSTR szFileData = "0123456789"; char szAddedFile[128]; DWORD dwFileSize = 10; CopyFile(_T("e:\\Ladik\\Incoming\\Tya's Zerg Defense.SC2Map"), _T("e:\\Multimedia\\MPQs\\Tya's Zerg Defense.SC2Map"), FALSE); for(int i = 0; i < 3; i++) { if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) { sprintf(szAddedFile, "AddedFile%04u.txt", i); if(SFileCreateFile(hMpq, szAddedFile, 0, dwFileSize, 0, MPQ_FILE_COMPRESS, &hFile)) { SFileWriteFile(hMpq, szFileData, dwFileSize, MPQ_COMPRESSION_ZLIB); SFileFinishFile(hFile); } } } return ERROR_SUCCESS; } static int TestFindFiles(const TCHAR * szMpqName) { TMPQFile * hf; HANDLE hFile; HANDLE hMpq = NULL; BYTE Buffer[100]; int nError = ERROR_SUCCESS; int nFiles = 0; int nFound = 0; // Open the archive if(nError == ERROR_SUCCESS) { _tprintf(_T("Opening \"%s\" for finding files ...\n"), szMpqName); if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) nError = GetLastError(); } // Compact the archive if(nError == ERROR_SUCCESS) { SFILE_FIND_DATA sf; HANDLE hFind; DWORD dwExtraDataSize; bool bFound = true; hFind = SFileFindFirstFile(hMpq, "*", &sf, "c:\\Tools32\\ListFiles\\ListFile.txt"); while(hFind != NULL && bFound != false) { if(SFileOpenFileEx(hMpq, sf.cFileName, 0, &hFile)) { hf = (TMPQFile *)hFile; SFileReadFile(hFile, Buffer, sizeof(Buffer), NULL, NULL); nFiles++; if(sf.dwFileFlags & MPQ_FILE_SECTOR_CRC) { dwExtraDataSize = hf->SectorOffsets[hf->dwSectorCount + 1] - hf->SectorOffsets[hf->dwSectorCount]; if(dwExtraDataSize != 0) nFound++; } SFileCloseFile(hFile); } bFound = SFileFindNextFile(hFind, &sf); } } if(hMpq != NULL) SFileCloseArchive(hMpq); if(nError == ERROR_SUCCESS) _tprintf(_T("Search complete\n")); return nError; } static int TestMpqCompacting(const TCHAR * szMpqName) { HANDLE hMpq = NULL; int nError = ERROR_SUCCESS; // Open the archive if(nError == ERROR_SUCCESS) { _tprintf(_T("Opening \"%s\" for compacting ...\n"), szMpqName); if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) nError = GetLastError(); } if(nError == ERROR_SUCCESS) { const char * szFileName = "Shaders\\Effects\\shadowmap.wfx"; printf("Deleting file %s ...\r", szFileName); if(!SFileRemoveFile(hMpq, szFileName, SFILE_OPEN_FROM_MPQ)) nError = GetLastError(); } /* // Compact the archive if(nError == ERROR_SUCCESS) { _tprintf(_T("Compacting archive ...\r")); SFileSetCompactCallback(hMpq, CompactCB, NULL); if(!SFileCompactArchive(hMpq, "c:\\Tools32\\ListFiles\\ListFile.txt")) nError = GetLastError(); } */ if(hMpq != NULL) SFileCloseArchive(hMpq); if(nError == ERROR_SUCCESS) _tprintf(_T("Compacting complete (No errors)\n")); return nError; } static int TestCreateArchive(const TCHAR * szMpqName) { TFileStream * pStream; const TCHAR * szFileName1 = MAKE_PATH("FileTest.exe"); const TCHAR * szFileName2 = MAKE_PATH("ZeroSize.txt"); HANDLE hMpq = NULL; // Handle of created archive DWORD dwVerifyResult; DWORD dwFileCount = 0; LCID LocaleIDs[] = {0x000, 0x405, 0x406, 0x407, 0xFFFF}; char szMpqFileName[MAX_PATH]; int nError = ERROR_SUCCESS; int i; // Create the new file _tprintf(_T("Creating %s ...\n"), szMpqName); pStream = FileStream_CreateFile(szMpqName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pStream == NULL) nError = GetLastError(); // Write some data if(nError == ERROR_SUCCESS) { ULONGLONG FileSize = 0x100000; FileStream_SetSize(pStream, FileSize); FileStream_Close(pStream); } // Well, now create the MPQ archive if(nError == ERROR_SUCCESS) { if(!SFileCreateArchive(szMpqName, MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_ATTRIBUTES, 17, &hMpq)) { nError = GetLastError(); } } // Add the same file multiple times if(nError == ERROR_SUCCESS) { // Add FileTest.exe for(i = 0; AddFlags[i] != 0xFFFFFFFF; i++) { sprintf(szMpqFileName, "FileTest_%02u.exe", i); PrintfTA(_T("Adding %s as %s ...\n"), szFileName1, szMpqFileName); if(SFileAddFileEx(hMpq, szFileName1, szMpqFileName, AddFlags[i], MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) { dwVerifyResult = SFileVerifyFile(hMpq, szMpqFileName, MPQ_ATTRIBUTE_CRC32 | MPQ_ATTRIBUTE_MD5); if(dwVerifyResult & (VERIFY_OPEN_ERROR | VERIFY_READ_ERROR | VERIFY_FILE_SECTOR_CRC_ERROR | VERIFY_FILE_CHECKSUM_ERROR | VERIFY_FILE_MD5_ERROR)) printf("CRC error on \"%s\"\n", szMpqFileName); dwFileCount++; } else { printf("Failed to add the file \"%s\".\n", szMpqFileName); } } // Delete a file in the middle of the file table SFileRemoveFile(hMpq, "FileTest_10.exe", SFILE_OPEN_FROM_MPQ); SFileAddFileEx(hMpq, szFileName1, "FileTest_xx.exe", MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME); // Try to decrement max file count dwFileCount = SFileGetMaxFileCount(hMpq); SFileSetMaxFileCount(hMpq, dwFileCount - 1); // Add ZeroSize.txt (1) sprintf(szMpqFileName, "ZeroSize_1.txt"); for(i = 0; LocaleIDs[i] != 0xFFFF; i++) { PrintfTA(_T("Adding %s as %s (locale %04x) ...\n"), szFileName2, szMpqFileName, LocaleIDs[i]); SFileSetLocale(LocaleIDs[i]); if(!SFileAddFileEx(hMpq, szFileName2, szMpqFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) printf("Cannot add the file\n"); } // Add ZeroSize.txt (1) sprintf(szMpqFileName, "ZeroSize_2.txt"); for(int i = 0; LocaleIDs[i] != 0xFFFF; i++) { PrintfTA(_T("Adding %s as %s (locale %04x) ...\n"), szFileName2, szMpqFileName, LocaleIDs[i]); SFileSetLocale(LocaleIDs[i]); if(!SFileAddFileEx(hMpq, szFileName2, szMpqFileName, MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) printf("Cannot add the file\n"); } } // Test rename function if(nError == ERROR_SUCCESS) { _tprintf(_T("Testing rename files ...\n")); SFileSetLocale(LANG_NEUTRAL); if(!SFileRenameFile(hMpq, "FileTest_08.exe", "FileTest_08a.exe")) { nError = GetLastError(); _tprintf(_T("Failed to rename the file\n")); } if(!SFileRenameFile(hMpq, "FileTest_08a.exe", "FileTest_08.exe")) { nError = GetLastError(); _tprintf(_T("Failed to rename the file\n")); } if(!SFileRenameFile(hMpq, "FileTest_10.exe", "FileTest_10a.exe")) { nError = GetLastError(); _tprintf(_T("Failed to rename the file\n")); } if(!SFileRenameFile(hMpq, "FileTest_10a.exe", "FileTest_10.exe")) { nError = GetLastError(); _tprintf(_T("Failed to rename the file\n")); } if(nError == ERROR_SUCCESS) _tprintf(_T("Rename test succeeded.\n\n")); else _tprintf(_T("Rename test failed.\n\n")); } // Compact the archive // if(nError == ERROR_SUCCESS) // SFileCompactArchive(hMpq); // Test changing hash table size if(nError == ERROR_SUCCESS) SFileSetMaxFileCount(hMpq, 0x95); if(hMpq != NULL) SFileCloseArchive(hMpq); // Try to reopen the archive if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) SFileCloseArchive(hMpq); _tprintf(_T("\n")); return nError; } static int TestCreateArchive_PaliRoharBug(const TCHAR * szMpqName) { const TCHAR * szFileName = MAKE_PATH("FileTest.exe"); HANDLE hMpq = NULL; // Handle of created archive DWORD dwMaxFileCount = 0; DWORD dwMpqFlags = MPQ_FILE_ENCRYPTED | MPQ_FILE_COMPRESS; char szMpqFileName[MAX_PATH]; int nError = ERROR_SUCCESS; int i; _tremove(szMpqName); if(SFileCreateArchive(szMpqName, MPQ_CREATE_ARCHIVE_V4 | MPQ_CREATE_ATTRIBUTES, 1, &hMpq)) { // Add the file there SFileAddFileEx(hMpq, szFileName, "FileTest_base.exe", dwMpqFlags, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME); SFileFlushArchive(hMpq); SFileCloseArchive(hMpq); // Add the same file 10 times for(i = 0; i < 10; i++) { if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) { dwMaxFileCount = SFileGetMaxFileCount(hMpq) + 1; _tprintf(_T("Increasing max file count to %u ...\n"), dwMaxFileCount); SFileSetMaxFileCount(hMpq, dwMaxFileCount); sprintf(szMpqFileName, "FileTest_%02u.exe", dwMaxFileCount); PrintfTA(_T("Adding %s as %s\n"), szFileName, szMpqFileName); if(!SFileAddFileEx(hMpq, szFileName, szMpqFileName, dwMpqFlags, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) { printf("Failed to add the file \"%s\".\n", szMpqFileName); break; } SFileFlushArchive(hMpq); SFileCompactArchive(hMpq, NULL, false); SFileCloseArchive(hMpq); } } } _tprintf(_T("\n")); return nError; } static int TestAddFilesToMpq( const TCHAR * szMpqName, ... ) { const TCHAR * szFileName; const TCHAR * szSrc; char * szTrg; HANDLE hMpq; va_list argList; char szMpqFileName[MAX_PATH]; int nError = ERROR_SUCCESS; if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) return GetLastError(); va_start(argList, szMpqName); while((szFileName = va_arg(argList, const TCHAR *)) != NULL) { // Convert the plain name to ANSI szSrc = GetPlainFileNameT(szFileName); szTrg = szMpqFileName; while(*szSrc != 0) *szTrg++ = (char)*szSrc++; *szTrg = 0; // Add the file to MPQ if(!SFileAddFileEx(hMpq, szFileName, szMpqFileName, MPQ_FILE_COMPRESS, MPQ_COMPRESSION_ZLIB, MPQ_COMPRESSION_NEXT_SAME)) { nError = GetLastError(); printf("Failed to add the file \"%s\"\n", szFileName); } } SFileCloseArchive(hMpq); return nError; } static int TestCreateArchiveFromMemory(const TCHAR * szMpqName) { #define FILE_SIZE 65535 HANDLE hFile; HANDLE hMPQ; char* data = new char [FILE_SIZE]; // random memory data char szFileName[100]; int i; // Create an mpq file for testing if(SFileCreateArchive(szMpqName, MPQ_CREATE_ARCHIVE_V2|MPQ_CREATE_ATTRIBUTES, 0x100000, &hMPQ)) { for(i = 0; i < 1000; i++) { sprintf(szFileName, "File%03u.bin", i); printf("Adding file %s\r", szFileName); if(SFileCreateFile(hMPQ, szFileName, 0, FILE_SIZE, 0, MPQ_FILE_COMPRESS, &hFile)) { SFileWriteFile(hFile, data, FILE_SIZE, MPQ_COMPRESSION_ZLIB); SFileFinishFile(hFile); } } } SFileCloseArchive(hMPQ); delete [] data; return ERROR_SUCCESS; } static int TestFileReadAndWrite( const TCHAR * szMpqName, const char * szFileName) { LPBYTE pvFile = NULL; HANDLE hFile = NULL; HANDLE hMpq = NULL; DWORD dwBytesRead; DWORD dwFileSize = 0; int nError = ERROR_SUCCESS; if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq)) { nError = GetLastError(); _tprintf(_T("Failed to open the archive %s (%u).\n"), szMpqName, nError); } if(nError == ERROR_SUCCESS) { if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) { nError = GetLastError(); printf("Failed to open the file %s (%u).\n", szFileName, nError); } } if(nError == ERROR_SUCCESS) { if(!SFileGetFileInfo(hFile, SFILE_INFO_FILE_SIZE, &dwFileSize, sizeof(DWORD), NULL)) { nError = GetLastError(); _tprintf(_T("Failed to get the file size (%u).\n"), nError); } } if(nError == ERROR_SUCCESS) { pvFile = new BYTE[dwFileSize]; if(pvFile == NULL) { nError = ERROR_NOT_ENOUGH_MEMORY; printf("Failed to allocate buffer for the file (%u).\n", nError); } } if(nError == ERROR_SUCCESS) { if(!SFileReadFile(hFile, pvFile, dwFileSize, &dwBytesRead, NULL)) { nError = GetLastError(); printf("Failed to read file (%u).\n", nError); } } if(hFile != NULL) { SFileCloseFile(hFile); hFile = NULL; } if(nError == ERROR_SUCCESS) { if(!SFileCreateFile(hMpq, szFileName, 0, dwFileSize, 0, MPQ_FILE_REPLACEEXISTING, &hFile)) { nError = GetLastError(); printf("Failed to create %s in the archive (%u).\n", szFileName, nError); } } if(nError == ERROR_SUCCESS) { if(!SFileWriteFile(hFile, pvFile, dwFileSize, 0)) { nError = GetLastError(); printf("Failed to write the data to the MPQ (%u).\n", nError); } } if(hFile != NULL) { if(!SFileFinishFile(hFile)) { nError = GetLastError(); printf("Failed to finalize file creation (%u).\n", nError); } } if(pvFile != NULL) delete [] pvFile; if(hMpq != NULL) SFileCloseArchive(hMpq); return nError; } static int TestSignatureVerify(const TCHAR * szMpqName) { HANDLE hMpq; if(SFileOpenArchive(szMpqName, 0, 0, &hMpq)) { _tprintf(_T("Verifying digital signature in %s:\n"), szMpqName); switch(SFileVerifyArchive(hMpq)) { case ERROR_NO_SIGNATURE: _tprintf(_T("No digital signature present.\n")); break; case ERROR_VERIFY_FAILED: _tprintf(_T("Failed to verify signature.\n")); break; case ERROR_WEAK_SIGNATURE_OK: _tprintf(_T("Weak signature is OK.\n")); break; case ERROR_WEAK_SIGNATURE_ERROR: _tprintf(_T("Weak signature mismatch.\n")); break; case ERROR_STRONG_SIGNATURE_OK: _tprintf(_T("Strong signature is OK.\n")); break; case ERROR_STRONG_SIGNATURE_ERROR: _tprintf(_T("Strong signature mismatch.\n")); break; } SFileCloseArchive(hMpq); _tprintf(_T("\n")); } return 0; } static int TestCreateArchiveCopy(const TCHAR * szMpqName, const TCHAR * szMpqCopyName, const char * szListFile) { TFileStream * pStream; TCHAR szLocalFile[MAX_PATH]; HANDLE hMpq1 = NULL; // Handle of existing archive HANDLE hMpq2 = NULL; // Handle of created archive DWORD dwHashTableSize = 0; int nError = ERROR_SUCCESS; // If no listfile or an empty one, use NULL if(szListFile == NULL || *szListFile == 0) szListFile = NULL; // Create the new file pStream = FileStream_CreateFile(szMpqCopyName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pStream == NULL) nError = GetLastError(); // Write some data if(nError == ERROR_SUCCESS) { ULONGLONG FileSize = 0x100000; FileStream_SetSize(pStream, FileSize); FileStream_Close(pStream); } // Open the existing MPQ archive if(nError == ERROR_SUCCESS) { _tprintf(_T("Opening %s ...\n"), szMpqName); if(!SFileOpenArchive(szMpqName, 0, 0, &hMpq1)) nError = GetLastError(); } // Well, now create the MPQ archive if(nError == ERROR_SUCCESS) { _tprintf(_T("Creating %s ...\n"), szMpqCopyName); SFileGetFileInfo(hMpq1, SFILE_INFO_HASH_TABLE_SIZE, &dwHashTableSize, 4, NULL); if(!SFileCreateArchive(szMpqCopyName, 0, dwHashTableSize, &hMpq2)) nError = GetLastError(); } // Copy all files from one archive to another if(nError == ERROR_SUCCESS) { SFILE_FIND_DATA sf; HANDLE hFind = SFileFindFirstFile(hMpq1, "*", &sf, szListFile); bool bResult = true; _tprintf(_T("Copying files ...\n")); if(hFind != NULL) { while(bResult) { if(strcmp(sf.cFileName, LISTFILE_NAME) && strcmp(sf.cFileName, ATTRIBUTES_NAME)) { SFileSetLocale(sf.lcLocale); // Create the local file name MergeLocalPath(szLocalFile, szWorkDir, sf.szPlainName); if(SFileExtractFile(hMpq1, sf.cFileName, szLocalFile, SFILE_OPEN_FROM_MPQ)) { printf("Extracting %s ... OK\n", sf.cFileName); if(!SFileAddFile(hMpq2, szLocalFile, sf.cFileName, sf.dwFileFlags)) { nError = GetLastError(); printf("Adding %s ... Failed\n\n", sf.cFileName); _tremove(szLocalFile); break; } else { printf("Adding %s ... OK\n", sf.cFileName); } } else { printf("Extracting %s ... Failed\n", sf.cFileName); } // Delete the added file _tremove(szLocalFile); } // Find the next file bResult = SFileFindNextFile(hFind, &sf); } // Close the search handle SFileFindClose(hFind); printf("\n"); } } // Close both archives if(hMpq2 != NULL) SFileCloseArchive(hMpq2); if(hMpq1 != NULL) SFileCloseArchive(hMpq1); return nError; } static int TestOpenPatchedArchive(const TCHAR * szMpqName, ...) { TFileStream * pStream; HANDLE hFile = NULL; HANDLE hMpq = NULL; va_list argList; const char * szFileName = "Creature\\ALLIANCELIONMOUNT\\AllianceLion.M2"; TCHAR szLocFileName[MAX_PATH]; LPBYTE pbFullFile = NULL; DWORD dwFileSize; int nError = ERROR_SUCCESS; // Open the primary MPQ _tprintf(_T("Opening %s ...\n"), szMpqName); if(!SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq)) { nError = GetLastError(); _tprintf(_T("Failed to open the archive %s ...\n"), szMpqName); } // Add all patches if(nError == ERROR_SUCCESS) { va_start(argList, szMpqName); while((szMpqName = va_arg(argList, const TCHAR *)) != NULL) { _tprintf(_T("Adding patch %s ...\n"), szMpqName); if(!SFileOpenPatchArchive(hMpq, szMpqName, NULL, 0)) { nError = GetLastError(); printf("Failed to add patch %s ...\n", szMpqName); } } va_end(argList); } /* // Now search all files if(nError == ERROR_SUCCESS) { SFILE_FIND_DATA sf; HANDLE hFind; bool bResult = true; hFind = SFileFindFirstFile(hMpq, "World\\Minimaps\\Azeroth\\noLiquid_map20_44.*", &sf, NULL); while(hFind && bResult) { printf("%s\n", sf.cFileName); bResult = SFileFindNextFile(hFind, &sf); } } // Now try to open patched version of a file if(nError == ERROR_SUCCESS) { SFileExtractFile(hMpq, szFileName, _T("E:\\noLiquid_map20_44.blp"), SFILE_OPEN_FROM_MPQ); } */ // Now try to open patched version of "Achievement.dbc" if(nError == ERROR_SUCCESS) { printf("Opening patched file \"%s\" ...\n", szFileName); SFileHasFile(hMpq, szFileName); SFileVerifyFile(hMpq, szFileName, SFILE_VERIFY_RAW_MD5); if(!SFileOpenFileEx(hMpq, szFileName, 0, &hFile)) { nError = GetLastError(); printf("Failed to open patched file \"%s\"\n", szFileName); } } // Verify of the patched version is correct if(nError == ERROR_SUCCESS) { TCHAR * szPatchChain = NULL; DWORD cbPatchChain = 0; // Get the patch chain SFileGetFileInfo(hFile, SFILE_INFO_PATCH_CHAIN, szPatchChain, cbPatchChain, &cbPatchChain); szPatchChain = (TCHAR *)(new BYTE[cbPatchChain]); SFileGetFileInfo(hFile, SFILE_INFO_PATCH_CHAIN, szPatchChain, cbPatchChain, &cbPatchChain); delete [] szPatchChain; // Get the size of the full patched file dwFileSize = SFileGetFileSize(hFile, NULL); if(dwFileSize != 0) { DWORD dwBytesRead = 0; BYTE TempData[0x100]; SFileReadFile(hFile, TempData, sizeof(TempData), &dwBytesRead, NULL); SFileSetFilePointer(hFile, 0, NULL, FILE_BEGIN); // Allocate space for the full file pbFullFile = new BYTE[dwFileSize]; if(pbFullFile != NULL) { if(!SFileReadFile(hFile, pbFullFile, dwFileSize, NULL, NULL)) { nError = GetLastError(); printf("Failed to read full patched file data \"%s\"\n", szFileName); } if(nError == ERROR_SUCCESS) { MergeLocalPath(szLocFileName, MAKE_PATH("Work//"), GetPlainFileNameA(szFileName)); pStream = FileStream_CreateFile(szLocFileName, STREAM_PROVIDER_LINEAR | BASE_PROVIDER_FILE); if(pStream != NULL) { FileStream_Write(pStream, NULL, pbFullFile, dwFileSize); FileStream_Close(pStream); } } delete [] pbFullFile; } } } // Close handles if(hFile != NULL) SFileCloseFile(hFile); if(hMpq != NULL) SFileCloseArchive(hMpq); return nError; } static int TestCompareTwoArchives( const TCHAR * szMpqName1, const TCHAR * szMpqName2, const char * szListFile, DWORD dwBlockSize) { TMPQArchive * ha1 = NULL; TMPQArchive * ha2 = NULL; HANDLE hMpq1 = NULL; // Handle of the first archive HANDLE hMpq2 = NULL; // Handle of the second archive HANDLE hFile1 = NULL; HANDLE hFile2 = NULL; int nError = ERROR_SUCCESS; // If no listfile or an empty one, use NULL if(szListFile == NULL || *szListFile == 0) szListFile = NULL; _tprintf(_T("=============== Comparing MPQ archives ===============\n")); // Open the first MPQ archive if(nError == ERROR_SUCCESS && szMpqName1 != NULL) { _tprintf(_T("Opening %s ...\n"), szMpqName1); if(!SFileOpenArchive(szMpqName1, 0, 0, &hMpq1)) nError = GetLastError(); ha1 = (TMPQArchive *)hMpq1; } // Open the second MPQ archive if(nError == ERROR_SUCCESS && szMpqName2 != NULL) { _tprintf(_T("Opening %s ...\n"), szMpqName2); if(!SFileOpenArchive(szMpqName2, 0, 0, &hMpq2)) nError = GetLastError(); ha2 = (TMPQArchive *)hMpq2; } // Compare the header if(nError == ERROR_SUCCESS && (ha1 != NULL && ha2 != NULL)) { if(ha1->pHeader->dwHeaderSize != ha2->pHeader->dwHeaderSize) printf(" - Header size is different\n"); if(ha1->pHeader->wFormatVersion != ha2->pHeader->wFormatVersion) printf(" - Format version is different\n"); if(ha1->pHeader->wSectorSize != ha2->pHeader->wSectorSize) printf(" - Sector size is different\n"); if(ha1->pHeader->HetTableSize64 != ha2->pHeader->HetTableSize64) printf(" - HET table size is different\n"); if(ha1->pHeader->BetTableSize64 != ha2->pHeader->BetTableSize64) printf(" - BET table size is different\n"); if(ha1->pHeader->dwHashTableSize != ha2->pHeader->dwHashTableSize) printf(" - Hash table size is different\n"); if(ha1->pHeader->dwBlockTableSize != ha2->pHeader->dwBlockTableSize) printf(" - Block table size is different\n"); } // Find all files in the first archive and compare them if(nError == ERROR_SUCCESS) { SFILE_FIND_DATA sf; TMPQFile * hf1; TMPQFile * hf2; HANDLE hFind = SFileFindFirstFile(hMpq1, "*", &sf, szListFile); bool bResult = true; while(hFind != NULL && bResult == true) { // printf("%s \r", sf.cFileName); SFileSetLocale(sf.lcLocale); // Open the first file if(!SFileOpenFileEx(hMpq1, sf.cFileName, 0, &hFile1)) printf("Failed to open the file %s in the first archive\n", sf.cFileName); if(!SFileOpenFileEx(hMpq2, sf.cFileName, 0, &hFile2)) printf("Failed to open the file %s in the second archive\n", sf.cFileName); if(hFile1 != NULL && hFile2 != NULL) { // Get the TMPQFile pointers hf1 = (TMPQFile *)hFile1; hf2 = (TMPQFile *)hFile2; // Compare the file sizes if(hf1->pFileEntry->dwFileSize != hf2->pFileEntry->dwFileSize) printf("Different file size: %s (%u x %u)\n", sf.cFileName, hf1->pFileEntry->dwFileSize, hf2->pFileEntry->dwFileSize); if(hf1->pFileEntry->dwCmpSize != hf2->pFileEntry->dwCmpSize) printf("Different cmpr size: %s (%u x %u)\n", sf.cFileName, hf1->pFileEntry->dwCmpSize, hf2->pFileEntry->dwCmpSize); if(hf1->pFileEntry->dwFlags != hf2->pFileEntry->dwFlags) printf("Different mpq flags: %s (%08X x %08X)\n", sf.cFileName, hf1->pFileEntry->dwFlags, hf2->pFileEntry->dwFlags); if(!CompareArchivedFiles(sf.cFileName, hFile1, hFile2, dwBlockSize)) printf("Different file data: %s\n", sf.cFileName); // if(!CompareArchivedFilesRR(sf.cFileName, hFile1, hFile2, dwBlockSize)) // printf("Different file data: %s\n", sf.cFileName); } // Close both files if(hFile2 != NULL) SFileCloseFile(hFile2); if(hFile1 != NULL) SFileCloseFile(hFile1); hFile2 = hFile1 = NULL; // Find the next file bResult = SFileFindNextFile(hFind, &sf); } // Close the find handle if(hFind != NULL) SFileFindClose(hFind); } // Close both archives clreol(); printf("================ MPQ compare complete ================\n"); if(hMpq2 != NULL) SFileCloseArchive(hMpq2); if(hMpq1 != NULL) SFileCloseArchive(hMpq1); return nError; } //----------------------------------------------------------------------------- // Searching all known MPQs #ifdef _WIN32 static int TestSearchOneArchive(const TCHAR * szMpqName) { SFILE_FIND_DATA sf; HANDLE hFind; HANDLE hMpq; const TCHAR * szExtension; bool bFound = true; // Get the file extension szExtension = _tcsrchr(szMpqName, _T('.')); if(szExtension == NULL) return ERROR_SUCCESS; // Only search defined extensions if(_tcsicmp(szExtension, _T(".mpq")) && _tcsnicmp(szExtension, _T(".SC2"), 4)) return ERROR_SUCCESS; _tprintf(_T("Searching %s ...\n"), szMpqName); // Try to open the MPQ if(SFileOpenArchive(szMpqName, 0, MPQ_OPEN_READ_ONLY, &hMpq)) { hFind = SFileFindFirstFile(hMpq, "*", &sf, NULL); if(hFind != NULL) { while(bFound) { if(sf.dwFileFlags & MPQ_FILE_DELETE_MARKER) _tprintf(_T("Delete marker: %s:%hs\n"), szMpqName, sf.cFileName); bFound = SFileFindNextFile(hFind, &sf); } } SFileCloseArchive(hMpq); } return ERROR_SUCCESS; } static int TestSearchAllArchives(const TCHAR * szSearchMask) { WIN32_FIND_DATA wf; LPTSTR szFilePart; HANDLE hFind; TCHAR szFullPath[MAX_PATH]; BOOL bFound = TRUE; // Initiate search _tcscpy(szFullPath, szSearchMask); szFilePart = _tcschr(szFullPath, _T('*')); assert(szFilePart != NULL); // Begin search hFind = FindFirstFile(szSearchMask, &wf); if(hFind != INVALID_HANDLE_VALUE) { while(bFound) { // Eliminate "." and ".." if(wf.cFileName[0] != _T('.')) { // Construct the full path _tcscpy(szFilePart, wf.cFileName); // If it a directory? if(wf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { _tcscat(szFullPath, _T("\\*")); TestSearchAllArchives(szFullPath); } else { TestSearchOneArchive(szFullPath); } } bFound = FindNextFile(hFind, &wf); } FindClose(hFind); } return ERROR_SUCCESS; } #endif //----------------------------------------------------------------------------- // Main // int main(void) { int nError = ERROR_SUCCESS; #if defined(_MSC_VER) && defined(_DEBUG) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif // defined(_MSC_VER) && defined(_DEBUG) // Mix the random number generator // srand(GetTickCount()); // Test for encrypted streams // FileStream_OpenFile(_T("e:\\Hry\\StarCraft II\\Updates\\SC2_HotS_20_BGDL\\SC2_HotS_20_BGDL_deDE.MPQE"), STREAM_PROVIDER_ENCRYPTED | BASE_PROVIDER_FILE); // Test structure sizes // if(nError == ERROR_SUCCESS) // nError = TestStructureSizes(); // if(nError == ERROR_SUCCESS) // nError = TestOpenLocalFile("C:\\Windows\\System32\\kernel32.dll"); // Test reading partial file // if(nError == ERROR_SUCCESS) // nError = TestPartFileRead(MAKE_PATH("2009 - PartialMPQs/patch.MPQ.part")); // if(nError == ERROR_SUCCESS) // { // CompareHuffmanCompressions7(); // nError = CompareHuffmanCompressions0(); // } // if(nError == ERROR_SUCCESS) // nError = ComparePklibCompressions(); // Test LZMA compression method against the code ripped from Starcraft II // if(nError == ERROR_SUCCESS) // nError = CompareLzmaCompressions(MPQ_SECTOR_SIZE); // Test compression methods // if(nError == ERROR_SUCCESS) // nError = TestSectorCompress(MPQ_SECTOR_SIZE); // Test the archive open and close // if(nError == ERROR_SUCCESS) // nError = TestArchiveOpenAndClose(MAKE_PATH("2012 - Longwu Online\\Data\\Scp.mpk")); // nError = TestArchiveOpenAndClose(MAKE_PATH("1997 - Diablo I\\DIABDAT.MPQ")); // nError = TestArchiveOpenAndClose(MAKE_PATH("2012 - War of the Immortals\\1\\Other.sqp")); // Test for bug reported by BlueRaja if(nError == ERROR_SUCCESS) nError = TestAddFilesToArchive(MAKE_PATH("Tya's Zerg Defense.SC2Map")); // if(nError == ERROR_SUCCESS) // nError = TestFindFiles(MAKE_PATH("2002 - Warcraft III/HumanEd.mpq")); // Create a big MPQ archive // if(nError == ERROR_SUCCESS) // nError = TestCreateArchive_PaliRoharBug(MAKE_PATH("Test.mpq")); // nError = TestCreateArchive(MAKE_PATH("Test.mpq")); // nError = TestCreateArchive((const TCHAR*)szUnicodeName1); // nError = TestCreateArchive((const TCHAR*)szUnicodeName2); // nError = TestCreateArchive((const TCHAR*)szUnicodeName3); // nError = TestCreateArchive((const TCHAR*)szUnicodeName4); // nError = TestCreateArchive((const TCHAR*)szUnicodeName5); // nError = TestCreateArchive((const TCHAR*)szUnicodeName6); // if(nError == ERROR_SUCCESS) // nError = TestAddFilesToMpq(MAKE_PATH("wow-update-13202.MPQ"), // "c:\\Tools32\\Arj32.exe", // "c:\\Tools32\\autoruns.chm", // "c:\\Tools32\\CPUEater.exe", // "c:\\Tools32\\dumpbin.exe", // "c:\\Tools32\\editbin.exe", // "c:\\Tools32\\fsg.ini", // "c:\\Tools32\\hiew8.ini", // "c:\\Tools32\\ida.bat", // "c:\\Tools32\\mp3.ini", // NULL); // if(nError == ERROR_SUCCESS) // nError = TestCreateArchiveFromMemory(MAKE_PATH("Test-leak.mpq")); // if(nError == ERROR_SUCCESS) // nError = TestFileReadAndWrite(MAKE_PATH("2002 - Warcraft III/(10)DustwallowKeys.w3m"), "war3map.j"); // Verify the archive signature // if(nError == ERROR_SUCCESS) // nError = TestSignatureVerify(MAKE_PATH("1998 - Starcraft/BW-1152.exe")); // nError = TestSignatureVerify(MAKE_PATH("2002 - Warcraft III/(10)DustwallowKeys.w3m")); // nError = TestSignatureVerify(MAKE_PATH("2002 - Warcraft III/War3TFT_121b_English.exe")); // nError = TestSignatureVerify(MAKE_PATH("2004 - World of Warcraft/WoW-2.3.3.7799-to-2.4.0.8089-enUS-patch.exe")); // nError = TestSignatureVerify(MAKE_PATH("2004 - World of Warcraft/standalone.MPQ")); // Compact the archive // if(nError == ERROR_SUCCESS) // nError = TestMpqCompacting(MAKE_PATH("wow-update-base-14333.MPQ")); // Create copy of the archive, appending some bytes before the MPQ header // if(nError == ERROR_SUCCESS) // nError = TestCreateArchiveCopy(MAKE_PATH("PartialMPQs/interface.MPQ.part"), MAKE_PATH("PartialMPQs/interface-copy.MPQ.part"), NULL); // if(nError == ERROR_SUCCESS) // { // nError = TestOpenPatchedArchive(MAKE_PATH("2013 - WoW\\12911\\world.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-13164.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-13205.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-13287.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-13329.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-13596.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-13623.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-13914.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14007.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14333.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14480.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14545.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-14946.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-15005.MPQ"), // MAKE_PATH("2013 - WoW\\12911\\wow-update-base-15050.MPQ"), // NULL); // } // if(nError == ERROR_SUCCESS) // { // nError = TestCompareTwoArchives(MAKE_PATH("Sound-copy.mpq"), // MAKE_PATH("Sound.mpq"), // NULL, // 0x1001); // } // if(nError == ERROR_SUCCESS) // nError = TestSearchAllArchives(MAKE_PATH("*")); return nError; }