aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src
diff options
context:
space:
mode:
Diffstat (limited to 'dep/CascLib/src')
-rw-r--r--dep/CascLib/src/CascBuildCfg.cpp1032
-rw-r--r--dep/CascLib/src/CascCommon.cpp23
-rw-r--r--dep/CascLib/src/CascCommon.h251
-rw-r--r--dep/CascLib/src/CascDecompress.cpp41
-rw-r--r--dep/CascLib/src/CascDecrypt.cpp300
-rw-r--r--dep/CascLib/src/CascDumpData.cpp334
-rw-r--r--dep/CascLib/src/CascFiles.cpp981
-rw-r--r--dep/CascLib/src/CascFindFile.cpp227
-rw-r--r--dep/CascLib/src/CascLib.def29
-rw-r--r--dep/CascLib/src/CascLib.h16
-rw-r--r--dep/CascLib/src/CascMndx.h (renamed from dep/CascLib/src/CascMndxRoot.h)12
-rw-r--r--dep/CascLib/src/CascOpenFile.cpp153
-rw-r--r--dep/CascLib/src/CascOpenStorage.cpp638
-rw-r--r--dep/CascLib/src/CascPort.h1
-rw-r--r--dep/CascLib/src/CascReadFile.cpp142
-rw-r--r--dep/CascLib/src/CascRootFile_Diablo3.cpp1189
-rw-r--r--dep/CascLib/src/CascRootFile_Mndx.cpp (renamed from dep/CascLib/src/CascMndxRoot.cpp)448
-rw-r--r--dep/CascLib/src/CascRootFile_Mndx_x86.asm (renamed from dep/CascLib/src/CascMndxRoot_x86.asm)158
-rw-r--r--dep/CascLib/src/CascRootFile_Ovr.cpp199
-rw-r--r--dep/CascLib/src/CascRootFile_WoW6.cpp531
-rw-r--r--dep/CascLib/src/common/Common.cpp226
-rw-r--r--dep/CascLib/src/common/Common.h24
-rw-r--r--dep/CascLib/src/common/Directory.h29
-rw-r--r--dep/CascLib/src/common/DumpContext.cpp153
-rw-r--r--dep/CascLib/src/common/DumpContext.h38
-rw-r--r--dep/CascLib/src/common/DynamicArray.cpp101
-rw-r--r--dep/CascLib/src/common/DynamicArray.h37
-rw-r--r--dep/CascLib/src/common/FileStream.cpp1
-rw-r--r--dep/CascLib/src/common/ListFile.cpp320
-rw-r--r--dep/CascLib/src/common/ListFile.h4
-rw-r--r--dep/CascLib/src/common/Map.cpp183
-rw-r--r--dep/CascLib/src/common/Map.h13
-rw-r--r--dep/CascLib/src/common/RootHandler.cpp78
-rw-r--r--dep/CascLib/src/common/RootHandler.h88
-rw-r--r--dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c4
35 files changed, 5221 insertions, 2783 deletions
diff --git a/dep/CascLib/src/CascBuildCfg.cpp b/dep/CascLib/src/CascBuildCfg.cpp
deleted file mode 100644
index f39f09d886a..00000000000
--- a/dep/CascLib/src/CascBuildCfg.cpp
+++ /dev/null
@@ -1,1032 +0,0 @@
-/*****************************************************************************/
-/* CascBuildCfg.cpp Copyright (c) Ladislav Zezula 2014 */
-/*---------------------------------------------------------------------------*/
-/* Build configuration for CascLib */
-/*---------------------------------------------------------------------------*/
-/* Date Ver Who Comment */
-/* -------- ---- --- ------- */
-/* 29.04.14 1.00 Lad The first version of CascBuildCfg.cpp */
-/*****************************************************************************/
-
-#define __CASCLIB_SELF__
-#include "CascLib.h"
-#include "CascCommon.h"
-
-//-----------------------------------------------------------------------------
-// Local functions
-
-static bool inline IsValueSeparator(LPBYTE pbVarValue)
-{
- return ((0 <= pbVarValue[0] && pbVarValue[0] <= 0x20) || (pbVarValue[0] == '|'));
-}
-
-static bool IsCharDigit(BYTE OneByte)
-{
- return ('0' <= OneByte && OneByte <= '9');
-}
-
-static void FreeCascBlob(PQUERY_KEY pBlob)
-{
- if(pBlob != NULL)
- {
- if(pBlob->pbData != NULL)
- CASC_FREE(pBlob->pbData);
-
- pBlob->pbData = NULL;
- pBlob->cbData = 0;
- }
-}
-
-static DWORD GetLocaleMask(const char * szTag)
-{
- if(!strcmp(szTag, "enUS"))
- return CASC_LOCALE_ENUS;
-
- if(!strcmp(szTag, "koKR"))
- return CASC_LOCALE_KOKR;
-
- if(!strcmp(szTag, "frFR"))
- return CASC_LOCALE_FRFR;
-
- if(!strcmp(szTag, "deDE"))
- return CASC_LOCALE_DEDE;
-
- if(!strcmp(szTag, "zhCN"))
- return CASC_LOCALE_ZHCN;
-
- if(!strcmp(szTag, "esES"))
- return CASC_LOCALE_ESES;
-
- if(!strcmp(szTag, "zhTW"))
- return CASC_LOCALE_ZHTW;
-
- if(!strcmp(szTag, "enGB"))
- return CASC_LOCALE_ENGB;
-
- if(!strcmp(szTag, "enCN"))
- return CASC_LOCALE_ENCN;
-
- if(!strcmp(szTag, "enTW"))
- return CASC_LOCALE_ENTW;
-
- if(!strcmp(szTag, "esMX"))
- return CASC_LOCALE_ESMX;
-
- if(!strcmp(szTag, "ruRU"))
- return CASC_LOCALE_RURU;
-
- if(!strcmp(szTag, "ptBR"))
- return CASC_LOCALE_PTBR;
-
- if(!strcmp(szTag, "itIT"))
- return CASC_LOCALE_ITIT;
-
- if(!strcmp(szTag, "ptPT"))
- return CASC_LOCALE_PTPT;
-
- return 0;
-}
-
-static bool IsInfoVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName, const char * szVarType)
-{
- size_t nLength;
-
- // Check the variable name
- nLength = strlen(szVarName);
- if((size_t)(szLineEnd - szLineBegin) > nLength)
- {
- // Check the variable name
- if(!_strnicmp(szLineBegin, szVarName, nLength))
- {
- // Skip variable name and the exclamation mark
- szLineBegin += nLength;
- if(szLineBegin < szLineEnd && szLineBegin[0] == '!')
- {
- // Skip the exclamation mark
- szLineBegin++;
-
- // Check the variable type
- nLength = strlen(szVarType);
- if((size_t)(szLineEnd - szLineBegin) > nLength)
- {
- // Check the variable name
- if(!_strnicmp(szLineBegin, szVarType, nLength))
- {
- // Skip variable type and the doublecolon
- szLineBegin += nLength;
- return (szLineBegin < szLineEnd && szLineBegin[0] == ':');
- }
- }
- }
- }
- }
-
- return false;
-}
-
-static const char * SkipInfoVariable(const char * szLineBegin, const char * szLineEnd)
-{
- while(szLineBegin < szLineEnd)
- {
- if(szLineBegin[0] == '|')
- return szLineBegin + 1;
-
- szLineBegin++;
- }
-
- return NULL;
-}
-
-static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
-{
- TCHAR * szIndexPath;
-
- // Cpmbine the index path
- szIndexPath = CombinePath(hs->szDataPath, szSubDir);
- if(DirectoryExists(szIndexPath))
- {
- hs->szIndexPath = szIndexPath;
- return hs->szIndexPath;
- }
-
- CASC_FREE(szIndexPath);
- return NULL;
-}
-
-TCHAR * AppendBlobText(TCHAR * szBuffer, LPBYTE pbData, DWORD cbData, TCHAR chSeparator)
-{
- // Put the separator, if any
- if(chSeparator != 0)
- *szBuffer++ = chSeparator;
-
- // Copy the blob data as text
- for(DWORD i = 0; i < cbData; i++)
- {
- *szBuffer++ = IntToHexChar[pbData[0] >> 0x04];
- *szBuffer++ = IntToHexChar[pbData[0] & 0x0F];
- pbData++;
- }
-
- // Terminate the string
- *szBuffer = 0;
-
- // Return new buffer position
- return szBuffer;
-}
-
-static int StringBlobToBinaryBlob(
- PQUERY_KEY pBlob,
- LPBYTE pbBlobBegin,
- LPBYTE pbBlobEnd)
-{
- // Sanity checks
- assert(pBlob != NULL && pBlob->pbData != NULL);
-
- // Reset the blob length
- pBlob->cbData = 0;
-
- // Convert the blob
- while(pbBlobBegin < pbBlobEnd)
- {
- BYTE DigitOne;
- BYTE DigitTwo;
-
- DigitOne = (BYTE)(AsciiToUpperTable_BkSlash[pbBlobBegin[0]] - '0');
- if(DigitOne > 9)
- DigitOne -= 'A' - '9' - 1;
-
- DigitTwo = (BYTE)(AsciiToUpperTable_BkSlash[pbBlobBegin[1]] - '0');
- if(DigitTwo > 9)
- DigitTwo -= 'A' - '9' - 1;
-
- if(DigitOne > 0x0F || DigitTwo > 0x0F || pBlob->cbData >= MAX_CASC_KEY_LENGTH)
- return ERROR_BAD_FORMAT;
-
- pBlob->pbData[pBlob->cbData++] = (DigitOne << 0x04) | DigitTwo;
- pbBlobBegin += 2;
- }
-
- return ERROR_SUCCESS;
-}
-
-
-static LPBYTE FindNextSeparator(PQUERY_KEY pFileBlob, LPBYTE pbFilePtr)
-{
- LPBYTE pbFileBegin = pFileBlob->pbData;
- LPBYTE pbFileEnd = pFileBlob->pbData + pFileBlob->cbData;
-
- if(pbFileBegin <= pbFilePtr && pbFilePtr < pbFileEnd)
- {
- while(pbFilePtr < pbFileEnd && pbFilePtr[0] != '|')
- pbFilePtr++;
-
- return pbFilePtr;
- }
-
- return NULL;
-}
-
-static bool GetNextFileLine(PQUERY_KEY pFileBlob, LPBYTE * ppbLineBegin, LPBYTE * ppbLineEnd)
-{
- LPBYTE pbLineBegin = *ppbLineBegin;
- LPBYTE pbLineEnd = *ppbLineEnd;
- LPBYTE pbFileEnd = pFileBlob->pbData + pFileBlob->cbData;
-
- // If there was a previous line, skip all end-of-line chars
- if(pbLineEnd != NULL)
- {
- // Go to the next line
- while(pbLineEnd < pbFileEnd && (pbLineEnd[0] == 0x0A || pbLineEnd[0] == 0x0D))
- pbLineEnd++;
- pbLineBegin = pbLineEnd;
-
- // If there is no more data, return false
- if(pbLineEnd >= pbFileEnd)
- return false;
- }
-
- // Skip all spaces before the line begins
- while(pbLineBegin < pbFileEnd && (pbLineBegin[0] == 0x09 || pbLineBegin[0] == 0x20))
- pbLineBegin++;
- pbLineEnd = pbLineBegin;
-
- // Go to the end of the line
- while(pbLineEnd < pbFileEnd && pbLineEnd[0] != 0x0A && pbLineEnd[0] != 0x0D)
- pbLineEnd++;
-
- // Give the results to the caller
- *ppbLineBegin = pbLineBegin;
- *ppbLineEnd = pbLineEnd;
- return true;
-}
-
-static LPBYTE CheckLineVariable(LPBYTE pbLineBegin, LPBYTE pbLineEnd, const char * szVarName)
-{
- size_t nLineLength = (size_t)(pbLineEnd - pbLineBegin);
- size_t nNameLength = strlen(szVarName);
-
- // If the line longer than the variable name?
- if(nLineLength > nNameLength)
- {
- if(!_strnicmp((const char *)pbLineBegin, szVarName, nNameLength))
- {
- // Skip the variable name
- pbLineBegin += nNameLength;
-
- // Skip the separator(s)
- while(pbLineBegin < pbLineEnd && IsValueSeparator(pbLineBegin))
- pbLineBegin++;
-
- // Check if there is "="
- if(pbLineBegin >= pbLineEnd || pbLineBegin[0] != '=')
- return NULL;
- pbLineBegin++;
-
- // Skip the separator(s)
- while(pbLineBegin < pbLineEnd && IsValueSeparator(pbLineBegin))
- pbLineBegin++;
-
- // Check if there is "="
- if(pbLineBegin >= pbLineEnd)
- return NULL;
-
- // Return the begin of the variable
- return pbLineBegin;
- }
- }
-
- return NULL;
-}
-
-static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const char * szLineEnd, bool bHexaValue)
-{
- const char * szLinePtr = szLineBegin;
-
- // Sanity checks
- assert(pVarBlob->pbData == NULL);
- assert(pVarBlob->cbData == 0);
-
- // Check length of the variable
- while(szLinePtr < szLineEnd && szLinePtr[0] != '|')
- szLinePtr++;
-
- // Allocate space for the blob
- if(bHexaValue)
- {
- // Initialize the blob
- pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) / 2);
- return StringBlobToBinaryBlob(pVarBlob, (LPBYTE)szLineBegin, (LPBYTE)szLinePtr);
- }
-
- // Initialize the blob
- pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1);
- pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin);
-
- // Check for success
- if(pVarBlob->pbData == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Copy the string
- memcpy(pVarBlob->pbData, szLineBegin, pVarBlob->cbData);
- pVarBlob->pbData[pVarBlob->cbData] = 0;
- return ERROR_SUCCESS;
-}
-
-
-static void AppendConfigFilePath(TCHAR * szFileName, PQUERY_KEY pFileKey)
-{
- // Get to the end of the file name
- szFileName = szFileName + _tcslen(szFileName);
-
- // Append the "config" directory
- _tcscat(szFileName, _T("/config"));
- szFileName += 7;
-
- // Append the first level directory
- szFileName = AppendBlobText(szFileName, pFileKey->pbData, 1, _T('/'));
- szFileName = AppendBlobText(szFileName, pFileKey->pbData + 1, 1, _T('/'));
- szFileName = AppendBlobText(szFileName, pFileKey->pbData, pFileKey->cbData, _T('/'));
-}
-
-static DWORD GetBlobCount(LPBYTE pbLineBegin, LPBYTE pbLineEnd)
-{
- DWORD dwBlobCount = 0;
-
- // Until we find an end of the line
- while(pbLineBegin < pbLineEnd)
- {
- // Skip the blob
- while(pbLineBegin < pbLineEnd && IsValueSeparator(pbLineBegin) == false)
- pbLineBegin++;
-
- // Increment the number of blobs
- dwBlobCount++;
-
- // Skip the separator
- while(pbLineBegin < pbLineEnd && IsValueSeparator(pbLineBegin))
- pbLineBegin++;
- }
-
- return dwBlobCount;
-}
-
-static int LoadBlobArray(
- PQUERY_KEY pBlob,
- DWORD dwMaxBlobs,
- LPBYTE pbLineBegin,
- LPBYTE pbLineEnd,
- LPBYTE pbBuffer,
- DWORD dwBufferSize)
-{
- LPBYTE pbBlobBegin = pbLineBegin;
- LPBYTE pbBlobEnd = pbLineBegin;
- int nError = ERROR_SUCCESS;
-
- // Sanity check
- assert(pbBuffer != NULL);
-
- // Until we find an end of the line
- while(pbBlobBegin < pbLineEnd)
- {
- // Convert the blob from string to binary
- if(dwBufferSize < MAX_CASC_KEY_LENGTH)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Find the end of the text blob
- while(pbBlobEnd < pbLineEnd && IsValueSeparator(pbBlobEnd) == false)
- pbBlobEnd++;
-
- // Convert the blob from ANSI to binary
- pBlob->pbData = pbBuffer;
- nError = StringBlobToBinaryBlob(pBlob, pbBlobBegin, pbBlobEnd);
- if(nError != ERROR_SUCCESS || dwMaxBlobs == 1)
- break;
-
- // Move the blob, buffer, and limits
- dwBufferSize -= MAX_CASC_KEY_LENGTH;
- pbBuffer += MAX_CASC_KEY_LENGTH;
- dwMaxBlobs--;
- pBlob++;
-
- // Skip the separator
- while(pbBlobEnd < pbLineEnd && IsValueSeparator(pbBlobEnd))
- pbBlobEnd++;
- pbBlobBegin = pbBlobEnd;
- }
-
- return nError;
-}
-
-static int LoadSingleBlob(PQUERY_KEY pBlob, LPBYTE pbBlobBegin, LPBYTE pbBlobEnd)
-{
- LPBYTE pbBuffer;
- size_t nLength = (pbBlobEnd - pbBlobBegin) / 2;
-
- // Check maximum size
- if(nLength > MAX_CASC_KEY_LENGTH)
- return ERROR_INVALID_PARAMETER;
-
- // Allocate the blob buffer
- pbBuffer = CASC_ALLOC(BYTE, MAX_CASC_KEY_LENGTH);
- if(pbBuffer == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- return LoadBlobArray(pBlob, 1, pbBlobBegin, pbBlobEnd, pbBuffer, MAX_CASC_KEY_LENGTH);
-}
-
-static PQUERY_KEY LoadMultipleBlobs(LPBYTE pbLineBegin, LPBYTE pbLineEnd, DWORD * pdwBlobCount)
-{
- PQUERY_KEY pBlobArray = NULL;
- LPBYTE pbBuffer = NULL;
- DWORD dwBlobCount = GetBlobCount(pbLineBegin, pbLineEnd);
- int nError;
-
- // Only if there is at least 1 blob
- if(dwBlobCount != 0)
- {
- // Allocate the array of blobs
- pBlobArray = CASC_ALLOC(QUERY_KEY, dwBlobCount);
- if(pBlobArray != NULL)
- {
- // Zero the blob array
- memset(pBlobArray, 0, dwBlobCount * sizeof(QUERY_KEY));
-
- // Allocate buffer for the blobs
- pbBuffer = CASC_ALLOC(BYTE, dwBlobCount * MAX_CASC_KEY_LENGTH);
- if(pbBuffer != NULL)
- {
- // Zero the buffer
- memset(pbBuffer, 0, dwBlobCount * MAX_CASC_KEY_LENGTH);
-
- // Load the entire blob array
- nError = LoadBlobArray(pBlobArray, dwBlobCount, pbLineBegin, pbLineEnd, pbBuffer, dwBlobCount * MAX_CASC_KEY_LENGTH);
- if(nError == ERROR_SUCCESS)
- {
- *pdwBlobCount = dwBlobCount;
- return pBlobArray;
- }
-
- // Free the buffer
- CASC_FREE(pbBuffer);
- }
-
- // Free the array of blobs
- CASC_FREE(pBlobArray);
- pBlobArray = NULL;
- }
-
- // Reset the blob count
- dwBlobCount = 0;
- }
-
- *pdwBlobCount = dwBlobCount;
- return pBlobArray;
-}
-
-static int LoadTextFile(const TCHAR * szFileName, PQUERY_KEY pFileBlob)
-{
- TFileStream * pStream;
- ULONGLONG FileSize = 0;
- int nError = ERROR_SUCCESS;
-
- // Open the agent file
- pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
- if(pStream != NULL)
- {
- // Retrieve its size
- FileStream_GetSize(pStream, &FileSize);
-
- // Load the file to memory
- if(0 < FileSize && FileSize < 0x100000)
- {
- // Initialize the blob
- pFileBlob->cbData = (DWORD)FileSize;
- pFileBlob->pbData = CASC_ALLOC(BYTE, pFileBlob->cbData + 1);
-
- // Load the file data into the blob
- if(pFileBlob->pbData != NULL)
- {
- FileStream_Read(pStream, NULL, pFileBlob->pbData, (DWORD)FileSize);
- pFileBlob->pbData[pFileBlob->cbData] = 0;
- }
- else
- nError = ERROR_NOT_ENOUGH_MEMORY;
- }
- else
- nError = ERROR_INVALID_PARAMETER;
-
- FileStream_Close(pStream);
- }
- else
- nError = GetLastError();
-
- return nError;
-}
-
-static int GetGameType(TCascStorage * hs, LPBYTE pbVarBegin, LPBYTE pbLineEnd)
-{
- // Alpha build of Heroes of the Storm
- if((pbLineEnd - pbVarBegin) == 4 && !_strnicmp((const char *)pbVarBegin, "Hero", 4))
- {
- hs->dwGameInfo = CASC_GAME_HOTS;
- return ERROR_SUCCESS;
- }
-
- // Alpha build of World of Warcraft - Warlords of Draenor
- if((pbLineEnd - pbVarBegin) == 3 && !_strnicmp((const char *)pbVarBegin, "WoW", 3))
- {
- hs->dwGameInfo = CASC_GAME_WOW6;
- return ERROR_SUCCESS;
- }
-
- // Diablo III BETA 2.2.0
- if((pbLineEnd - pbVarBegin) == 7 && !_strnicmp((const char *)pbVarBegin, "Diablo3", 7))
- {
- hs->dwGameInfo = CASC_GAME_DIABLO3;
- return ERROR_SUCCESS;
- }
-
- // An unknown game
- assert(false);
- return ERROR_BAD_FORMAT;
-}
-
-// "B29049"
-// "WOW-18125patch6.0.1"
-// "30013_Win32_2_2_0_Ptr_ptr"
-static int GetBuildNumber(TCascStorage * hs, LPBYTE pbVarBegin, LPBYTE pbLineEnd)
-{
- DWORD dwBuildNumber = 0;
-
- // Skip all non-digit characters
- while(pbVarBegin < pbLineEnd && IsCharDigit(pbVarBegin[0]) == false)
- pbVarBegin++;
-
- // Convert the build number
- while(pbVarBegin < pbLineEnd && IsCharDigit(pbVarBegin[0]))
- dwBuildNumber = (dwBuildNumber * 10) + (*pbVarBegin++ - '0');
-
- assert(dwBuildNumber != 0);
- hs->dwBuildNumber = dwBuildNumber;
- return (dwBuildNumber != 0) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
-}
-
-static int GetDefaultLocaleMask(TCascStorage * hs, PQUERY_KEY pTagsString)
-{
- char * szTagEnd = (char *)pTagsString->pbData + pTagsString->cbData;
- char * szTagPtr = (char *)pTagsString->pbData;
- char * szNext;
- DWORD dwLocaleMask = 0;
-
- while(szTagPtr < szTagEnd)
- {
- // Get the next part
- szNext = strchr(szTagPtr, ' ');
- if(szNext != NULL)
- *szNext++ = 0;
-
- // Check whether the current tag is a language identifier
- dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr);
-
- // Get the next part
- if(szNext == NULL)
- break;
-
- // Skip spaces
- while(szNext < szTagEnd && szNext[0] == ' ')
- szNext++;
- szTagPtr = szNext;
- }
-
- hs->dwDefaultLocale = dwLocaleMask;
- return ERROR_SUCCESS;
-}
-
-static int FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PQUERY_KEY pFileBlob)
-{
- TCHAR * szFileName;
- int nError;
-
- // Construct the local file name
- szFileName = NewStr(hs->szDataPath, 8 + 3 + 3 + 32);
- if(szFileName == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Add the part where the config file path is
- AppendConfigFilePath(szFileName, pFileKey);
-
- // Load the config file
- nError = LoadTextFile(szFileName, pFileBlob);
- if(nError == ERROR_SUCCESS)
- {
- // Verify the blob's MD5
- if(!VerifyDataBlockHash(pFileBlob->pbData, pFileBlob->cbData, pFileKey->pbData))
- {
- FreeCascBlob(pFileBlob);
- nError = ERROR_BAD_FORMAT;
- }
- }
-
- CASC_FREE(szFileName);
- return nError;
-}
-
-static int ParseInfoFile(TCascStorage * hs, PQUERY_KEY pFileBlob)
-{
- QUERY_KEY Active = {NULL, 0};
- QUERY_KEY TagString = {NULL, 0};
- QUERY_KEY CdnHost = {NULL, 0};
- QUERY_KEY CdnPath = {NULL, 0};
- const char * szLineBegin1 = NULL;
- const char * szLinePtr1 = NULL;
- const char * szLineBegin2 = NULL;
- const char * szLineEnd1 = NULL;
- const char * szLineEnd2 = NULL;
- const char * szFileEnd = (const char *)(pFileBlob->pbData + pFileBlob->cbData);
- const char * szFilePtr = (const char *)pFileBlob->pbData;
- int nError = ERROR_BAD_FORMAT;
-
- // Find the first line
- szLineBegin1 = szFilePtr;
- while(szFilePtr < szFileEnd)
- {
- // Check for the end of the line
- if(szFilePtr[0] == 0x0D || szFilePtr[0] == 0x0A)
- {
- szLineEnd1 = szFilePtr;
- break;
- }
-
- szFilePtr++;
- }
-
- while (szFilePtr < szFileEnd)
- {
- szLinePtr1 = szLineBegin1;
-
- // Skip the newline character(s)
- while (szFilePtr < szFileEnd && (szFilePtr[0] == 0x0D || szFilePtr[0] == 0x0A))
- szFilePtr++;
-
- // Find the next line
- szLineBegin2 = szFilePtr;
- while (szFilePtr < szFileEnd)
- {
- // Check for the end of the line
- if (szFilePtr[0] == 0x0D || szFilePtr[0] == 0x0A)
- {
- szLineEnd2 = szFilePtr;
- break;
- }
-
- szFilePtr++;
- }
-
- // Find the build key, CDN config key and the URL path
- while (szLinePtr1 < szLineEnd1)
- {
- // Check for variables we need
- if (IsInfoVariable(szLinePtr1, szLineEnd1, "Active", "DEC"))
- LoadInfoVariable(&Active, szLineBegin2, szLineEnd2, false);
- if (IsInfoVariable(szLinePtr1, szLineEnd1, "Build Key", "HEX"))
- LoadInfoVariable(&hs->CdnBuildKey, szLineBegin2, szLineEnd2, true);
- if (IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Key", "HEX"))
- LoadInfoVariable(&hs->CdnConfigKey, szLineBegin2, szLineEnd2, true);
- if (IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Hosts", "STRING"))
- LoadInfoVariable(&CdnHost, szLineBegin2, szLineEnd2, false);
- if (IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Path", "STRING"))
- LoadInfoVariable(&CdnPath, szLineBegin2, szLineEnd2, false);
- if (IsInfoVariable(szLinePtr1, szLineEnd1, "Tags", "STRING"))
- LoadInfoVariable(&TagString, szLineBegin2, szLineEnd2, false);
-
- // Move both line pointers
- szLinePtr1 = SkipInfoVariable(szLinePtr1, szLineEnd1);
- if (szLineBegin1 == NULL)
- break;
-
- szLineBegin2 = SkipInfoVariable(szLineBegin2, szLineEnd2);
- if (szLineBegin2 == NULL)
- break;
- }
-
- // Stop parsing if found active config
- if (Active.pbData != NULL && *Active.pbData == '1')
- break;
- }
-
- // All four must be present
- if(hs->CdnBuildKey.pbData != NULL &&
- hs->CdnConfigKey.pbData != NULL &&
- CdnHost.pbData != NULL &&
- CdnPath.pbData != NULL)
- {
- // Merge the CDN host and CDN path
- hs->szUrlPath = CASC_ALLOC(TCHAR, CdnHost.cbData + CdnPath.cbData + 1);
- if(hs->szUrlPath != NULL)
- {
- CopyString(hs->szUrlPath, (char *)CdnHost.pbData, CdnHost.cbData);
- CopyString(hs->szUrlPath + CdnHost.cbData, (char *)CdnPath.pbData, CdnPath.cbData);
- nError = ERROR_SUCCESS;
- }
- }
-
- // If we found tags, we can extract language build from it
- if(TagString.pbData != NULL)
- GetDefaultLocaleMask(hs, &TagString);
-
- FreeCascBlob(&CdnHost);
- FreeCascBlob(&CdnPath);
- FreeCascBlob(&TagString);
- return nError;
-}
-
-static int ParseAgentFile(TCascStorage * hs, PQUERY_KEY pFileBlob)
-{
- LPBYTE pbBlobBegin = pFileBlob->pbData;
- LPBYTE pbBlobEnd;
- int nError = ERROR_SUCCESS;
-
- // Extract the CDN build hash
- pbBlobEnd = FindNextSeparator(pFileBlob, pbBlobBegin);
- if(pbBlobEnd != NULL)
- {
- // Convert the string to a blob
- nError = LoadSingleBlob(&hs->CdnBuildKey, pbBlobBegin, pbBlobEnd);
-
- // Move to the next part
- if(pbBlobEnd[0] == _T('|'))
- pbBlobEnd++;
- pbBlobBegin = pbBlobEnd;
- }
-
- // Extract the CDN config hash
- pbBlobEnd = FindNextSeparator(pFileBlob, pbBlobBegin);
- if(pbBlobEnd != NULL)
- {
- // Convert the string to a blob
- nError = LoadSingleBlob(&hs->CdnConfigKey, pbBlobBegin, pbBlobEnd);
-
- // Move to the next part
- if(pbBlobEnd[0] == _T('|'))
- pbBlobEnd++;
- pbBlobBegin = pbBlobEnd;
- }
-
- // Skip the intermediate part
- pbBlobEnd = FindNextSeparator(pFileBlob, pbBlobBegin);
- if(pbBlobEnd != NULL)
- {
- // Move to the next part
- if(pbBlobEnd[0] == _T('|'))
- pbBlobEnd++;
- pbBlobBegin = pbBlobEnd;
- }
-
- // Extract the URL config hash
- pbBlobEnd = FindNextSeparator(pFileBlob, pbBlobBegin);
- if(pbBlobEnd != NULL)
- {
- // Convert the string to a blob
- hs->szUrlPath = NewStrFromAnsi(pbBlobBegin, pbBlobEnd);
- }
-
- // Verify all variables
- if(hs->CdnBuildKey.pbData == NULL || hs->CdnConfigKey.pbData == NULL || hs->szUrlPath == NULL)
- nError = ERROR_BAD_FORMAT;
- return nError;
-}
-
-static int LoadCdnConfigFile(TCascStorage * hs, PQUERY_KEY pFileBlob)
-{
- LPBYTE pbLineBegin = pFileBlob->pbData;
- LPBYTE pbVarBegin;
- LPBYTE pbLineEnd = NULL;
- int nError;
-
- while(pbLineBegin != NULL)
- {
- // Get the next line
- if(!GetNextFileLine(pFileBlob, &pbLineBegin, &pbLineEnd))
- break;
-
- // Archive group
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "archive-group");
- if(pbVarBegin != NULL)
- {
- nError = LoadSingleBlob(&hs->ArchiveGroup, pbVarBegin, pbLineEnd);
- if(nError != ERROR_SUCCESS)
- return nError;
- continue;
- }
-
- // Archives
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "archives");
- if(pbVarBegin != NULL)
- {
- hs->pArchiveArray = LoadMultipleBlobs(pbVarBegin, pbLineEnd, &hs->ArchiveCount);
- if(hs->pArchiveArray == NULL || hs->ArchiveCount == 0)
- return ERROR_BAD_FORMAT;
- continue;
- }
-
- // Patch archive group
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "patch-archive-group");
- if(pbVarBegin != NULL)
- {
- LoadSingleBlob(&hs->PatchArchiveGroup, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Patch archives
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "patch-archives");
- if(pbVarBegin != NULL)
- {
- hs->pPatchArchiveArray = LoadMultipleBlobs(pbVarBegin, pbLineEnd, &hs->PatchArchiveCount);
- continue;
- }
- }
-
- // Check if all required fields are present
- if(hs->ArchiveGroup.pbData == NULL || hs->ArchiveGroup.cbData == 0 || hs->pArchiveArray == NULL || hs->ArchiveCount == 0)
- return ERROR_BAD_FORMAT;
-
- return ERROR_SUCCESS;
-}
-
-static int LoadCdnBuildFile(TCascStorage * hs, PQUERY_KEY pFileBlob)
-{
- LPBYTE pbLineBegin = pFileBlob->pbData;
- LPBYTE pbVarBegin;
- LPBYTE pbLineEnd = NULL;
-
- while(pbLineBegin != NULL)
- {
- // Get the next line
- if(!GetNextFileLine(pFileBlob, &pbLineBegin, &pbLineEnd))
- break;
-
- // Game name
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "build-product");
- if(pbVarBegin != NULL)
- {
- GetGameType(hs, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Game build number
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "build-name");
- if(pbVarBegin != NULL)
- {
- GetBuildNumber(hs, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Root
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "root");
- if(pbVarBegin != NULL)
- {
- LoadSingleBlob(&hs->RootKey, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Patch
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "patch");
- if(pbVarBegin != NULL)
- {
- LoadSingleBlob(&hs->PatchKey, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Download
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "download");
- if(pbVarBegin != NULL)
- {
- LoadSingleBlob(&hs->DownloadKey, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Install
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "install");
- if(pbVarBegin != NULL)
- {
- LoadSingleBlob(&hs->InstallKey, pbVarBegin, pbLineEnd);
- continue;
- }
-
- // Encoding keys
- pbVarBegin = CheckLineVariable(pbLineBegin, pbLineEnd, "encoding");
- if(pbVarBegin != NULL)
- {
- hs->pEncodingKeys = LoadMultipleBlobs(pbVarBegin, pbLineEnd, &hs->EncodingKeys);
- if(hs->pEncodingKeys == NULL || hs->EncodingKeys != 2)
- return ERROR_BAD_FORMAT;
-
- hs->EncodingKey = hs->pEncodingKeys[0];
- hs->EncodingEKey = hs->pEncodingKeys[1];
- continue;
- }
- }
-
- // Check the encoding keys
- if(hs->pEncodingKeys == NULL || hs->EncodingKeys == 0)
- return ERROR_BAD_FORMAT;
- return ERROR_SUCCESS;
-}
-
-//-----------------------------------------------------------------------------
-// Public functions
-
-int LoadBuildInfo(TCascStorage * hs)
-{
- QUERY_KEY InfoFile = {NULL, 0};
- QUERY_KEY FileData = {NULL, 0};
- TCHAR * szAgentFile;
- TCHAR * szInfoFile;
- bool bBuildConfigComplete = false;
- int nError = ERROR_SUCCESS;
-
- // Since HOTS build 30027, the game uses build.info file for storage info
- if(bBuildConfigComplete == false)
- {
- szInfoFile = CombinePath(hs->szRootPath, _T(".build.info"));
- if(szInfoFile != NULL)
- {
- nError = LoadTextFile(szInfoFile, &InfoFile);
- if(nError == ERROR_SUCCESS)
- {
- // Parse the info file
- nError = ParseInfoFile(hs, &InfoFile);
- if(nError == ERROR_SUCCESS)
- bBuildConfigComplete = true;
-
- // Free the loaded blob
- FreeCascBlob(&InfoFile);
- }
-
- CASC_FREE(szInfoFile);
- }
- }
-
- // If the info file has not been loaded, try the legacy .build.db
- if(bBuildConfigComplete == false)
- {
- szAgentFile = CombinePath(hs->szRootPath, _T(".build.db"));
- if(szAgentFile != NULL)
- {
- nError = LoadTextFile(szAgentFile, &FileData);
- if(nError == ERROR_SUCCESS)
- {
- nError = ParseAgentFile(hs, &FileData);
- if(nError == ERROR_SUCCESS)
- bBuildConfigComplete = true;
-
- FreeCascBlob(&FileData);
- }
- CASC_FREE(szAgentFile);
- }
- }
-
- // If the .build.info and .build.db file hasn't been loaded,
- if(nError == ERROR_SUCCESS && bBuildConfigComplete == false)
- {
- nError = ERROR_FILE_CORRUPT;
- }
-
- // Load the configuration file
- if(nError == ERROR_SUCCESS)
- {
- nError = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey, &FileData);
- if(nError == ERROR_SUCCESS)
- {
- nError = LoadCdnConfigFile(hs, &FileData);
- FreeCascBlob(&FileData);
- }
- }
-
- // Load the build file
- if(nError == ERROR_SUCCESS)
- {
- nError = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey, &FileData);
- if(nError == ERROR_SUCCESS)
- {
- nError = LoadCdnBuildFile(hs, &FileData);
- FreeCascBlob(&FileData);
- }
- }
-
- // Fill the index directory
- if(nError == ERROR_SUCCESS)
- {
- // First, check for more common "data" subdirectory
- if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
- return ERROR_SUCCESS;
-
- // Second, try the "darch" subdirectory (older builds of HOTS - Alpha)
- if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
- return ERROR_SUCCESS;
-
- nError = ERROR_FILE_NOT_FOUND;
- }
-
- return nError;
-}
diff --git a/dep/CascLib/src/CascCommon.cpp b/dep/CascLib/src/CascCommon.cpp
index 8ad7d716b82..34c3df66b5c 100644
--- a/dep/CascLib/src/CascCommon.cpp
+++ b/dep/CascLib/src/CascCommon.cpp
@@ -65,3 +65,26 @@ ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes)
return Value;
}
+
+void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes)
+{
+ ValueAsBytes[0] = (Value >> 0x18) & 0xFF;
+ ValueAsBytes[1] = (Value >> 0x10) & 0xFF;
+ ValueAsBytes[2] = (Value >> 0x08) & 0xFF;
+ ValueAsBytes[3] = (Value >> 0x00) & 0xFF;
+}
+
+//-----------------------------------------------------------------------------
+// Common fre routine of a CASC blob
+
+void FreeCascBlob(PQUERY_KEY pBlob)
+{
+ if(pBlob != NULL)
+ {
+ if(pBlob->pbData != NULL)
+ CASC_FREE(pBlob->pbData);
+
+ pBlob->pbData = NULL;
+ pBlob->cbData = 0;
+ }
+}
diff --git a/dep/CascLib/src/CascCommon.h b/dep/CascLib/src/CascCommon.h
index 7a2b4071dc6..42e158bfcb2 100644
--- a/dep/CascLib/src/CascCommon.h
+++ b/dep/CascLib/src/CascCommon.h
@@ -23,9 +23,13 @@
#include "CascPort.h"
#include "common/Common.h"
+#include "common/DynamicArray.h"
#include "common/Map.h"
#include "common/FileStream.h"
+#include "common/Directory.h"
#include "common/ListFile.h"
+#include "common/DumpContext.h"
+#include "common/RootHandler.h"
// Headers from LibTomCrypt
#include "libtomcrypt/src/headers/tomcrypt.h"
@@ -36,16 +40,17 @@
//-----------------------------------------------------------------------------
// CascLib private defines
-#define CASC_GAME_HOTS 0x00010000 // Heroes of the Storm
-#define CASC_GAME_WOW6 0x00020000 // World of Warcraft - Warlords of Draenor
-#define CASC_GAME_DIABLO3 0x00030000 // Diablo 3 Since PTR 2.2.0
-#define CASC_GAME_MASK 0xFFFF0000 // Mask for getting game ID
+#define CASC_GAME_HOTS 0x00010000 // Heroes of the Storm
+#define CASC_GAME_WOW6 0x00020000 // World of Warcraft - Warlords of Draenor
+#define CASC_GAME_DIABLO3 0x00030000 // Diablo 3 since PTR 2.2.0
+#define CASC_GAME_OVERWATCH 0x00040000 // Overwatch since PTR 24919
+#define CASC_GAME_STARCRAFT2 0x00050000 // Starcraft II - Legacy of the Void, since build 38996
+#define CASC_GAME_MASK 0xFFFF0000 // Mask for getting game ID
#define CASC_INDEX_COUNT 0x10
#define CASC_FILE_KEY_SIZE 0x09 // Size of the file key
#define CASC_MAX_DATA_FILES 0x100
-#define CASC_MAX_MAR_FILES 3 // Maximum of 3 MAR files are supported
-#define CASC_MNDX_SIGNATURE 0x58444E4D // 'MNDX'
+#define CASC_EXTRA_FILES 0x20 // Number of extra entries to be reserved for additionally inserted files
#define CASC_SEARCH_HAVE_NAME 0x0001 // Indicated that previous search found a name
@@ -71,41 +76,28 @@
#define CASC_PACKAGE_BUFFER 0x1000
-//-----------------------------------------------------------------------------
-// On-disk structures
-
-typedef struct _FILE_LOCALE_BLOCK
-{
- DWORD NumberOfFiles; // Number of entries
- DWORD Flags;
- DWORD Locales; // File locale mask (CASC_LOCALE_XXX)
+#ifndef _maxchars
+#define _maxchars(buff) ((sizeof(buff) / sizeof(buff[0])) - 1)
+#endif
- // Followed by a block of 32-bit integers (count: NumberOfFiles)
- // Followed by the MD5 and file name hash (count: NumberOfFiles)
+//-----------------------------------------------------------------------------
+// In-memory structures
+// See http://pxr.dk/wowdev/wiki/index.php?title=CASC for more information
-} FILE_LOCALE_BLOCK, *PFILE_LOCALE_BLOCK;
+struct TFileStream;
-typedef struct _FILE_ROOT_ENTRY
+typedef enum _CBLD_TYPE
{
- DWORD EncodingKey[4]; // MD5 of the file
- ULONGLONG FileNameHash; // Jenkins hash of the file name
-
-} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
+ CascBuildNone = 0, // No build type found
+ CascBuildInfo, // .build.info
+ CascBuildDb, // .build.db (older storages)
+} CBLD_TYPE, *PCBLD_TYPE;
-typedef struct _ROOT_BLOCK_INFO
+typedef struct _ENCODING_KEY
{
- PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
- PDWORD pInt32Array; // Pointer to the array of 32-bit integers
- PFILE_ROOT_ENTRY pRootEntries;
-
-} ROOT_BLOCK_INFO, *PROOT_BLOCK_INFO;
-
-//-----------------------------------------------------------------------------
-// In-memory structures
+ BYTE Value[MD5_HASH_SIZE]; // MD5 of the file
-class TMndxFindResult;
-struct TFileStream;
-struct _MAR_FILE;
+} ENCODING_KEY, *PENCODING_KEY;
typedef struct _CASC_INDEX_ENTRY
{
@@ -140,25 +132,34 @@ typedef struct _CASC_FILE_FRAME
BYTE md5[MD5_HASH_SIZE]; // MD5 hash of the file sector
} CASC_FILE_FRAME, *PCASC_FILE_FRAME;
+// The encoding file is in the form of:
+// * File header
+// * String block #1
+// * Table A header
+// * Table A entries
+// * Table B header
+// * Table B entries
+// * String block #2
+// http://pxr.dk/wowdev/wiki/index.php?title=CASC#Key_CASC_Files
typedef struct _CASC_ENCODING_HEADER
{
BYTE Magic[2]; // "EN"
- BYTE field_2;
- BYTE field_3;
- BYTE field_4;
- BYTE field_5[2];
- BYTE field_7[2];
- BYTE NumSegments[4]; // Number of entries (big endian)
- BYTE field_D[4];
+ BYTE Version; // Expected to be 1 by CascLib
+ BYTE ChecksumSizeA; // The length of the checksums in Encoding Table
+ BYTE ChecksumSizeB; // The length of the checksums in Encoding Layout Table
+ BYTE Flags_TableA[2]; // Flags for Encoding Table
+ BYTE Flags_TableB[2]; // Flags for Encoding Layout Table
+ BYTE Entries_TableA[4]; // Number of segments in Encoding Table (big endian)
+ BYTE Entries_TableB[4]; // Number of segments in Encoding Layout Table (big endian)
BYTE field_11;
- BYTE SegmentsPos[4]; // Offset of encoding segments
+ BYTE Size_StringTable1[4]; // Size of the string block #1
} CASC_ENCODING_HEADER, *PCASC_ENCODING_HEADER;
typedef struct _CASC_ENCODING_ENTRY
{
- USHORT KeyCount; // Number of subitems
- BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
+ USHORT KeyCount; // Number of index keys
+ BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key
// Followed by the index keys
@@ -166,87 +167,21 @@ typedef struct _CASC_ENCODING_ENTRY
// Followed by the index keys (number of items = KeyCount)
} CASC_ENCODING_ENTRY, *PCASC_ENCODING_ENTRY;
-typedef struct _CASC_ROOT_LOCALE_BLOCK
-{
- DWORD NumberOfFiles; // Number of entries
- DWORD Flags;
- DWORD FileLocales; // File locales (CASC_LOCALE_XXX)
-
- // Followed by a block of 32-bit integers (count: NumberOfFiles)
- // Followed by the MD5 and file name hash (count: NumberOfFiles)
-
-} CASC_ROOT_LOCALE_BLOCK, *PCASC_ROOT_LOCALE_BLOCK;
-
-// Root file entry for CASC storages with MNDX root file (Heroes of the Storm)
-// Corresponds to the in-file structure
-typedef struct _CASC_ROOT_ENTRY_MNDX
+// A version of CASC_ENCODING_ENTRY with one index key
+typedef struct _CASC_ENCODING_ENTRY_1
{
- DWORD Flags; // High 8 bits: Flags, low 24 bits: package index
- BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file
- DWORD FileSize; // Uncompressed file size, in bytes
-
-} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX;
-
-// Root file entry for CASC storages without MNDX root file (World of Warcraft 6.0+)
-// Does not match to the in-file structure of the root entry
-typedef struct _CASC_ROOT_ENTRY
-{
- ULONGLONG FileNameHash; // Jenkins hash of the file name
- DWORD SumValue; // Sum value
- DWORD Locales; // Locale flags of the file
- DWORD EncodingKey[4]; // File encoding key (MD5)
-
-} CASC_ROOT_ENTRY, *PCASC_ROOT_ENTRY;
-
-// Definition of the hash table for CASC root items
-typedef struct _CASC_ROOT_HASH_TABLE
-{
- PCASC_ROOT_ENTRY TablePtr; // Pointer to the CASC root table
- DWORD TableSize; // Total size of the root table
- DWORD ItemCount; // Number of items currently in the table
-
-} CASC_ROOT_HASH_TABLE, *PCASC_ROOT_HASH_TABLE;
-
-typedef struct _CASC_MNDX_INFO
-{
- bool bRootFileLoaded; // true if the root info file was properly loaded
- BYTE RootFileName[MD5_HASH_SIZE]; // Name (aka MD5) of the root file
- DWORD HeaderVersion; // Must be <= 2
- DWORD FormatVersion;
- DWORD field_1C;
- DWORD field_20;
- DWORD MarInfoOffset; // Offset of the first MAR entry info
- DWORD MarInfoCount; // Number of the MAR info entries
- DWORD MarInfoSize; // Size of the MAR info entry
- DWORD MndxEntriesOffset;
- DWORD MndxEntriesTotal; // Total number of MNDX root entries
- DWORD MndxEntriesValid; // Number of valid MNDX root entries
- DWORD MndxEntrySize; // Size of one MNDX root entry
- struct _MAR_FILE * pMarFile1; // File name list for the packages
- struct _MAR_FILE * pMarFile2; // File name list for names stripped of package names
- struct _MAR_FILE * pMarFile3; // File name list for complete names
- PCASC_ROOT_ENTRY_MNDX pMndxEntries;
- PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
-
-} CASC_MNDX_INFO, *PCASC_MNDX_INFO;
-
-typedef struct _CASC_PACKAGE
-{
- char * szFileName; // Pointer to file name
- size_t nLength; // Length of the file name
-
-} CASC_PACKAGE, *PCASC_PACKAGE;
+ USHORT KeyCount; // Number of index keys
+ BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
+ BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key
+ BYTE IndexKey[MD5_HASH_SIZE]; // File index key
-typedef struct _CASC_PACKAGES
-{
- char * szNameBuffer; // Pointer to the buffer for file names
- size_t NameEntries; // Number of name entries in Names
- size_t NameBufferUsed; // Number of bytes used in the name buffer
- size_t NameBufferMax; // Total size of the name buffer
+} CASC_ENCODING_ENTRY_1, *PCASC_ENCODING_ENTRY_1;
- CASC_PACKAGE Packages[1]; // List of packages
+#define GET_INDEX_KEY(pEncodingEntry) (pEncodingEntry->EncodingKey + MD5_HASH_SIZE)
+#define FAKE_ENCODING_ENTRY_SIZE (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE)
-} CASC_PACKAGES, *PCASC_PACKAGES;
+//-----------------------------------------------------------------------------
+// Structures for CASC storage and CASC file
typedef struct _TCascStorage
{
@@ -254,6 +189,7 @@ typedef struct _TCascStorage
const TCHAR * szIndexFormat; // Format of the index file name
TCHAR * szRootPath; // This is the game directory
TCHAR * szDataPath; // This is the directory where data files are
+ TCHAR * szBuildFile; // Build file name (.build.info or .build.db)
TCHAR * szIndexPath; // This is the directory where index files are
TCHAR * szUrlPath; // URL to the Blizzard servers
DWORD dwRefCount; // Number of references
@@ -262,40 +198,29 @@ typedef struct _TCascStorage
DWORD dwFileBeginDelta; // This is number of bytes to shift back from archive offset (from index entry) to actual begin of file data
DWORD dwDefaultLocale; // Default locale, read from ".build.info"
+ CBLD_TYPE BuildFileType; // Type of the build file
+
QUERY_KEY CdnConfigKey;
QUERY_KEY CdnBuildKey;
-
- PQUERY_KEY pArchiveArray; // Array of the archives
- QUERY_KEY ArchiveGroup; // Name of the group archive file
- DWORD ArchiveCount; // Number of archives in the array
-
- PQUERY_KEY pPatchArchiveArray; // Array of the patch archives
- QUERY_KEY PatchArchiveGroup; // Name of the patch group archive file
- DWORD PatchArchiveCount; // Number of patch archives in the array
-
+ QUERY_KEY ArchivesGroup; // Key array of the "archive-group"
+ QUERY_KEY ArchivesKey; // Key array of the "archives"
+ QUERY_KEY PatchArchivesKey; // Key array of the "patch-archives"
QUERY_KEY RootKey;
QUERY_KEY PatchKey;
QUERY_KEY DownloadKey;
QUERY_KEY InstallKey;
-
- PQUERY_KEY pEncodingKeys;
QUERY_KEY EncodingKey;
- QUERY_KEY EncodingEKey;
- DWORD EncodingKeys;
TFileStream * DataFileArray[CASC_MAX_DATA_FILES]; // Data file handles
CASC_MAPPING_TABLE KeyMapping[CASC_INDEX_COUNT]; // Key mapping
PCASC_MAP pIndexEntryMap; // Map of index entries
- PCASC_ENCODING_HEADER pEncodingHeader; // The encoding file
- PCASC_ENCODING_ENTRY * ppEncodingEntries; // Map of encoding entries
- size_t nEncodingEntries;
+ QUERY_KEY EncodingFile; // Content of the ENCODING file
+ PCASC_MAP pEncodingMap; // Map of encoding entries
+ DYNAMIC_ARRAY ExtraEntries; // Extra encoding entries
- CASC_ROOT_HASH_TABLE RootTable; // Hash table for the root entries
-
- PCASC_MNDX_INFO pMndxInfo; // Used for storages which have MNDX/MAR file
- PCASC_PACKAGES pPackages; // Linear list of present packages
+ TRootHandler * pRootHandler; // Common handler for various ROOT file formats
} TCascStorage;
@@ -336,16 +261,19 @@ typedef struct _TCascFile
typedef struct _TCascSearch
{
TCascStorage * hs; // Pointer to the storage handle
- const char * szClassName;
- TCHAR * szListFile;
+ const char * szClassName; // Contains "TCascSearch"
+ TCHAR * szListFile; // Name of the listfile
void * pCache; // Listfile cache
- TMndxFindResult * pStruct1C; // Search structure for MNDX info
- char * szMask;
+ char * szMask; // Search mask
char szFileName[MAX_PATH]; // Buffer for the file name
- char szNormName[MAX_PATH]; // Buffer for normalized file name
- DWORD RootIndex; // Root index of the previously found item
- DWORD dwState; // Pointer to the state (0 = listfile, 1 = nameless, 2 = done)
- BYTE BitArray[1]; // Bit array of already-reported files
+
+ // Provider-specific data
+ void * pRootContext; // Root-specific search context
+ size_t IndexLevel1; // Root-specific search context
+ size_t IndexLevel2; // Root-specific search context
+ DWORD dwState; // Pointer to the search state (0 = listfile, 1 = nameless, 2 = done)
+
+ BYTE BitArray[1]; // Bit array of encoding keys. Set for each entry that has already been reported
} TCascSearch;
@@ -384,10 +312,15 @@ DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes);
DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes);
ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes);
+void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes);
+void FreeCascBlob(PQUERY_KEY pQueryKey);
+
//-----------------------------------------------------------------------------
-// Build configuration reading
+// Text file parsing (CascFiles.cpp)
int LoadBuildInfo(TCascStorage * hs);
+int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory);
+int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, PQUERY_KEY pEncodingKey, char * szFileName, size_t nMaxChars);
//-----------------------------------------------------------------------------
// Internal file functions
@@ -395,11 +328,20 @@ int LoadBuildInfo(TCascStorage * hs);
TCascStorage * IsValidStorageHandle(HANDLE hStorage);
TCascFile * IsValidFileHandle(HANDLE hFile);
-PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex);
-PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex);
+PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, PDWORD PtrIndex);
PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey);
-int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer);
+int CascDecompress(LPBYTE pvOutBuffer, PDWORD pcbOutBuffer, LPBYTE pvInBuffer, DWORD cbInBuffer);
+int CascDecrypt (LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex);
+int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer);
+
+//-----------------------------------------------------------------------------
+// Support for ROOT file
+
+int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
+int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
+int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
+int RootHandler_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask);
//-----------------------------------------------------------------------------
// Dumping CASC data structures
@@ -409,8 +351,7 @@ void CascDumpSparseArray(const char * szFileName, void * pvSparseArray);
void CascDumpNameFragTable(const char * szFileName, void * pvMarFile);
void CascDumpFileNames(const char * szFileName, void * pvMarFile);
void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs);
-void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo);
-void CascDumpRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, const char * szFormat, const TCHAR * szListFile, int nDumpLevel);
+void CascDumpEncodingEntry(TCascStorage * hs, TDumpContext * dc, PCASC_ENCODING_ENTRY pEncodingEntry, int nDumpLevel);
void CascDumpFile(const char * szFileName, HANDLE hFile);
#endif // _DEBUG
diff --git a/dep/CascLib/src/CascDecompress.cpp b/dep/CascLib/src/CascDecompress.cpp
index 2858bcec5ab..290b08d64d8 100644
--- a/dep/CascLib/src/CascDecompress.cpp
+++ b/dep/CascLib/src/CascDecompress.cpp
@@ -13,9 +13,9 @@
#include "CascCommon.h"
//-----------------------------------------------------------------------------
-// Local functions
+// Public functions
-static int Decompress_ZLIB(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
+int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
{
z_stream z; // Stream information for zlib
int nResult;
@@ -44,40 +44,3 @@ static int Decompress_ZLIB(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInB
// Return an error code
return (nResult == Z_OK || nResult == Z_STREAM_END) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
}
-
-//-----------------------------------------------------------------------------
-// Public functions
-
-int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer)
-{
- LPBYTE pbOutBuffer = (LPBYTE)pvOutBuffer;
- LPBYTE pbInBuffer = (LPBYTE)pvInBuffer;
- DWORD cbOutBuffer = *pcbOutBuffer; // Current size of the output buffer
- BYTE uCompression; // Decompressions applied to the data
-
- // Verify buffer sizes
- if(cbInBuffer <= 1)
- return 0;
-
- // Get applied compression types and decrement data length
- uCompression = *pbInBuffer++;
- cbInBuffer--;
-
- // Perform the decompressions
- switch(uCompression)
- {
- case 'N': // Uncompressed
-
- assert(cbOutBuffer == cbInBuffer);
- memcpy(pbOutBuffer, pbInBuffer, cbInBuffer);
- *pcbOutBuffer = cbOutBuffer;
- return ERROR_SUCCESS;
-
- case 'Z': // ZLIB
-
- return Decompress_ZLIB(pbOutBuffer, pcbOutBuffer, pbInBuffer, cbInBuffer);
- }
-
- assert(false);
- return ERROR_NOT_SUPPORTED;
-}
diff --git a/dep/CascLib/src/CascDecrypt.cpp b/dep/CascLib/src/CascDecrypt.cpp
new file mode 100644
index 00000000000..5f4dc77dfe8
--- /dev/null
+++ b/dep/CascLib/src/CascDecrypt.cpp
@@ -0,0 +1,300 @@
+/*****************************************************************************/
+/* CascDecrypt.cpp Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Decryption functions for CascLib */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 31.10.15 1.00 Lad The first version of CascDecrypt.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+typedef struct _CASC_ENCRYPTION_KEY
+{
+ ULONGLONG KeyName; // "Name" of the key
+ BYTE Key[0x10]; // The key itself
+} CASC_ENCRYPTION_KEY, *PCASC_ENCRYPTION_KEY;
+
+typedef struct _CASC_SALSA20
+{
+ DWORD Key[0x10];
+ DWORD dwRounds;
+
+} CASC_SALSA20, *PCASC_SALSA20;
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+//keyName FB680CB6A8BF81F3 key 62D90EFA7F36D71C398AE2F1FE37BDB9 keyNameSize 8 keySize 16
+//keyName 402CD9D8D6BFED98 key AEB0EADEA47612FE6C041A03958DF241 keyNameSize 8 keySize 16
+//keyName 87AEBBC9C4E6B601 key 685E86C6063DFDA6C9E85298076B3D42 keyNameSize 8 keySize 16
+//keyName A19C4F859F6EFA54 key 0196CB6F5ECBAD7CB5283891B9712B4B keyNameSize 8 keySize 16
+//keyName 11A9203C9881710A key 2E2CB8C397C2F24ED0B5E452F18DC267 keyNameSize 8 keySize 16
+//keyName DBD3371554F60306 key 34E397ACE6DD30EEFDC98A2AB093CD3C keyNameSize 8 keySize 16
+//keyName DEE3A0521EFF6F03 key AD740CE3FFFF9231468126985708E1B9 keyNameSize 8 keySize 16
+//keyName 8C9106108AA84F07 key 53D859DDA2635A38DC32E72B11B32F29 keyNameSize 8 keySize 16
+
+static CASC_ENCRYPTION_KEY CascKeys[] =
+{
+ {0xFB680CB6A8BF81F3ULL, {0x62, 0xD9, 0x0E, 0xFA, 0x7F, 0x36, 0xD7, 0x1C, 0x39, 0x8A, 0xE2, 0xF1, 0xFE, 0x37, 0xBD, 0xB9}},
+ {0x402CD9D8D6BFED98ULL, {0xAE, 0xB0, 0xEA, 0xDE, 0xA4, 0x76, 0x12, 0xFE, 0x6C, 0x04, 0x1A, 0x03, 0x95, 0x8D, 0xF2, 0x41}},
+ {0x87AEBBC9C4E6B601ULL, {0x68, 0x5E, 0x86, 0xC6, 0x06, 0x3D, 0xFD, 0xA6, 0xC9, 0xE8, 0x52, 0x98, 0x07, 0x6B, 0x3D, 0x42}},
+ {0xA19C4F859F6EFA54ULL, {0x01, 0x96, 0xCB, 0x6F, 0x5E, 0xCB, 0xAD, 0x7C, 0xB5, 0x28, 0x38, 0x91, 0xB9, 0x71, 0x2B, 0x4B}},
+ {0x11A9203C9881710AULL, {0x2E, 0x2C, 0xB8, 0xC3, 0x97, 0xC2, 0xF2, 0x4E, 0xD0, 0xB5, 0xE4, 0x52, 0xF1, 0x8D, 0xC2, 0x67}},
+ {0xDBD3371554F60306ULL, {0x34, 0xE3, 0x97, 0xAC, 0xE6, 0xDD, 0x30, 0xEE, 0xFD, 0xC9, 0x8A, 0x2A, 0xB0, 0x93, 0xCD, 0x3C}},
+ {0xDEE3A0521EFF6F03ULL, {0xAD, 0x74, 0x0C, 0xE3, 0xFF, 0xFF, 0x92, 0x31, 0x46, 0x81, 0x26, 0x98, 0x57, 0x08, 0xE1, 0xB9}},
+ {0x8C9106108AA84F07ULL, {0x53, 0xD8, 0x59, 0xDD, 0xA2, 0x63, 0x5A, 0x38, 0xDC, 0x32, 0xE7, 0x2B, 0x11, 0xB3, 0x2F, 0x29}},
+ {0x49166D358A34D815ULL, {0x66, 0x78, 0x68, 0xCD, 0x94, 0xEA, 0x01, 0x35, 0xB9, 0xB1, 0x6C, 0x93, 0xB1, 0x12, 0x4A, 0xBA}},
+ {0, {0}}
+};
+
+static const char * szKeyConstant16 = "expand 16-byte k";
+static const char * szKeyConstant32 = "expand 32-byte k";
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static DWORD Rol32(DWORD dwValue, DWORD dwRolCount)
+{
+ return (dwValue << dwRolCount) | (dwValue >> (32 - dwRolCount));
+}
+
+static LPBYTE FindCascKey(ULONGLONG KeyName)
+{
+ // Search the known keys
+ for(size_t i = 0; CascKeys[i].KeyName != 0; i++)
+ {
+ if(CascKeys[i].KeyName == KeyName)
+ return CascKeys[i].Key;
+ }
+
+ // Key not found
+ return NULL;
+}
+
+static void Initialize(PCASC_SALSA20 pState, LPBYTE pbKey, DWORD cbKeyLength, LPBYTE pbVector)
+{
+ const char * szConstants = (cbKeyLength == 32) ? szKeyConstant32 : szKeyConstant16;
+ DWORD KeyIndex = cbKeyLength - 0x10;
+
+ memset(pState, 0, sizeof(CASC_SALSA20));
+ pState->Key[0] = *(PDWORD)(szConstants + 0x00);
+ pState->Key[1] = *(PDWORD)(pbKey + 0x00);
+ pState->Key[2] = *(PDWORD)(pbKey + 0x04);
+ pState->Key[3] = *(PDWORD)(pbKey + 0x08);
+ pState->Key[4] = *(PDWORD)(pbKey + 0x0C);
+ pState->Key[5] = *(PDWORD)(szConstants + 0x04);
+ pState->Key[6] = *(PDWORD)(pbVector + 0x00);
+ pState->Key[7] = *(PDWORD)(pbVector + 0x04);
+ pState->Key[8] = 0;
+ pState->Key[9] = 0;
+ pState->Key[10] = *(PDWORD)(szConstants + 0x08);
+ pState->Key[11] = *(PDWORD)(pbKey + KeyIndex + 0x00);
+ pState->Key[12] = *(PDWORD)(pbKey + KeyIndex + 0x04);
+ pState->Key[13] = *(PDWORD)(pbKey + KeyIndex + 0x08);
+ pState->Key[14] = *(PDWORD)(pbKey + KeyIndex + 0x0C);
+ pState->Key[15] = *(PDWORD)(szConstants + 0x0C);
+
+ pState->dwRounds = 20;
+}
+
+static int Decrypt(PCASC_SALSA20 pState, LPBYTE pbOutBuffer, LPBYTE pbInBuffer, size_t cbInBuffer)
+{
+ LPBYTE pbXorValue;
+ DWORD KeyMirror[0x10];
+ DWORD XorValue[0x10];
+ DWORD BlockSize;
+ DWORD i;
+
+ // Repeat until we have data to read
+ while(cbInBuffer > 0)
+ {
+ // Create the copy of the key
+ memcpy(KeyMirror, pState->Key, sizeof(KeyMirror));
+
+ // Shuffle the key
+ for(i = 0; i < pState->dwRounds; i += 2)
+ {
+ KeyMirror[0x04] ^= Rol32((KeyMirror[0x00] + KeyMirror[0x0C]), 0x07);
+ KeyMirror[0x08] ^= Rol32((KeyMirror[0x04] + KeyMirror[0x00]), 0x09);
+ KeyMirror[0x0C] ^= Rol32((KeyMirror[0x08] + KeyMirror[0x04]), 0x0D);
+ KeyMirror[0x00] ^= Rol32((KeyMirror[0x0C] + KeyMirror[0x08]), 0x12);
+
+ KeyMirror[0x09] ^= Rol32((KeyMirror[0x05] + KeyMirror[0x01]), 0x07);
+ KeyMirror[0x0D] ^= Rol32((KeyMirror[0x09] + KeyMirror[0x05]), 0x09);
+ KeyMirror[0x01] ^= Rol32((KeyMirror[0x0D] + KeyMirror[0x09]), 0x0D);
+ KeyMirror[0x05] ^= Rol32((KeyMirror[0x01] + KeyMirror[0x0D]), 0x12);
+
+ KeyMirror[0x0E] ^= Rol32((KeyMirror[0x0A] + KeyMirror[0x06]), 0x07);
+ KeyMirror[0x02] ^= Rol32((KeyMirror[0x0E] + KeyMirror[0x0A]), 0x09);
+ KeyMirror[0x06] ^= Rol32((KeyMirror[0x02] + KeyMirror[0x0E]), 0x0D);
+ KeyMirror[0x0A] ^= Rol32((KeyMirror[0x06] + KeyMirror[0x02]), 0x12);
+
+ KeyMirror[0x03] ^= Rol32((KeyMirror[0x0F] + KeyMirror[0x0B]), 0x07);
+ KeyMirror[0x07] ^= Rol32((KeyMirror[0x03] + KeyMirror[0x0F]), 0x09);
+ KeyMirror[0x0B] ^= Rol32((KeyMirror[0x07] + KeyMirror[0x03]), 0x0D);
+ KeyMirror[0x0F] ^= Rol32((KeyMirror[0x0B] + KeyMirror[0x07]), 0x12);
+
+ KeyMirror[0x01] ^= Rol32((KeyMirror[0x00] + KeyMirror[0x03]), 0x07);
+ KeyMirror[0x02] ^= Rol32((KeyMirror[0x01] + KeyMirror[0x00]), 0x09);
+ KeyMirror[0x03] ^= Rol32((KeyMirror[0x02] + KeyMirror[0x01]), 0x0D);
+ KeyMirror[0x00] ^= Rol32((KeyMirror[0x03] + KeyMirror[0x02]), 0x12);
+
+ KeyMirror[0x06] ^= Rol32((KeyMirror[0x05] + KeyMirror[0x04]), 0x07);
+ KeyMirror[0x07] ^= Rol32((KeyMirror[0x06] + KeyMirror[0x05]), 0x09);
+ KeyMirror[0x04] ^= Rol32((KeyMirror[0x07] + KeyMirror[0x06]), 0x0D);
+ KeyMirror[0x05] ^= Rol32((KeyMirror[0x04] + KeyMirror[0x07]), 0x12);
+
+ KeyMirror[0x0B] ^= Rol32((KeyMirror[0x0A] + KeyMirror[0x09]), 0x07);
+ KeyMirror[0x08] ^= Rol32((KeyMirror[0x0B] + KeyMirror[0x0A]), 0x09);
+ KeyMirror[0x09] ^= Rol32((KeyMirror[0x08] + KeyMirror[0x0B]), 0x0D);
+ KeyMirror[0x0A] ^= Rol32((KeyMirror[0x09] + KeyMirror[0x08]), 0x12);
+
+ KeyMirror[0x0C] ^= Rol32((KeyMirror[0x0F] + KeyMirror[0x0E]), 0x07);
+ KeyMirror[0x0D] ^= Rol32((KeyMirror[0x0C] + KeyMirror[0x0F]), 0x09);
+ KeyMirror[0x0E] ^= Rol32((KeyMirror[0x0D] + KeyMirror[0x0C]), 0x0D);
+ KeyMirror[0x0F] ^= Rol32((KeyMirror[0x0E] + KeyMirror[0x0D]), 0x12);
+ }
+
+ // Set the number of remaining bytes
+ pbXorValue = (LPBYTE)XorValue;
+ BlockSize = (DWORD)CASCLIB_MIN(cbInBuffer, 0x40);
+
+ // Prepare the XOR constants
+ for(i = 0; i < 16; i++)
+ {
+ XorValue[i] = KeyMirror[i] + pState->Key[i];
+ }
+
+ // Decrypt the block
+ for(i = 0; i < BlockSize; i++)
+ {
+ pbOutBuffer[i] = pbInBuffer[i] ^ pbXorValue[i];
+ }
+
+ pState->Key[8] = pState->Key[8] + 1;
+ if(pState->Key[8] == 0)
+ pState->Key[9] = pState->Key[9] + 1;
+
+ // Adjust buffers
+ pbOutBuffer += BlockSize;
+ pbInBuffer += BlockSize;
+ cbInBuffer -= BlockSize;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int Decrypt_Salsa20(LPBYTE pbOutBuffer, LPBYTE pbInBuffer, size_t cbInBuffer, LPBYTE pbKey, DWORD cbKeySize, LPBYTE pbVector)
+{
+ CASC_SALSA20 SalsaState;
+
+ Initialize(&SalsaState, pbKey, cbKeySize, pbVector);
+ return Decrypt(&SalsaState, pbOutBuffer, pbInBuffer, cbInBuffer);
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int CascDecrypt(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex)
+{
+ ULONGLONG KeyName = 0;
+ LPBYTE pbBufferEnd = pbInBuffer + cbInBuffer;
+ LPBYTE pbKey;
+ DWORD KeyNameSize;
+ DWORD dwShift = 0;
+ DWORD IVSize;
+ BYTE Vector[0x08];
+ BYTE EncryptionType;
+ int nError;
+
+ // Verify and retrieve the key name size
+ if(pbInBuffer >= pbBufferEnd)
+ return ERROR_FILE_CORRUPT;
+ if(pbInBuffer[0] != 0 && pbInBuffer[0] != 8)
+ return ERROR_NOT_SUPPORTED;
+ KeyNameSize = *pbInBuffer++;
+
+ // Copy the key name
+ if((pbInBuffer + KeyNameSize) >= pbBufferEnd)
+ return ERROR_FILE_CORRUPT;
+ memcpy(&KeyName, pbInBuffer, KeyNameSize);
+ pbInBuffer += KeyNameSize;
+
+ // Verify and retrieve the Vector size
+ if(pbInBuffer >= pbBufferEnd)
+ return ERROR_FILE_CORRUPT;
+ if(pbInBuffer[0] != 4 && pbInBuffer[0] != 8)
+ return ERROR_NOT_SUPPORTED;
+ IVSize = *pbInBuffer++;
+
+ // Copy the initialization vector
+ if((pbInBuffer + IVSize) >= pbBufferEnd)
+ return ERROR_FILE_CORRUPT;
+ memset(Vector, 0, sizeof(Vector));
+ memcpy(Vector, pbInBuffer, IVSize);
+ pbInBuffer += IVSize;
+
+ // Verify and retrieve the encryption type
+ if(pbInBuffer >= pbBufferEnd)
+ return ERROR_FILE_CORRUPT;
+ if(pbInBuffer[0] != 'S' && pbInBuffer[0] != 'A')
+ return ERROR_NOT_SUPPORTED;
+ EncryptionType = *pbInBuffer++;
+
+ // Do we have enough space in the output buffer?
+ if((DWORD)(pbBufferEnd - pbInBuffer) > pcbOutBuffer[0])
+ return ERROR_INSUFFICIENT_BUFFER;
+
+ // Check if we know the key
+ pbKey = FindCascKey(KeyName);
+ if(pbKey == NULL)
+ return ERROR_UNKNOWN_FILE_KEY;
+
+ // Shuffle the Vector with the block index
+ // Note that there's no point to go beyond 32 bits, unless the file has
+ // more than 0xFFFFFFFF frames.
+ for(int i = 0; i < sizeof(dwFrameIndex); i++)
+ {
+ Vector[i] = Vector[i] ^ (BYTE)((dwFrameIndex >> dwShift) & 0xFF);
+ dwShift += 8;
+ }
+
+ // Perform the decryption-specific action
+ switch(EncryptionType)
+ {
+ case 'S': // Salsa20
+ nError = Decrypt_Salsa20(pbOutBuffer, pbInBuffer, (pbBufferEnd - pbInBuffer), pbKey, 0x10, Vector);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Supply the size of the output buffer
+ pcbOutBuffer[0] = (DWORD)(pbBufferEnd - pbInBuffer);
+ return ERROR_SUCCESS;
+
+// case 'A':
+// return ERROR_NOT_SUPPORTED;
+ }
+
+ assert(false);
+ return ERROR_NOT_SUPPORTED;
+}
+
+int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer)
+{
+ // Check the buffer size
+ if((cbInBuffer - 1) > pcbOutBuffer[0])
+ return ERROR_INSUFFICIENT_BUFFER;
+
+ // Copy the data
+ memcpy(pbOutBuffer, pbInBuffer, cbInBuffer);
+ pcbOutBuffer[0] = cbInBuffer;
+ return ERROR_SUCCESS;
+}
+
diff --git a/dep/CascLib/src/CascDumpData.cpp b/dep/CascLib/src/CascDumpData.cpp
index 5dc9110b6cd..3c0e385ac07 100644
--- a/dep/CascLib/src/CascDumpData.cpp
+++ b/dep/CascLib/src/CascDumpData.cpp
@@ -11,14 +11,14 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
-#include "CascMndxRoot.h"
+#include "CascMndx.h"
-#ifdef _DEBUG // The entire file is only valid for debug purposes
+#ifdef _DEBUG // The entire feature is only valid for debug purposes
//-----------------------------------------------------------------------------
// Forward definitions
-LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd);
+//LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd);
//-----------------------------------------------------------------------------
// Sort compare functions
@@ -50,160 +50,6 @@ static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1,
}
//-----------------------------------------------------------------------------
-// Local functions
-
-static char * StringFromMD5(LPBYTE md5, char * szBuffer)
-{
- return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
-}
-
-static char * FormatFileName(const char * szFormat, TCascStorage * hs)
-{
- char * szFileName;
- char * szSrc;
- char * szTrg;
-
- // Create copy of the file name
- szFileName = szSrc = szTrg = NewStr(szFormat, 0);
- if(szFileName != NULL)
- {
- // Format the file name
- while(szSrc[0] != 0)
- {
- if(szSrc[0] == '%')
- {
- // Replace "%build%" with a build number
- if(!strncmp(szSrc, "%build%", 7))
- {
- szTrg += sprintf(szTrg, "%u", hs->dwBuildNumber);
- szSrc += 7;
- continue;
- }
- }
-
- // Just copy the character
- *szTrg++ = *szSrc++;
- }
-
- // Terminate the target file name
- szTrg[0] = 0;
- }
-
- return szFileName;
-}
-
-FILE * CreateDumpFile(const char * szFormat, TCascStorage * hs)
-{
- FILE * fp = NULL;
- char * szFileName;
-
- // Validate the storage handle
- if(hs != NULL)
- {
- // Format the real file name
- szFileName = FormatFileName(szFormat, hs);
- if(szFileName != NULL)
- {
- // Create the dump file
- fp = fopen(szFileName, "wt");
- CASC_FREE(szFileName);
- }
- }
-
- return fp;
-}
-
-static void DumpIndexKey(
- FILE * fp,
- TCascStorage * hs,
- LPBYTE pbIndexKey,
- int nDumpLevel)
-{
- PCASC_INDEX_ENTRY pIndexEntry;
- TCascFile * hf;
- QUERY_KEY QueryKey;
- HANDLE hFile;
- BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
- char szBuffer[0x20];
-
- QueryKey.pbData = pbIndexKey;
- QueryKey.cbData = MD5_HASH_SIZE;
- pIndexEntry = FindIndexEntry(hs, &QueryKey);
- if(pIndexEntry != NULL)
- {
- ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
- DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E);
- DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
-
- // Mask the file offset
- FileOffset &= 0x3FFFFFFF;
- fprintf(fp, " data.%03u at 0x%08x (0x%lx bytes)\n",
- ArchIndex,
- (DWORD)FileOffset,
- FileSize);
-
- if(nDumpLevel > 2)
- {
- QueryKey.pbData = pIndexEntry->IndexKey;
- QueryKey.cbData = MD5_HASH_SIZE;
- if(CascOpenFileByIndexKey((HANDLE)hs, &QueryKey, 0, &hFile))
- {
- // Make sure that the data file is open and frame header loaded
- CascGetFileSize(hFile, NULL);
- hf = IsValidFileHandle(hFile);
- assert(hf->pStream != NULL);
-
- // Read the header area
- FileOffset = hf->HeaderOffset - BLTE_HEADER_DELTA;
- FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea));
- CascCloseFile(hFile);
-
- // Dump the header area
- fprintf(fp, " FileSize: %X Rest: %s\n",
- ConvertBytesToInteger_4_LE(&HeaderArea[0x10]),
- StringFromBinary(&HeaderArea[0x14], 10, szBuffer));
- }
- }
- }
- else
- {
- fprintf(fp, " NO INDEX ENTRY\n");
- }
-}
-
-static void DumpEncodingEntry(
- FILE * fp,
- TCascStorage * hs,
- PCASC_ENCODING_ENTRY pEncodingEntry,
- int nDumpLevel)
-{
- LPBYTE pbIndexKey;
- char szMd5[MD5_STRING_SIZE];
-
- // If the encoding key exists
- if(pEncodingEntry != NULL)
- {
- fprintf(fp, " Size %lx Key Count: %u\n",
- ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE),
- pEncodingEntry->KeyCount);
-
- // Dump all index keys
- pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
- for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++)
- {
- fprintf(fp, " %s\n", StringFromMD5(pbIndexKey, szMd5));
- DumpIndexKey(fp, hs, pbIndexKey, nDumpLevel);
- pbIndexKey += MD5_HASH_SIZE;
- }
-
- }
- else
- {
- fprintf(fp, " NO ENCODING KEYS\n");
- }
-}
-
-//-----------------------------------------------------------------------------
// Public functions
void CascDumpSparseArray(const char * szFileName, void * pvSparseArray)
@@ -323,27 +169,92 @@ void CascDumpFileNames(const char * szFileName, void * pvMarFile)
Struct1C.FreeStruct40();
}
-void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo)
+void CascDumpIndexEntry(
+ TCascStorage * /* hs */,
+ TDumpContext * dc,
+ PCASC_INDEX_ENTRY pIndexEntry,
+ int /* nDumpLevel */)
{
- PCASC_ROOT_ENTRY_MNDX pRootEntry;
- FILE * fp;
- char szMd5[MD5_STRING_SIZE];
+ if(pIndexEntry != NULL)
+ {
+ ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
+ DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E);
+ DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
- // Create the dump file
- fp = fopen(szFileName, "wt");
- if(fp != NULL)
+ // Mask the file offset
+ FileOffset &= 0x3FFFFFFF;
+ dump_print(dc, " data.%03u at 0x%08x (0x%lx bytes)\n",
+ ArchIndex,
+ (DWORD)FileOffset,
+ FileSize);
+
+ //if(nDumpLevel > 2)
+ //{
+ // QueryKey.pbData = pIndexEntry->IndexKey;
+ // QueryKey.cbData = MD5_HASH_SIZE;
+ // if(CascOpenFileByIndexKey((HANDLE)hs, &QueryKey, 0, &hFile))
+ // {
+ // // Make sure that the data file is open and frame header loaded
+ // CascGetFileSize(hFile, NULL);
+ // hf = IsValidFileHandle(hFile);
+ // assert(hf->pStream != NULL);
+
+ // // Read the header area
+ // FileOffset = hf->HeaderOffset - BLTE_HEADER_DELTA;
+ // FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea));
+ // CascCloseFile(hFile);
+
+ // // Dump the header area
+ // dump_print(dc, " FileSize: %X Rest: %s\n",
+ // ConvertBytesToInteger_4_LE(&HeaderArea[0x10]),
+ // StringFromBinary(&HeaderArea[0x14], 10, szBuffer));
+ // }
+ //}
+ }
+ else
{
- fprintf(fp, "Indx Fl+Asset EncodingKey FileSize\n==== ======== ================================ ========\n");
- for(DWORD i = 0; i < pMndxInfo->MndxEntriesValid; i++)
- {
- pRootEntry = pMndxInfo->ppValidEntries[i];
+ dump_print(dc, " NO INDEX ENTRY\n");
+ }
+}
+
+void CascDumpEncodingEntry(
+ TCascStorage * hs,
+ TDumpContext * dc,
+ PCASC_ENCODING_ENTRY pEncodingEntry,
+ int nDumpLevel)
+{
+ PCASC_INDEX_ENTRY pIndexEntry;
+ QUERY_KEY QueryKey;
+ LPBYTE pbIndexKey;
+ char szMd5[MD5_STRING_SIZE+1];
- fprintf(fp, "%04X %08X %s %08X\n", i,
- pRootEntry->Flags,
- StringFromMD5(pRootEntry->EncodingKey, szMd5),
- pRootEntry->FileSize);
+ // If the encoding key exists
+ if(pEncodingEntry != NULL)
+ {
+ dump_print(dc, " Size %lx Key Count: %u\n",
+ ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE),
+ pEncodingEntry->KeyCount);
+
+ // Dump all index keys
+ pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
+ for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++, pbIndexKey += MD5_HASH_SIZE)
+ {
+ // Dump the index key
+ dump_print(dc, " %s\n", StringFromMD5(pbIndexKey, szMd5));
+
+ // Dump the index entry as well
+ if(nDumpLevel >= DUMP_LEVEL_INDEX_ENTRIES)
+ {
+ QueryKey.pbData = pbIndexKey;
+ QueryKey.cbData = MD5_HASH_SIZE;
+ pIndexEntry = FindIndexEntry(hs, &QueryKey);
+ CascDumpIndexEntry(hs, dc, pIndexEntry, nDumpLevel);
+ }
}
- fclose(fp);
+ }
+ else
+ {
+ dump_print(dc, " NO ENCODING KEYS\n");
}
}
@@ -380,7 +291,7 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
ArchOffset &= 0x3FFFFFFF;
- fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey));
+ fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, (DWORD)ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey));
}
CASC_FREE(ppIndexEntries);
@@ -390,81 +301,6 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
}
}
-void CascDumpRootFile(
- TCascStorage * hs,
- LPBYTE pbRootFile,
- DWORD cbRootFile,
- const char * szFormat,
- const TCHAR * szListFile,
- int nDumpLevel)
-{
- PCASC_ENCODING_ENTRY pEncodingEntry;
- ROOT_BLOCK_INFO BlockInfo;
- PLISTFILE_MAP pListMap;
- QUERY_KEY EncodingKey;
- LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
- LPBYTE pbFilePointer;
- FILE * fp;
- char szOneLine[0x100];
- DWORD i;
-
- // This function only dumps WoW-style root file
- assert(*(PDWORD)pbRootFile != CASC_MNDX_SIGNATURE);
-
- // Create the dump file
- fp = CreateDumpFile(szFormat, hs);
- if(fp != NULL)
- {
- // Create the listfile map
-// DWORD dwTickCount = GetTickCount();
- pListMap = ListFile_CreateMap(szListFile);
-// dwTickCount = GetTickCount() - dwTickCount;
-
- // Dump the root entries as-is
- for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
- {
- // Validate the root block
- pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
- if(pbFilePointer == NULL)
- break;
-
- // Dump the locale block
- fprintf(fp, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
- "=========================================================\n",
- BlockInfo.pLocaleBlockHdr->Flags,
- BlockInfo.pLocaleBlockHdr->Locales,
- BlockInfo.pLocaleBlockHdr->NumberOfFiles);
-
- // Dump the hashes and encoding keys
- for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
- {
- // Dump the entry
- fprintf(fp, "%08X %08X-%08X %s %s\n",
- (DWORD)(BlockInfo.pInt32Array[i]),
- (DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20),
- (DWORD)(BlockInfo.pRootEntries[i].FileNameHash),
- StringFromMD5((LPBYTE)BlockInfo.pRootEntries[i].EncodingKey, szOneLine),
- ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash));
-
- // Find the encoding entry in the encoding table
- if(nDumpLevel > 1)
- {
- EncodingKey.pbData = (LPBYTE)BlockInfo.pRootEntries[i].EncodingKey;
- EncodingKey.cbData = MD5_HASH_SIZE;
- pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL);
- DumpEncodingEntry(fp, hs, pEncodingEntry, nDumpLevel);
- }
- }
-
- // Put extra newline
- fprintf(fp, "\n");
- }
-
- ListFile_FreeMap(pListMap);
- fclose(fp);
- }
-}
-
void CascDumpFile(const char * szFileName, HANDLE hFile)
{
FILE * fp;
diff --git a/dep/CascLib/src/CascFiles.cpp b/dep/CascLib/src/CascFiles.cpp
new file mode 100644
index 00000000000..8709ea09e36
--- /dev/null
+++ b/dep/CascLib/src/CascFiles.cpp
@@ -0,0 +1,981 @@
+/*****************************************************************************/
+/* CascFiles.cpp Copyright (c) Ladislav Zezula 2014 */
+/*---------------------------------------------------------------------------*/
+/* Various text file parsers */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.04.14 1.00 Lad The first version of CascBuildCfg.cpp */
+/* 30.10.15 1.00 Lad Renamed to CascFiles.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+typedef int (*PARSEINFOFILE)(TCascStorage * hs, void * pvListFile);
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+struct TBuildFileInfo
+{
+ const TCHAR * szFileName;
+ CBLD_TYPE BuildFileType;
+};
+
+struct TGameIdString
+{
+ const char * szGameInfo;
+ size_t cchGameInfo;
+ DWORD dwGameInfo;
+};
+
+static const TBuildFileInfo BuildTypes[] =
+{
+ {_T(".build.info"), CascBuildInfo}, // Since HOTS build 30027, the game uses .build.info file for storage info
+ {_T(".build.db"), CascBuildDb}, // Older CASC storages
+ {NULL, CascBuildNone}
+};
+
+static const TCHAR * DataDirs[] =
+{
+ _T("SC2Data"), // Starcraft II (Legacy of the Void) build 38749
+ _T("Data\\Casc"), // Overwatch
+ _T("Data"), // World of Warcraft, Diablo
+ _T("HeroesData"), // Heroes of the Storm
+ _T("BNTData"), // Heroes of the Storm, until build 30414
+ NULL,
+};
+
+static const TGameIdString GameIds[] =
+{
+ {"Hero", 0x04, CASC_GAME_HOTS}, // Alpha build of Heroes of the Storm
+ {"WoW", 0x03, CASC_GAME_WOW6}, // Alpha build of World of Warcraft - Warlords of Draenor
+ {"Diablo3", 0x07, CASC_GAME_DIABLO3}, // Diablo III BETA 2.2.0
+ {"Prometheus", 0x0A, CASC_GAME_OVERWATCH}, // Overwatch BETA since build 24919
+ {"SC2", 0x03, CASC_GAME_STARCRAFT2}, // Starcraft II - Legacy of the Void
+ {NULL, 0, 0},
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static bool inline IsValueSeparator(const char * szVarValue)
+{
+ return ((0 <= szVarValue[0] && szVarValue[0] <= 0x20) || (szVarValue[0] == '|'));
+}
+
+static bool IsCharDigit(BYTE OneByte)
+{
+ return ('0' <= OneByte && OneByte <= '9');
+}
+
+static DWORD GetLocaleMask(const char * szTag)
+{
+ if(!strcmp(szTag, "enUS"))
+ return CASC_LOCALE_ENUS;
+
+ if(!strcmp(szTag, "koKR"))
+ return CASC_LOCALE_KOKR;
+
+ if(!strcmp(szTag, "frFR"))
+ return CASC_LOCALE_FRFR;
+
+ if(!strcmp(szTag, "deDE"))
+ return CASC_LOCALE_DEDE;
+
+ if(!strcmp(szTag, "zhCN"))
+ return CASC_LOCALE_ZHCN;
+
+ if(!strcmp(szTag, "esES"))
+ return CASC_LOCALE_ESES;
+
+ if(!strcmp(szTag, "zhTW"))
+ return CASC_LOCALE_ZHTW;
+
+ if(!strcmp(szTag, "enGB"))
+ return CASC_LOCALE_ENGB;
+
+ if(!strcmp(szTag, "enCN"))
+ return CASC_LOCALE_ENCN;
+
+ if(!strcmp(szTag, "enTW"))
+ return CASC_LOCALE_ENTW;
+
+ if(!strcmp(szTag, "esMX"))
+ return CASC_LOCALE_ESMX;
+
+ if(!strcmp(szTag, "ruRU"))
+ return CASC_LOCALE_RURU;
+
+ if(!strcmp(szTag, "ptBR"))
+ return CASC_LOCALE_PTBR;
+
+ if(!strcmp(szTag, "itIT"))
+ return CASC_LOCALE_ITIT;
+
+ if(!strcmp(szTag, "ptPT"))
+ return CASC_LOCALE_PTPT;
+
+ return 0;
+}
+
+static bool IsInfoVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName, const char * szVarType)
+{
+ size_t nLength;
+
+ // Check the variable name
+ nLength = strlen(szVarName);
+ if((size_t)(szLineEnd - szLineBegin) > nLength)
+ {
+ // Check the variable name
+ if(!_strnicmp(szLineBegin, szVarName, nLength))
+ {
+ // Skip variable name and the exclamation mark
+ szLineBegin += nLength;
+ if(szLineBegin < szLineEnd && szLineBegin[0] == '!')
+ {
+ // Skip the exclamation mark
+ szLineBegin++;
+
+ // Check the variable type
+ nLength = strlen(szVarType);
+ if((size_t)(szLineEnd - szLineBegin) > nLength)
+ {
+ // Check the variable name
+ if(!_strnicmp(szLineBegin, szVarType, nLength))
+ {
+ // Skip variable type and the doublecolon
+ szLineBegin += nLength;
+ return (szLineBegin < szLineEnd && szLineBegin[0] == ':');
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static const char * SkipInfoVariable(const char * szLineBegin, const char * szLineEnd)
+{
+ while(szLineBegin < szLineEnd)
+ {
+ if(szLineBegin[0] == '|')
+ return szLineBegin + 1;
+
+ szLineBegin++;
+ }
+
+ return NULL;
+}
+
+static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
+{
+ TCHAR * szIndexPath;
+
+ // Cpmbine the index path
+ szIndexPath = CombinePath(hs->szDataPath, szSubDir);
+ if(DirectoryExists(szIndexPath))
+ {
+ hs->szIndexPath = szIndexPath;
+ return hs->szIndexPath;
+ }
+
+ CASC_FREE(szIndexPath);
+ return NULL;
+}
+
+TCHAR * AppendBlobText(TCHAR * szBuffer, LPBYTE pbData, DWORD cbData, TCHAR chSeparator)
+{
+ // Put the separator, if any
+ if(chSeparator != 0)
+ *szBuffer++ = chSeparator;
+
+ // Copy the blob data as text
+ for(DWORD i = 0; i < cbData; i++)
+ {
+ *szBuffer++ = IntToHexChar[pbData[0] >> 0x04];
+ *szBuffer++ = IntToHexChar[pbData[0] & 0x0F];
+ pbData++;
+ }
+
+ // Terminate the string
+ *szBuffer = 0;
+
+ // Return new buffer position
+ return szBuffer;
+}
+
+static const char * CheckLineVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName)
+{
+ size_t nLineLength = (size_t)(szLineEnd - szLineBegin);
+ size_t nNameLength = strlen(szVarName);
+
+ // If the line longer than the variable name?
+ if(nLineLength > nNameLength)
+ {
+ if(!_strnicmp((const char *)szLineBegin, szVarName, nNameLength))
+ {
+ // Skip the variable name
+ szLineBegin += nNameLength;
+
+ // Skip the separator(s)
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
+ szLineBegin++;
+
+ // Check if there is "="
+ if(szLineBegin >= szLineEnd || szLineBegin[0] != '=')
+ return NULL;
+ szLineBegin++;
+
+ // Skip the separator(s)
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
+ szLineBegin++;
+
+ // Check if there is "="
+ if(szLineBegin >= szLineEnd)
+ return NULL;
+
+ // Return the begin of the variable
+ return szLineBegin;
+ }
+ }
+
+ return NULL;
+}
+
+static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const char * szLineEnd, bool bHexaValue)
+{
+ const char * szLinePtr = szLineBegin;
+
+ // Sanity checks
+ assert(pVarBlob->pbData == NULL);
+ assert(pVarBlob->cbData == 0);
+
+ // Check length of the variable
+ while(szLinePtr < szLineEnd && szLinePtr[0] != '|')
+ szLinePtr++;
+
+ // Allocate space for the blob
+ if(bHexaValue)
+ {
+ // Initialize the blob
+ pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) / 2);
+ pVarBlob->cbData = (DWORD)((szLinePtr - szLineBegin) / 2);
+ return ConvertStringToBinary(szLineBegin, (size_t)(szLinePtr - szLineBegin), pVarBlob->pbData);
+ }
+
+ // Initialize the blob
+ pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1);
+ pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin);
+
+ // Check for success
+ if(pVarBlob->pbData == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Copy the string
+ memcpy(pVarBlob->pbData, szLineBegin, pVarBlob->cbData);
+ pVarBlob->pbData[pVarBlob->cbData] = 0;
+ return ERROR_SUCCESS;
+}
+
+static void AppendConfigFilePath(TCHAR * szFileName, PQUERY_KEY pFileKey)
+{
+ size_t nLength = _tcslen(szFileName);
+
+ // If there is no slash, append if
+ if(nLength > 0 && szFileName[nLength - 1] != '\\' && szFileName[nLength - 1] != '/')
+ szFileName[nLength++] = _T('/');
+
+ // Get to the end of the file name
+ szFileName = szFileName + nLength;
+
+ // Append the "config" directory
+ _tcscpy(szFileName, _T("config"));
+ szFileName += 6;
+
+ // Append the first level directory
+ szFileName = AppendBlobText(szFileName, pFileKey->pbData, 1, _T('/'));
+ szFileName = AppendBlobText(szFileName, pFileKey->pbData + 1, 1, _T('/'));
+ szFileName = AppendBlobText(szFileName, pFileKey->pbData, pFileKey->cbData, _T('/'));
+}
+
+static DWORD GetBlobCount(const char * szLineBegin, const char * szLineEnd)
+{
+ DWORD dwBlobCount = 0;
+
+ // Until we find an end of the line
+ while(szLineBegin < szLineEnd)
+ {
+ // Skip the blob
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin) == false)
+ szLineBegin++;
+
+ // Increment the number of blobs
+ dwBlobCount++;
+
+ // Skip the separator
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
+ szLineBegin++;
+ }
+
+ return dwBlobCount;
+}
+
+static int LoadBlobArray(
+ PQUERY_KEY pBlob,
+ const char * szLineBegin,
+ const char * szLineEnd,
+ DWORD dwMaxBlobs)
+{
+ LPBYTE pbBufferEnd = pBlob->pbData + pBlob->cbData;
+ LPBYTE pbBuffer = pBlob->pbData;
+ int nError = ERROR_SUCCESS;
+
+ // Sanity check
+ assert(pBlob->pbData != NULL);
+ assert(pBlob->cbData != 0);
+
+ // Until we find an end of the line
+ while(szLineBegin < szLineEnd && dwMaxBlobs > 0)
+ {
+ const char * szBlobEnd = szLineBegin;
+
+ // Find the end of the text blob
+ while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd) == false)
+ szBlobEnd++;
+
+ // Verify the length of the found blob
+ if((szBlobEnd - szLineBegin) != MD5_STRING_SIZE)
+ return ERROR_BAD_FORMAT;
+
+ // Verify if there is enough space in the buffer
+ if((pbBufferEnd - pbBuffer) < MD5_HASH_SIZE)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Perform the conversion
+ nError = ConvertStringToBinary(szLineBegin, MD5_STRING_SIZE, pbBuffer);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Move pointers
+ pbBuffer += MD5_HASH_SIZE;
+ dwMaxBlobs--;
+
+ // Skip the separator
+ while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd))
+ szBlobEnd++;
+ szLineBegin = szBlobEnd;
+ }
+
+ return nError;
+}
+
+static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd, DWORD dwBlobCount)
+{
+ size_t nLength = (szLineEnd - szLineBegin);
+
+ // We expect each blob to have length of the encoding key and one space between
+ if(nLength > (dwBlobCount * MD5_STRING_SIZE) + ((dwBlobCount - 1) * sizeof(char)))
+ return ERROR_INVALID_PARAMETER;
+
+ // Allocate the blob buffer
+ pBlob->pbData = CASC_ALLOC(BYTE, dwBlobCount * MD5_HASH_SIZE);
+ if(pBlob->pbData == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Set the buffer size and load the blob array
+ pBlob->cbData = dwBlobCount * MD5_HASH_SIZE;
+ return LoadBlobArray(pBlob, szLineBegin, szLineEnd, dwBlobCount);
+}
+
+static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd)
+{
+ return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, GetBlobCount(szLineBegin, szLineEnd));
+}
+
+static int LoadSingleBlob(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd)
+{
+ return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, 1);
+}
+
+static int GetGameType(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd)
+{
+ // Go through all games that we support
+ for(size_t i = 0; GameIds[i].szGameInfo != NULL; i++)
+ {
+ // Check the length of the variable
+ if((size_t)(szLineEnd - szVarBegin) == GameIds[i].cchGameInfo)
+ {
+ // Check the string
+ if(!_strnicmp(szVarBegin, GameIds[i].szGameInfo, GameIds[i].cchGameInfo))
+ {
+ hs->dwGameInfo = GameIds[i].dwGameInfo;
+ return ERROR_SUCCESS;
+ }
+ }
+ }
+
+ // Unknown/unsupported game
+ assert(false);
+ return ERROR_BAD_FORMAT;
+}
+
+// "B29049"
+// "WOW-18125patch6.0.1"
+// "30013_Win32_2_2_0_Ptr_ptr"
+// "prometheus-0_8_0_0-24919"
+static int GetBuildNumber(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd)
+{
+ DWORD dwBuildNumber = 0;
+
+ // Skip all non-digit characters
+ while(szVarBegin < szLineEnd)
+ {
+ // There must be at least three digits (build 99 anyone?)
+ if(IsCharDigit(szVarBegin[0]) && IsCharDigit(szVarBegin[1]) && IsCharDigit(szVarBegin[2]))
+ {
+ // Convert the build number string to value
+ while(szVarBegin < szLineEnd && IsCharDigit(szVarBegin[0]))
+ dwBuildNumber = (dwBuildNumber * 10) + (*szVarBegin++ - '0');
+ break;
+ }
+
+ // Move to the next
+ szVarBegin++;
+ }
+
+ assert(dwBuildNumber != 0);
+ hs->dwBuildNumber = dwBuildNumber;
+ return (dwBuildNumber != 0) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
+}
+
+static int GetDefaultLocaleMask(TCascStorage * hs, PQUERY_KEY pTagsString)
+{
+ char * szTagEnd = (char *)pTagsString->pbData + pTagsString->cbData;
+ char * szTagPtr = (char *)pTagsString->pbData;
+ char * szNext;
+ DWORD dwLocaleMask = 0;
+
+ while(szTagPtr < szTagEnd)
+ {
+ // Get the next part
+ szNext = strchr(szTagPtr, ' ');
+ if(szNext != NULL)
+ *szNext++ = 0;
+
+ // Check whether the current tag is a language identifier
+ dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr);
+
+ // Get the next part
+ if(szNext == NULL)
+ break;
+
+ // Skip spaces
+ while(szNext < szTagEnd && szNext[0] == ' ')
+ szNext++;
+ szTagPtr = szNext;
+ }
+
+ hs->dwDefaultLocale = dwLocaleMask;
+ return ERROR_SUCCESS;
+}
+
+static void * FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey)
+{
+ TCHAR * szFileName;
+ void * pvListFile = NULL;
+
+ // Construct the local file name
+ szFileName = CascNewStr(hs->szDataPath, 8 + 3 + 3 + 32);
+ if(szFileName != NULL)
+ {
+ // Add the part where the config file path is
+ AppendConfigFilePath(szFileName, pFileKey);
+
+ // Load and verify the external listfile
+ pvListFile = ListFile_OpenExternal(szFileName);
+ if(pvListFile != NULL)
+ {
+ if(!ListFile_VerifyMD5(pvListFile, pFileKey->pbData))
+ {
+ ListFile_Free(pvListFile);
+ pvListFile = NULL;
+ }
+ }
+
+ // Free the file name
+ CASC_FREE(szFileName);
+ }
+
+ return pvListFile;
+}
+
+static int ParseFile_BuildInfo(TCascStorage * hs, void * pvListFile)
+{
+ QUERY_KEY Active = {NULL, 0};
+ QUERY_KEY TagString = {NULL, 0};
+ QUERY_KEY CdnHost = {NULL, 0};
+ QUERY_KEY CdnPath = {NULL, 0};
+ char szOneLine1[0x200];
+ char szOneLine2[0x200];
+ size_t nLength1;
+ size_t nLength2;
+ int nError = ERROR_BAD_FORMAT;
+
+ // Extract the first line, cotaining the headers
+ nLength1 = ListFile_GetNextLine(pvListFile, szOneLine1, _maxchars(szOneLine1));
+ if(nLength1 == 0)
+ return ERROR_BAD_FORMAT;
+
+ // Now parse the second and the next lines. We are looking for line
+ // with "Active" set to 1
+ for(;;)
+ {
+ const char * szLinePtr1 = szOneLine1;
+ const char * szLineEnd1 = szOneLine1 + nLength1;
+ const char * szLinePtr2 = szOneLine2;
+ const char * szLineEnd2;
+
+ // Read the next line
+ nLength2 = ListFile_GetNextLine(pvListFile, szOneLine2, _maxchars(szOneLine2));
+ if(nLength2 == 0)
+ break;
+ szLineEnd2 = szLinePtr2 + nLength2;
+
+ // Parse all variables
+ while(szLinePtr1 < szLineEnd1)
+ {
+ // Check for variables we need
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "Active", "DEC"))
+ LoadInfoVariable(&Active, szLinePtr2, szLineEnd2, false);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "Build Key", "HEX"))
+ LoadInfoVariable(&hs->CdnBuildKey, szLinePtr2, szLineEnd2, true);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Key", "HEX"))
+ LoadInfoVariable(&hs->CdnConfigKey, szLinePtr2, szLineEnd2, true);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Hosts", "STRING"))
+ LoadInfoVariable(&CdnHost, szLinePtr2, szLineEnd2, false);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Path", "STRING"))
+ LoadInfoVariable(&CdnPath, szLinePtr2, szLineEnd2, false);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "Tags", "STRING"))
+ LoadInfoVariable(&TagString, szLinePtr2, szLineEnd2, false);
+
+ // Move both line pointers
+ szLinePtr1 = SkipInfoVariable(szLinePtr1, szLineEnd1);
+ if(szLinePtr1 == NULL)
+ break;
+
+ szLinePtr2 = SkipInfoVariable(szLinePtr2, szLineEnd2);
+ if(szLinePtr2 == NULL)
+ break;
+ }
+
+ // Stop parsing if found active config
+ if(Active.pbData != NULL && *Active.pbData == '1')
+ break;
+
+ // Free the blobs
+ FreeCascBlob(&Active);
+ FreeCascBlob(&hs->CdnBuildKey);
+ FreeCascBlob(&hs->CdnConfigKey);
+ FreeCascBlob(&CdnHost);
+ FreeCascBlob(&CdnPath);
+ FreeCascBlob(&TagString);
+ }
+
+ // All four must be present
+ if(hs->CdnBuildKey.pbData != NULL &&
+ hs->CdnConfigKey.pbData != NULL &&
+ CdnHost.pbData != NULL &&
+ CdnPath.pbData != NULL)
+ {
+ // Merge the CDN host and CDN path
+ hs->szUrlPath = CASC_ALLOC(TCHAR, CdnHost.cbData + CdnPath.cbData + 1);
+ if(hs->szUrlPath != NULL)
+ {
+ CopyString(hs->szUrlPath, (char *)CdnHost.pbData, CdnHost.cbData);
+ CopyString(hs->szUrlPath + CdnHost.cbData, (char *)CdnPath.pbData, CdnPath.cbData);
+ nError = ERROR_SUCCESS;
+ }
+ }
+
+ // If we found tags, we can extract language build from it
+ if(TagString.pbData != NULL)
+ GetDefaultLocaleMask(hs, &TagString);
+
+ FreeCascBlob(&CdnHost);
+ FreeCascBlob(&CdnPath);
+ FreeCascBlob(&TagString);
+ FreeCascBlob(&Active);
+ return nError;
+}
+
+static int ParseFile_BuildDb(TCascStorage * hs, void * pvListFile)
+{
+ const char * szLinePtr;
+ const char * szLineEnd;
+ char szOneLine[0x200];
+ size_t nLength;
+ int nError;
+
+ // Load the single line from the text file
+ nLength = ListFile_GetNextLine(pvListFile, szOneLine, _maxchars(szOneLine));
+ if(nLength == 0)
+ return ERROR_BAD_FORMAT;
+
+ // Set the line range
+ szLinePtr = szOneLine;
+ szLineEnd = szOneLine + nLength;
+
+ // Extract the CDN build key
+ nError = LoadInfoVariable(&hs->CdnBuildKey, szLinePtr, szLineEnd, true);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Skip the variable
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Load the CDN config hash
+ nError = LoadInfoVariable(&hs->CdnConfigKey, szLinePtr, szLineEnd, true);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Skip the variable
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Skip the Locale/OS/code variable
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Load the URL
+ hs->szUrlPath = CascNewStrFromAnsi(szLinePtr, szLineEnd);
+ if(hs->szUrlPath == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Verify all variables
+ if(hs->CdnBuildKey.pbData == NULL || hs->CdnConfigKey.pbData == NULL || hs->szUrlPath == NULL)
+ nError = ERROR_BAD_FORMAT;
+ return nError;
+}
+
+static int LoadCdnConfigFile(TCascStorage * hs, void * pvListFile)
+{
+ const char * szLineBegin;
+ const char * szVarBegin;
+ const char * szLineEnd;
+ int nError = ERROR_SUCCESS;
+
+ // Keep parsing the listfile while there is something in there
+ for(;;)
+ {
+ // Get the next line
+ if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd))
+ break;
+
+ // Archive group
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archive-group");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadSingleBlob(&hs->ArchivesGroup, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Archives
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archives");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadMultipleBlobs(&hs->ArchivesKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Patch archive group
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archive-group");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->PatchArchivesKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Patch archives
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archives");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadMultipleBlobs(&hs->PatchArchivesKey, szVarBegin, szLineEnd);
+ continue;
+ }
+ }
+
+ // Check if all required fields are present
+ if(hs->ArchivesKey.pbData == NULL || hs->ArchivesKey.cbData == 0)
+ return ERROR_BAD_FORMAT;
+
+ return nError;
+}
+
+static int LoadCdnBuildFile(TCascStorage * hs, void * pvListFile)
+{
+ const char * szLineBegin;
+ const char * szVarBegin;
+ const char * szLineEnd = NULL;
+ int nError = ERROR_SUCCESS;
+
+ for(;;)
+ {
+ // Get the next line
+ if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd))
+ break;
+
+ // Game name
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-product");
+ if(szVarBegin != NULL)
+ {
+ GetGameType(hs, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Game build number
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-name");
+ if(szVarBegin != NULL)
+ {
+ GetBuildNumber(hs, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Root
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "root");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->RootKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Patch
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->PatchKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Download
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "download");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->DownloadKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Install
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "install");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->InstallKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Encoding keys
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "encoding");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadMultipleBlobs(&hs->EncodingKey, szVarBegin, szLineEnd, 2);
+ continue;
+ }
+ }
+
+ // Check the encoding keys
+ if(hs->EncodingKey.pbData == NULL || hs->EncodingKey.cbData != MD5_HASH_SIZE * 2)
+ return ERROR_BAD_FORMAT;
+ return nError;
+}
+
+static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory)
+{
+ TCHAR * szDataPath;
+ int nError = ERROR_FILE_NOT_FOUND;
+
+ // Try all known subdirectories
+ for(size_t i = 0; DataDirs[i] != NULL; i++)
+ {
+ // Create the eventual data path
+ szDataPath = CombinePath(szDirectory, DataDirs[i]);
+ if(szDataPath != NULL)
+ {
+ // Does that directory exist?
+ if(DirectoryExists(szDataPath))
+ {
+ hs->szDataPath = szDataPath;
+ return ERROR_SUCCESS;
+ }
+
+ // Free the data path
+ CASC_FREE(szDataPath);
+ }
+ }
+
+ return nError;
+}
+
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int LoadBuildInfo(TCascStorage * hs)
+{
+ PARSEINFOFILE PfnParseProc = NULL;
+ void * pvListFile;
+ int nError = ERROR_SUCCESS;
+
+ switch(hs->BuildFileType)
+ {
+ case CascBuildInfo:
+ PfnParseProc = ParseFile_BuildInfo;
+ break;
+
+ case CascBuildDb:
+ PfnParseProc = ParseFile_BuildDb;
+ break;
+
+ default:
+ nError = ERROR_NOT_SUPPORTED;
+ break;
+ }
+
+ // Parse the appropriate build file
+ if(nError == ERROR_SUCCESS)
+ {
+ pvListFile = ListFile_OpenExternal(hs->szBuildFile);
+ if(pvListFile != NULL)
+ {
+ // Parse the info file
+ nError = PfnParseProc(hs, pvListFile);
+ ListFile_Free(pvListFile);
+ }
+ else
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // If the .build.info OR .build.db file has been loaded,
+ // proceed with loading the CDN config file and CDN build file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Load the configuration file
+ pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey);
+ if(pvListFile != NULL)
+ {
+ nError = LoadCdnConfigFile(hs, pvListFile);
+ ListFile_Free(pvListFile);
+ }
+ else
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // Load the build file
+ if(nError == ERROR_SUCCESS)
+ {
+ pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey);
+ if(pvListFile != NULL)
+ {
+ nError = LoadCdnBuildFile(hs, pvListFile);
+ ListFile_Free(pvListFile);
+ }
+ else
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // Fill the index directory
+ if(nError == ERROR_SUCCESS)
+ {
+ // First, check for more common "data" subdirectory
+ if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
+ return ERROR_SUCCESS;
+
+ // Second, try the "darch" subdirectory (older builds of HOTS - Alpha)
+ if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
+ return ERROR_SUCCESS;
+
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ return nError;
+}
+
+// Checks whether there is a ".agent.db". If yes, the function
+// sets "szRootPath" and "szDataPath" in the storage structure
+// and returns ERROR_SUCCESS
+int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory)
+{
+ TFileStream * pStream;
+ TCHAR * szBuildFile;
+ int nError = ERROR_FILE_NOT_FOUND;
+
+ // Try to find any of the root files used in the history
+ for(size_t i = 0; BuildTypes[i].szFileName != NULL; i++)
+ {
+ // Create the full name of the .agent.db file
+ szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName);
+ if(szBuildFile != NULL)
+ {
+ // Attempt to open the file
+ pStream = FileStream_OpenFile(szBuildFile, 0);
+ if(pStream != NULL)
+ {
+ // Free the stream
+ FileStream_Close(pStream);
+
+ // Check for the data directory
+ nError = CheckDataDirectory(hs, szDirectory);
+ if(nError == ERROR_SUCCESS)
+ {
+ hs->szBuildFile = szBuildFile;
+ hs->BuildFileType = BuildTypes[i].BuildFileType;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ CASC_FREE(szBuildFile);
+ }
+ }
+
+ return nError;
+}
+
+// Parses single line from Overwatch.
+// The line structure is: "#MD5|CHUNK_ID|FILENAME|INSTALLPATH"
+// The line has all preceding spaces removed
+int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, PQUERY_KEY PtrEncodingKey, char * szFileName, size_t nMaxChars)
+{
+ size_t nLength;
+ int nError;
+
+ // Check the MD5 (aka encoding key)
+ if(szLinePtr[MD5_STRING_SIZE] != '|')
+ return ERROR_BAD_FORMAT;
+
+ // Convert the encoding key to binary
+ PtrEncodingKey->cbData = MD5_HASH_SIZE;
+ nError = ConvertStringToBinary(szLinePtr, MD5_STRING_SIZE, PtrEncodingKey->pbData);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Skip the MD5
+ szLinePtr += MD5_STRING_SIZE+1;
+
+ // Skip the chunk ID
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Get the archived file name
+ szLineEnd = SkipInfoVariable(szLinePtr, szLineEnd);
+ nLength = (size_t)(szLineEnd - szLinePtr - 1);
+
+ // Get the file name
+ if(nLength > nMaxChars)
+ return ERROR_INSUFFICIENT_BUFFER;
+
+ memcpy(szFileName, szLinePtr, nLength);
+ szFileName[nLength] = 0;
+ return ERROR_SUCCESS;
+}
diff --git a/dep/CascLib/src/CascFindFile.cpp b/dep/CascLib/src/CascFindFile.cpp
index 0bfe16cae1d..bea2e308747 100644
--- a/dep/CascLib/src/CascFindFile.cpp
+++ b/dep/CascLib/src/CascFindFile.cpp
@@ -11,7 +11,6 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
-#include "CascMndxRoot.h"
//-----------------------------------------------------------------------------
// Local functions
@@ -30,16 +29,22 @@ static void FreeSearchHandle(TCascSearch * pSearch)
// Close (dereference) the archive handle
if(pSearch->hs != NULL)
+ {
+ // Give root handler chance to free their stuff
+ RootHandler_EndSearch(pSearch->hs->pRootHandler, pSearch);
+
+ // Dereference the storage handle
CascCloseStorage((HANDLE)pSearch->hs);
- pSearch->hs = NULL;
+ pSearch->hs = NULL;
+ }
// Free the file cache and frame array
if(pSearch->szMask != NULL)
CASC_FREE(pSearch->szMask);
if(pSearch->szListFile != NULL)
CASC_FREE(pSearch->szListFile);
- if(pSearch->pStruct1C != NULL)
- delete pSearch->pStruct1C;
+// if(pSearch->pStruct1C != NULL)
+// delete pSearch->pStruct1C;
if(pSearch->pCache != NULL)
ListFile_Free(pSearch->pCache);
@@ -47,58 +52,6 @@ static void FreeSearchHandle(TCascSearch * pSearch)
pSearch->szClassName = NULL;
CASC_FREE(pSearch);
}
-/*
-DWORD dwRootEntries = 0;
-DWORD dwEncoEntries = 0;
-DWORD dwIndexEntries = 0;
-*/
-static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry, PCASC_FIND_DATA pFindData, size_t nRootIndex)
-{
- PCASC_ENCODING_ENTRY pEncodingEntry;
- PCASC_INDEX_ENTRY pIndexEntry;
- TCascStorage * hs = pSearch->hs;
- QUERY_KEY QueryKey;
- DWORD dwByteIndex = (DWORD)(nRootIndex / 0x08);
- DWORD dwBitIndex = (DWORD)(nRootIndex & 0x07);
-
- // First of all, check if that entry has been reported before
- // If the bit is set, then the file has already been reported
- // by a previous search iteration
- if(pSearch->BitArray[dwByteIndex] & (1 << dwBitIndex))
- return false;
- pSearch->BitArray[dwByteIndex] |= (1 << dwBitIndex);
-
- // Increment the number of root entries
-// dwRootEntries++;
-
- // Now try to find that encoding key in the array of encoding keys
- QueryKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
- QueryKey.cbData = MD5_HASH_SIZE;
- pEncodingEntry = FindEncodingEntry(hs, &QueryKey, NULL);
- if(pEncodingEntry == NULL)
- return false;
-
-// dwEncoEntries++;
-
- // Now try to find the index entry. Note that we take the first key
- QueryKey.pbData = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
- QueryKey.cbData = MD5_HASH_SIZE;
- pIndexEntry = FindIndexEntry(hs, &QueryKey);
- if(pIndexEntry == NULL)
- return false;
-
-// dwIndexEntries++;
-
- // Fill the name hash and the MD5
- memcpy(pFindData->EncodingKey, pRootEntry->EncodingKey, MD5_HASH_SIZE);
- pFindData->FileNameHash = pRootEntry->FileNameHash;
- pFindData->dwPackageIndex = 0;
- pFindData->dwLocaleFlags = pRootEntry->Locales;
-
- // Fill-in the file size
- pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
- return true;
-}
static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szListFile, const char * szMask)
{
@@ -106,7 +59,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
size_t cbToAllocate;
// When using the MNDX info, do not allocate the extra bit array
- cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->RootTable.TableSize / 8) : 0);
+ cbToAllocate = sizeof(TCascSearch) + ((hs->pEncodingMap->TableSize + 7) / 8);
pSearch = (TCascSearch *)CASC_ALLOC(BYTE, cbToAllocate);
if(pSearch != NULL)
{
@@ -125,7 +78,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
// Save the other variables
if(szListFile != NULL)
{
- pSearch->szListFile = NewStr(szListFile, 0);
+ pSearch->szListFile = CascNewStr(szListFile, 0);
if(pSearch->szListFile == NULL)
{
FreeSearchHandle(pSearch);
@@ -134,7 +87,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
}
// Allocate the search mask
- pSearch->szMask = NewStr(szMask, 0);
+ pSearch->szMask = CascNewStr(szMask, 0);
if(pSearch->szMask == NULL)
{
FreeSearchHandle(pSearch);
@@ -145,64 +98,106 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
return pSearch;
}
-static bool DoStorageSearch_ListFile(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
+// Perform searching using root-specific provider.
+// The provider may need the listfile
+static bool DoStorageSearch_RootFile(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
- PCASC_ROOT_ENTRY pRootEntry;
- TCascStorage * hs = pSearch->hs;
- char szFileName2[MAX_PATH + 1];
- DWORD TableIndex = 0;
-
- // Get next file from the listfile
- while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
+ PCASC_ENCODING_ENTRY pEncodingEntry;
+ PCASC_INDEX_ENTRY pIndexEntry;
+ QUERY_KEY EncodingKey;
+ QUERY_KEY IndexKey;
+ LPBYTE pbEncodingKey;
+ DWORD EncodingIndex = 0;
+ DWORD LocaleFlags = 0;
+ DWORD FileSize = CASC_INVALID_SIZE;
+ DWORD ByteIndex;
+ DWORD BitMask;
+
+ for(;;)
{
-#ifdef _DEBUG
-// if(!_stricmp(pSearch->szFileName, "Character\\BloodElf\\Female\\DeathKnightEyeGlow.blp"))
-// DebugBreak();
-#endif
-
- // Normalize the file name found in the list file
- NormalizeFileName_UpperBkSlash(szFileName2, pSearch->szFileName, MAX_PATH);
-
- // Find the root entry
- pRootEntry = FindRootEntry(hs, szFileName2, &TableIndex);
- if(pRootEntry != NULL)
+ // Attempt to find (the next) file from the root entry
+ pbEncodingKey = RootHandler_Search(pSearch->hs->pRootHandler, pSearch, &FileSize, &LocaleFlags);
+ if(pbEncodingKey == NULL)
+ return false;
+
+ // Verify whether the encoding key exists in the encoding table
+ EncodingKey.pbData = pbEncodingKey;
+ EncodingKey.cbData = MD5_HASH_SIZE;
+ pEncodingEntry = FindEncodingEntry(pSearch->hs, &EncodingKey, &EncodingIndex);
+ if(pEncodingEntry != NULL)
{
- // Verify whether the file exists in the storage
- if(VerifyRootEntry(pSearch, pRootEntry, pFindData, TableIndex))
- {
- strcpy(pFindData->szFileName, pSearch->szFileName);
- pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
- return true;
- }
+ // Mark the item as already found
+ // Note: Duplicate items are allowed while we are searching using file names
+ // Do not exclude items from search if they were found before
+ ByteIndex = (DWORD)(EncodingIndex / 8);
+ BitMask = 1 << (EncodingIndex & 0x07);
+ pSearch->BitArray[ByteIndex] |= BitMask;
+
+ // Locate the index entry
+ IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry);
+ IndexKey.cbData = MD5_HASH_SIZE;
+ pIndexEntry = FindIndexEntry(pSearch->hs, &IndexKey);
+ if(pIndexEntry == NULL)
+ continue;
+
+ // If we retrieved the file size directly from the root provider, use it
+ // Otherwise, we need to retrieve it from the encoding entry
+ if(FileSize == CASC_INVALID_SIZE)
+ FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
+
+ // Fill-in the found file
+ strcpy(pFindData->szFileName, pSearch->szFileName);
+ memcpy(pFindData->EncodingKey, pEncodingEntry->EncodingKey, MD5_HASH_SIZE);
+ pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
+ pFindData->dwLocaleFlags = LocaleFlags;
+ pFindData->dwFileSize = FileSize;
+ return true;
}
}
-
- // Listfile search ended
- return false;
}
-static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
+static bool DoStorageSearch_EncodingKey(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
- PCASC_ROOT_ENTRY pRootEntry;
+ PCASC_ENCODING_ENTRY pEncodingEntry;
+ PCASC_INDEX_ENTRY pIndexEntry;
TCascStorage * hs = pSearch->hs;
+ QUERY_KEY IndexKey;
+ DWORD ByteIndex;
+ DWORD BitMask;
- // Check if there is more files with the same name hash
- while(pSearch->RootIndex < hs->RootTable.TableSize)
+ // Check for encoding keys that haven't been found yet
+ while(pSearch->IndexLevel1 < hs->pEncodingMap->TableSize)
{
- // Get the pointer to the root entry
- pRootEntry = hs->RootTable.TablePtr + pSearch->RootIndex;
-
- // Verify if that root entry exists in the CASC storage
- // and was not found before
- if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex))
+ // Check if that entry has been reported before
+ ByteIndex = (DWORD)(pSearch->IndexLevel1 / 8);
+ BitMask = 1 << (pSearch->IndexLevel1 & 0x07);
+ if((pSearch->BitArray[ByteIndex] & BitMask) == 0)
{
- pFindData->szFileName[0] = 0;
- pFindData->szPlainName = NULL;
- return true;
+ // Locate the index entry
+ pEncodingEntry = (PCASC_ENCODING_ENTRY)hs->pEncodingMap->HashTable[pSearch->IndexLevel1];
+ if(pEncodingEntry != NULL)
+ {
+ IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry);
+ IndexKey.cbData = MD5_HASH_SIZE;
+ pIndexEntry = FindIndexEntry(pSearch->hs, &IndexKey);
+ if(pIndexEntry != NULL)
+ {
+ // Fill-in the found file
+ memcpy(pFindData->EncodingKey, pEncodingEntry->EncodingKey, MD5_HASH_SIZE);
+ pFindData->szFileName[0] = 0;
+ pFindData->szPlainName = NULL;
+ pFindData->dwLocaleFlags = CASC_LOCALE_NONE;
+ pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
+
+ // Mark the entry as already-found
+ pSearch->BitArray[ByteIndex] |= BitMask;
+ return true;
+ }
+ }
}
- // Move to the next entry
- pSearch->RootIndex++;
+ // Go to the next encoding entry
+ pSearch->IndexLevel1++;
}
// Nameless search ended
@@ -211,10 +206,6 @@ static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindDat
static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
- // Are we searching using the MNDX ?
- if(pSearch->hs->pMndxInfo != NULL)
- return DoStorageSearch_MNDX(pSearch, pFindData);
-
// State 0: No search done yet
if(pSearch->dwState == 0)
{
@@ -223,30 +214,25 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile);
// Move the search phase to the listfile searching
- pSearch->RootIndex = 0;
+ pSearch->IndexLevel1 = 0;
pSearch->dwState++;
-
- // If either file stream or listfile cache are invalid,
- // move to the next phase
- if(pSearch->pCache == NULL)
- pSearch->dwState++;
}
// State 1: Searching the list file
if(pSearch->dwState == 1)
{
- if(DoStorageSearch_ListFile(pSearch, pFindData))
+ if(DoStorageSearch_RootFile(pSearch, pFindData))
return true;
// Move to the nameless search state
- assert(pSearch->RootIndex == 0);
+ pSearch->IndexLevel1 = 0;
pSearch->dwState++;
}
// State 2: Searching the remaining entries
if(pSearch->dwState == 2)
{
- if(DoStorageSearch_Hash(pSearch, pFindData))
+ if(DoStorageSearch_EncodingKey(pSearch, pFindData))
return true;
// Move to the final search state
@@ -275,19 +261,12 @@ HANDLE WINAPI CascFindFirstFile(
if(szMask == NULL || pFindData == NULL)
nError = ERROR_INVALID_PARAMETER;
- // Allocate the structure for archive search
+ // Init the search structure and search handle
if(nError == ERROR_SUCCESS)
{
// Clear the entire search structure
memset(pFindData, 0, sizeof(CASC_FIND_DATA));
- // We must have listfile for non-MNDX storages
- if(hs->pMndxInfo == NULL && szListFile == NULL)
- {
- SetLastError(ERROR_INVALID_PARAMETER);
- return NULL;
- }
-
// Allocate the search handle
pSearch = AllocateSearchHandle(hs, szListFile, szMask);
if(pSearch == NULL)
diff --git a/dep/CascLib/src/CascLib.def b/dep/CascLib/src/CascLib.def
new file mode 100644
index 00000000000..cb5f9166e49
--- /dev/null
+++ b/dep/CascLib/src/CascLib.def
@@ -0,0 +1,29 @@
+;
+; Export file for Windows
+; Copyright (c) 2015 Ladislav Zezula
+; ladik@zezula.net
+;
+
+LIBRARY CascLib.dll
+
+EXPORTS
+
+ CascOpenStorage
+ CascGetStorageInfo
+ CascCloseStorage
+
+ CascOpenFileByIndexKey
+ CascOpenFileByEncodingKey
+ CascOpenFile
+ CascGetFileSize
+ CascSetFilePointer
+ CascReadFile
+ CascCloseFile
+
+ CascFindFirstFile
+ CascFindNextFile
+ CascFindClose
+
+ GetLastError=Kernel32.GetLastError
+ SetLastError=Kernel32.SetLastError
+ \ No newline at end of file
diff --git a/dep/CascLib/src/CascLib.h b/dep/CascLib/src/CascLib.h
index 330a4b2bb49..bad32eb2dba 100644
--- a/dep/CascLib/src/CascLib.h
+++ b/dep/CascLib/src/CascLib.h
@@ -39,7 +39,7 @@ extern "C" {
#define CASC_STOR_XXXXX 0x00000001 // Not used
// Values for CascOpenFile
-#define CASC_FILE_XXXXX 0x00000001 // Not used
+#define CASC_OPEN_BY_ENCODING_KEY 0x00000001 // The name is just the encoding key; skip ROOT file processing
// Flags for file stream
#define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file
@@ -103,7 +103,7 @@ extern "C" {
#ifndef MD5_HASH_SIZE
#define MD5_HASH_SIZE 0x10
-#define MD5_STRING_SIZE 0x21
+#define MD5_STRING_SIZE 0x20
#endif
#ifndef SHA1_DIGEST_SIZE
@@ -146,9 +146,7 @@ typedef struct _CASC_FIND_DATA
{
char szFileName[MAX_PATH]; // Full name of the found file
char * szPlainName; // Plain name of the found file
- ULONGLONG FileNameHash; // File name hash
BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key
- DWORD dwPackageIndex; // File package index (HOTS only)
DWORD dwLocaleFlags; // Locale flags (WoW only)
DWORD dwFileSize; // Size of the file
@@ -184,6 +182,16 @@ HANDLE WINAPI CascFindFirstFile(HANDLE hStorage, const char * szMask, PCASC_FIND
bool WINAPI CascFindNextFile(HANDLE hFind, PCASC_FIND_DATA pFindData);
bool WINAPI CascFindClose(HANDLE hFind);
+//-----------------------------------------------------------------------------
+// GetLastError/SetLastError support for non-Windows platform
+
+#ifndef PLATFORM_WINDOWS
+
+int GetLastError();
+void SetLastError(int nError);
+
+#endif // PLATFORM_WINDOWS
+
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/dep/CascLib/src/CascMndxRoot.h b/dep/CascLib/src/CascMndx.h
index bd93f230845..d1b6653d4fe 100644
--- a/dep/CascLib/src/CascMndxRoot.h
+++ b/dep/CascLib/src/CascMndx.h
@@ -13,6 +13,9 @@
class TFileNameDatabase;
+#define CASC_MAX_MAR_FILES 3 // Maximum of 3 MAR files are supported
+#define CASC_MNDX_SIGNATURE 0x58444E4D // 'MNDX'
+
#define CASC_MAX_ENTRIES(type) (0xFFFFFFFF / sizeof(type))
#define CASC_SEARCH_INITIALIZING 0
@@ -353,13 +356,4 @@ inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex)
return ((Table.NameFragArray[ItemIndex].FragOffs & 0xFFFFFF00) == 0xFFFFFF00);
}
-//-----------------------------------------------------------------------------
-// CASC functions related to MNDX
-
-int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
-PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName);
-int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppFoundInfo);
-bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData);
-void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo);
-
#endif // __CASC_MNDX_ROOT__
diff --git a/dep/CascLib/src/CascOpenFile.cpp b/dep/CascLib/src/CascOpenFile.cpp
index 2b8c3d3c4ad..c4c27a3f6a6 100644
--- a/dep/CascLib/src/CascOpenFile.cpp
+++ b/dep/CascLib/src/CascOpenFile.cpp
@@ -11,10 +11,6 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
-#include "CascMndxRoot.h"
-
-//-----------------------------------------------------------------------------
-// Local structures
//-----------------------------------------------------------------------------
// Local functions
@@ -31,79 +27,19 @@ PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey)
PCASC_INDEX_ENTRY pIndexEntry = NULL;
if(hs->pIndexEntryMap != NULL)
- pIndexEntry = (PCASC_INDEX_ENTRY)Map_FindObject(hs->pIndexEntryMap, pIndexKey->pbData);
+ pIndexEntry = (PCASC_INDEX_ENTRY)Map_FindObject(hs->pIndexEntryMap, pIndexKey->pbData, NULL);
return pIndexEntry;
}
-PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex)
+PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, PDWORD PtrIndex)
{
- PCASC_ENCODING_ENTRY pEncodingEntry;
- size_t StartEntry = 0;
- size_t MidlEntry;
- size_t EndEntry = hs->nEncodingEntries;
- int nResult;
-
- // Perform binary search
- while(StartEntry < EndEntry)
- {
- // Calculate the middle of the interval
- MidlEntry = StartEntry + ((EndEntry - StartEntry) / 2);
- pEncodingEntry = hs->ppEncodingEntries[MidlEntry];
-
- // Did we find it?
- nResult = memcmp(pEncodingKey->pbData, pEncodingEntry->EncodingKey, MD5_HASH_SIZE);
- if(nResult == 0)
- {
- if(PtrIndex != NULL)
- PtrIndex[0] = MidlEntry;
- return pEncodingEntry;
- }
-
- // Move the interval to the left or right
- (nResult < 0) ? EndEntry = MidlEntry : StartEntry = MidlEntry + 1;
- }
-
- // Not found, sorry
- return NULL;
-}
-
-// Also used in CascSearchFile
-PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex)
-{
- PCASC_ROOT_ENTRY pRootEntry;
- ULONGLONG FileNameHash;
- DWORD TableIndex;
- uint32_t dwHashHigh = 0;
- uint32_t dwHashLow = 0;
-
- // Calculate the HASH value of the normalized file name
- hashlittle2(szFileName, strlen(szFileName), &dwHashHigh, &dwHashLow);
- FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
-
- // Get the first table index
- TableIndex = (DWORD)(FileNameHash & (hs->RootTable.TableSize - 1));
- assert(hs->RootTable.ItemCount < hs->RootTable.TableSize);
-
- // Search the proper entry
- for(;;)
- {
- // Does the has match?
- pRootEntry = hs->RootTable.TablePtr + TableIndex;
- if(pRootEntry->FileNameHash == FileNameHash)
- {
- if(PtrTableIndex != NULL)
- PtrTableIndex[0] = TableIndex;
- return pRootEntry;
- }
+ PCASC_ENCODING_ENTRY pEncodingEntry = NULL;
- // If the entry is free, the file is not there
- if(pRootEntry->FileNameHash == 0 && pRootEntry->SumValue == 0)
- return NULL;
+ if(hs->pEncodingMap != NULL)
+ pEncodingEntry = (PCASC_ENCODING_ENTRY)Map_FindObject(hs->pEncodingMap, pEncodingKey->pbData, PtrIndex);
- // Move to the next entry
- TableIndex = (DWORD)((TableIndex + 1) & (hs->RootTable.TableSize - 1));
- }
+ return pEncodingEntry;
}
static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexEntry)
@@ -187,7 +123,7 @@ static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DW
// Prepare the file index and open the file by index
// Note: We don't know what to do if there is more than just one index key
// We always take the first file present. Is that correct?
- IndexKey.pbData = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
+ IndexKey.pbData = GET_INDEX_KEY(pEncodingEntry);
IndexKey.cbData = MD5_HASH_SIZE;
if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, ppCascFile))
{
@@ -259,13 +195,10 @@ bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey,
bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile)
{
- PCASC_ROOT_ENTRY_MNDX pRootEntryMndx = NULL;
- PCASC_ROOT_ENTRY pRootEntry;
- PCASC_PACKAGE pPackage;
TCascStorage * hs;
QUERY_KEY EncodingKey;
- char * szStrippedName;
- char szFileName2[MAX_PATH+1];
+ LPBYTE pbEncodingKey;
+ BYTE KeyBuffer[MD5_HASH_SIZE];
int nError = ERROR_SUCCESS;
CASCLIB_UNUSED(dwLocale);
@@ -285,55 +218,37 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
return false;
}
- // If the storage has a MNDX root directory, use it to search the entry
- if(hs->pMndxInfo != NULL)
+ // If the user is opening the file via encoding key, skip the ROOT file processing
+ if((dwFlags & CASC_OPEN_BY_ENCODING_KEY) == 0)
{
- // Convert the file name to lowercase + slashes
- NormalizeFileName_LowerSlash(szFileName2, szFileName, MAX_PATH);
-
- // Find the package number
- pPackage = FindMndxPackage(hs, szFileName2);
- if(pPackage != NULL)
+ // Let the root directory provider get us the encoding key
+ pbEncodingKey = RootHandler_GetKey(hs->pRootHandler, szFileName);
+ if(pbEncodingKey == NULL)
{
- // Cut the package name off the full path
- szStrippedName = szFileName2 + pPackage->nLength;
- while(szStrippedName[0] == '/')
- szStrippedName++;
-
- nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntryMndx);
- if(nError == ERROR_SUCCESS)
- {
- // Prepare the encoding key
- EncodingKey.pbData = pRootEntryMndx->EncodingKey;
- EncodingKey.cbData = MD5_HASH_SIZE;
- }
- }
- else
- {
- nError = ERROR_FILE_NOT_FOUND;
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return false;
}
+
+ // Setup the encoding key
+ EncodingKey.pbData = pbEncodingKey;
+ EncodingKey.cbData = MD5_HASH_SIZE;
}
else
{
- // Convert the file name to lowercase + slashes
- NormalizeFileName_UpperBkSlash(szFileName2, szFileName, MAX_PATH);
-
- // Check the root directory for that hash
- pRootEntry = FindRootEntry(hs, szFileName2, NULL);
- if(pRootEntry != NULL)
- {
- // Prepare the root key
- EncodingKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
- EncodingKey.cbData = MD5_HASH_SIZE;
- nError = ERROR_SUCCESS;
- }
- else
+ // Check the length of the file name
+ if(strlen(szFileName) < MD5_STRING_SIZE)
{
- nError = ERROR_FILE_NOT_FOUND;
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
}
+
+ // Convert the file name to binary blob
+ EncodingKey.pbData = KeyBuffer;
+ EncodingKey.cbData = MD5_HASH_SIZE;
+ nError = ConvertStringToBinary(szFileName, MD5_STRING_SIZE, KeyBuffer);
}
- // Use the root key to find the file in the encoding table entry
+ // Use the encoding key to find the file in the encoding table entry
if(nError == ERROR_SUCCESS)
{
if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, (TCascFile **)phFile))
@@ -344,10 +259,10 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
}
#ifdef CASCLIB_TEST
- if(phFile[0] != NULL && pRootEntryMndx != NULL)
- {
- ((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize;
- }
+// if(phFile[0] != NULL && pRootEntryMndx != NULL)
+// {
+// ((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize;
+// }
#endif
if(nError != ERROR_SUCCESS)
diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp
index 6a8d83ee903..c3d623df9f0 100644
--- a/dep/CascLib/src/CascOpenStorage.cpp
+++ b/dep/CascLib/src/CascOpenStorage.cpp
@@ -13,19 +13,12 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
-#include "CascMndxRoot.h"
-
-//-----------------------------------------------------------------------------
-// Dumping options
-
-#ifdef _DEBUG
-#define CASC_DUMP_ROOT_FILE 2 // The root file will be dumped (level 2)
-#endif
//-----------------------------------------------------------------------------
// Local structures
-#define CASC_INITIAL_ROOT_TABLE_SIZE 0x00100000
+// Size of one segment in the ENCODING table
+// The segment is filled by entries of type
#define CASC_ENCODING_SEGMENT_SIZE 0x1000
typedef struct _BLOCK_SIZE_AND_HASH
@@ -67,25 +60,10 @@ typedef struct _FILE_INDEX_HEADER_V2
} FILE_INDEX_HEADER_V2, *PFILE_INDEX_HEADER_V2;
-typedef struct _FILE_ENCODING_HEADER
-{
- BYTE Magic[2]; // "EN"
- BYTE field_2;
- BYTE field_3;
- BYTE field_4;
- BYTE field_5[2];
- BYTE field_7[2];
- BYTE NumSegments[4]; // Number of entries (big endian)
- BYTE field_D[4];
- BYTE field_11;
- BYTE SegmentsPos[4]; // Offset of encoding segments
-
-} FILE_ENCODING_HEADER, *PFILE_ENCODING_HEADER;
-
typedef struct _FILE_ENCODING_SEGMENT
{
- BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment
- BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
+ BYTE FirstEncodingKey[MD5_HASH_SIZE]; // The first encoding key in the segment
+ BYTE SegmentHash[MD5_HASH_SIZE]; // MD5 hash of the entire segment
} FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT;
@@ -143,28 +121,6 @@ static bool IsIndexFileName_V2(const TCHAR * szFileName)
_tcsicmp(szFileName + 0x0A, _T(".idx")) == 0);
}
-static void QUERY_KEY_Free(PQUERY_KEY pBlob)
-{
- if(pBlob != NULL)
- {
- if(pBlob->pbData != NULL)
- CASC_FREE(pBlob->pbData);
-
- pBlob->pbData = NULL;
- pBlob->cbData = 0;
- }
-}
-
-static void QUERY_KEY_FreeArray(PQUERY_KEY pBlobArray)
-{
- // Free the buffer in the first blob
- // (will also free all buffers in the array)
- QUERY_KEY_Free(pBlobArray);
-
- // Free the array itself
- CASC_FREE(pBlobArray);
-}
-
static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData)
{
PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData;
@@ -206,52 +162,99 @@ static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData)
return (HashHigh == pSizeAndHash->dwBlockHash);
}
-LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
+static bool CutLastPathPart(TCHAR * szWorkPath)
{
- // Validate the file locale block
- pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
- pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
- if(pbFilePointer > pbFileEnd)
- return NULL;
-
- // Validate the array of 32-bit integers
- pBlockInfo->pInt32Array = (PDWORD)pbFilePointer;
- pbFilePointer = (LPBYTE)(pBlockInfo->pInt32Array + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
- if(pbFilePointer > pbFileEnd)
- return NULL;
-
- // Validate the array of root entries
- pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer;
- pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
- if(pbFilePointer > pbFileEnd)
- return NULL;
-
- // Return the position of the next block
- return pbFilePointer;
+ size_t nLength = _tcslen(szWorkPath);
+
+ for(nLength = _tcslen(szWorkPath); nLength > 0; nLength--)
+ {
+ if(szWorkPath[nLength] == '\\' || szWorkPath[nLength] == '/')
+ {
+ szWorkPath[nLength] = 0;
+ return true;
+ }
+ }
+
+ return false;
}
-static int InitializeCascDirectories(TCascStorage * hs, const TCHAR * szDataPath)
+static int InsertExtraFile(
+ TCascStorage * hs,
+ const char * szFileName,
+ PQUERY_KEY pQueryKey)
{
- TCHAR * szLastPathPart;
+ // If the given key is not encoding key (aka, it's an index key),
+ // we need to create a fake encoding entry
+ if(pQueryKey->cbData == MD5_HASH_SIZE * 2)
+ {
+ PCASC_ENCODING_ENTRY pNewEntry;
+ PCASC_INDEX_ENTRY pIndexEntry;
+ QUERY_KEY IndexKey;
+
+ // Find the entry in the index table in order to get the file size
+ IndexKey.pbData = pQueryKey->pbData + MD5_HASH_SIZE;
+ IndexKey.cbData = MD5_HASH_SIZE;
+ pIndexEntry = FindIndexEntry(hs, &IndexKey);
+ if(pIndexEntry == NULL)
+ return ERROR_FILE_NOT_FOUND;
+
+ // Create a fake entry in the encoding map
+ pNewEntry = (PCASC_ENCODING_ENTRY)Array_Insert(&hs->ExtraEntries, NULL, 1);
+ if(pNewEntry == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the encoding entry
+ pNewEntry->KeyCount = 1;
+ pNewEntry->FileSizeBE[0] = pIndexEntry->FileSizeLE[3];
+ pNewEntry->FileSizeBE[1] = pIndexEntry->FileSizeLE[2];
+ pNewEntry->FileSizeBE[2] = pIndexEntry->FileSizeLE[1];
+ pNewEntry->FileSizeBE[3] = pIndexEntry->FileSizeLE[0];
+ memcpy(pNewEntry->EncodingKey, pQueryKey->pbData, MD5_HASH_SIZE);
+ memcpy(pNewEntry + 1, pQueryKey->pbData + MD5_HASH_SIZE, MD5_HASH_SIZE);
+
+ // Insert the entry to the map of encoding keys
+ Map_InsertObject(hs->pEncodingMap, pNewEntry, pNewEntry->EncodingKey);
+ }
- // Save the game data directory
- hs->szDataPath = NewStr(szDataPath, 0);
-
- // Save the root game directory
- hs->szRootPath = NewStr(szDataPath, 0);
+ // Now we need to insert the entry to the root handler in order
+ // to be able to translate file name to encoding key
+ return RootHandler_Insert(hs->pRootHandler, szFileName, pQueryKey->pbData);
+}
+
+static int InitializeCascDirectories(TCascStorage * hs, const TCHAR * szDataPath)
+{
+ TCHAR * szWorkPath;
+ int nError = ERROR_NOT_ENOUGH_MEMORY;
- // Find the last part
- szLastPathPart = hs->szRootPath;
- for(size_t i = 0; hs->szRootPath[i] != 0; i++)
+ // Find the root directory of the storage. The root directory
+ // is the one where ".build.info" is.
+ szWorkPath = CascNewStr(szDataPath, 0);
+ if(szWorkPath != NULL)
{
- if(hs->szRootPath[i] == '\\' || hs->szRootPath[i] == '/')
- szLastPathPart = hs->szRootPath + i;
- }
-
- // Cut the last part
- if(szLastPathPart != NULL)
- szLastPathPart[0] = 0;
- return (hs->szRootPath && hs->szDataPath) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
+ // Get the length and go up until we find the ".build.info" or ".build.db"
+ for(;;)
+ {
+ // Is this a game directory?
+ nError = CheckGameDirectory(hs, szWorkPath);
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = ERROR_SUCCESS;
+ break;
+ }
+
+ // Cut one path part
+ if(!CutLastPathPart(szWorkPath))
+ {
+ nError = ERROR_FILE_NOT_FOUND;
+ break;
+ }
+ }
+
+ // Free the work path buffer
+ CASC_FREE(szWorkPath);
+ }
+
+ return nError;
}
static bool IndexDirectory_OnFileFound(
@@ -566,7 +569,7 @@ static int CreateArrayOfIndexEntries(TCascStorage * hs)
// 9e dc a7 8f e2 09 ad d8 b7 (encoding file)
// f3 5e bb fb d1 2b 3f ef 8b
// c8 69 9f 18 a2 5e df 7e 52
- Map_InsertObject(pMap, pIndexEntry->IndexKey);
+ Map_InsertObject(pMap, pIndexEntry, pIndexEntry->IndexKey);
// Move to the next entry
pIndexEntry++;
@@ -581,28 +584,28 @@ static int CreateArrayOfIndexEntries(TCascStorage * hs)
return nError;
}
-static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumberOfSegments)
+static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEncodingSegment, DWORD dwNumSegments)
{
PCASC_ENCODING_ENTRY pEncodingEntry;
- size_t nMaxEntries;
- size_t nEntries = 0;
+ DWORD dwMaxEntries;
int nError = ERROR_SUCCESS;
// Sanity check
- assert(hs->ppEncodingEntries == NULL);
assert(hs->pIndexEntryMap != NULL);
+ assert(hs->pEncodingMap == NULL);
- // Calculate the largest eventual number of encodign entries
- nMaxEntries = (dwNumberOfSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE);
+ // Calculate the largest eventual number of encoding entries
+ // Add space for extra entries
+ dwMaxEntries = (dwNumSegments * CASC_ENCODING_SEGMENT_SIZE) / (sizeof(CASC_ENCODING_ENTRY) + MD5_HASH_SIZE);
- // Allocate the array of pointers to encoding entries
- hs->ppEncodingEntries = CASC_ALLOC(PCASC_ENCODING_ENTRY, nMaxEntries);
- if(hs->ppEncodingEntries != NULL)
+ // Create the map of the encoding entries
+ hs->pEncodingMap = Map_Create(dwMaxEntries + CASC_EXTRA_FILES, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ENCODING_ENTRY, EncodingKey));
+ if(hs->pEncodingMap != NULL)
{
- LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumberOfSegments);
+ LPBYTE pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments);
// Parse all segments
- for(DWORD i = 0; i < dwNumberOfSegments; i++)
+ for(DWORD i = 0; i < dwNumSegments; i++)
{
LPBYTE pbEncodingEntry = pbStartOfSegment;
LPBYTE pbEndOfSegment = pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE - sizeof(CASC_ENCODING_ENTRY) - MD5_HASH_SIZE;
@@ -616,7 +619,7 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn
break;
// Insert the pointer the array
- hs->ppEncodingEntries[nEntries++] = pEncodingEntry;
+ Map_InsertObject(hs->pEncodingMap, pEncodingEntry, pEncodingEntry->EncodingKey);
// Move to the next encoding entry
pbEncodingEntry += sizeof(CASC_ENCODING_ENTRY) + (pEncodingEntry->KeyCount * MD5_HASH_SIZE);
@@ -625,9 +628,6 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn
// Move to the next segment
pbStartOfSegment += CASC_ENCODING_SEGMENT_SIZE;
}
-
- // Remember the total number of encoding entries
- hs->nEncodingEntries = nEntries;
}
else
nError = ERROR_NOT_ENOUGH_MEMORY;
@@ -684,8 +684,16 @@ static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
if(dwBytesRead == sizeof(CASC_ENCODING_HEADER))
{
- dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.NumSegments);
- dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.SegmentsPos);
+ // Check the version and sizes
+ if(EncodingHeader.Version != 0x01 || EncodingHeader.ChecksumSizeA != MD5_HASH_SIZE || EncodingHeader.ChecksumSizeB != MD5_HASH_SIZE)
+ {
+ assert(false);
+ return NULL;
+ }
+
+ // Get the number of segments
+ dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.Entries_TableA);
+ dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.Size_StringTable1);
if(EncodingHeader.Magic[0] == 'E' && EncodingHeader.Magic[1] == 'N' && dwSegmentPos != 0 && dwNumSegments != 0)
nError = ERROR_SUCCESS;
}
@@ -721,36 +729,16 @@ static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
{
- TCascFile * hf;
LPBYTE pbRootFile = NULL;
DWORD cbRootFile = 0;
DWORD dwBytesRead = 0;
- BYTE StartOfFile[0x10];
int nError = ERROR_SUCCESS;
- // Dummy read the first 16 bytes
- CascReadFile(hFile, &StartOfFile, sizeof(StartOfFile), &dwBytesRead);
- if(dwBytesRead != sizeof(StartOfFile))
+ // Retrieve the size of the ROOT file
+ cbRootFile = CascGetFileSize(hFile, NULL);
+ if(cbRootFile == 0)
nError = ERROR_BAD_FORMAT;
- // Calculate and allocate space for the entire file
- if(nError == ERROR_SUCCESS)
- {
- // Convert the file handle to pointer to TCascFile
- hf = IsValidFileHandle(hFile);
- if(hf != NULL)
- {
- // Parse the frames to get the file size
- for(DWORD i = 0; i < hf->FrameCount; i++)
- {
- cbRootFile += hf->pFrames[i].FrameSize;
- }
- }
-
- // Evaluate the error
- nError = (cbRootFile != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
- }
-
// Allocate space for the entire file
if(nError == ERROR_SUCCESS)
{
@@ -762,12 +750,9 @@ static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
// If all went OK, we load the entire file to memory
if(nError == ERROR_SUCCESS)
{
- // Copy the header itself
- memcpy(pbRootFile, StartOfFile, sizeof(StartOfFile));
-
- // Read the rest of the data
- CascReadFile(hFile, pbRootFile + sizeof(StartOfFile), cbRootFile - sizeof(StartOfFile), &dwBytesRead);
- if(dwBytesRead != (cbRootFile - sizeof(StartOfFile)))
+ // Read the entire file to memory
+ CascReadFile(hFile, pbRootFile, cbRootFile, &dwBytesRead);
+ if(dwBytesRead != cbRootFile)
nError = ERROR_FILE_CORRUPT;
}
@@ -780,17 +765,19 @@ static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
static int LoadEncodingFile(TCascStorage * hs)
{
PFILE_ENCODING_SEGMENT pEncodingSegment;
- PCASC_ENCODING_ENTRY pEncodingEntry;
+ QUERY_KEY EncodingKey;
LPBYTE pbStartOfSegment;
LPBYTE pbEncodingFile = NULL;
HANDLE hFile = NULL;
DWORD cbEncodingFile = 0;
- DWORD dwNumberOfSegments = 0;
+ DWORD dwNumSegments = 0;
DWORD dwSegmentsPos = 0;
int nError = ERROR_SUCCESS;
// Open the encoding file
- if(!CascOpenFileByIndexKey((HANDLE)hs, &hs->EncodingEKey, 0, &hFile))
+ EncodingKey.pbData = hs->EncodingKey.pbData + MD5_HASH_SIZE;
+ EncodingKey.cbData = MD5_HASH_SIZE;
+ if(!CascOpenFileByIndexKey((HANDLE)hs, &EncodingKey, 0, &hFile))
nError = GetLastError();
// Load the entire ENCODING file to memory
@@ -808,20 +795,25 @@ static int LoadEncodingFile(TCascStorage * hs)
// Verify all encoding segments
if(nError == ERROR_SUCCESS)
{
- // Save the encoding header
- hs->pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile;
+ PCASC_ENCODING_HEADER pEncodingHeader = (PCASC_ENCODING_HEADER)pbEncodingFile;
// Convert size and offset
- dwNumberOfSegments = ConvertBytesToInteger_4(hs->pEncodingHeader->NumSegments);
- dwSegmentsPos = ConvertBytesToInteger_4(hs->pEncodingHeader->SegmentsPos);
+ dwNumSegments = ConvertBytesToInteger_4(pEncodingHeader->Entries_TableA);
+ dwSegmentsPos = ConvertBytesToInteger_4(pEncodingHeader->Size_StringTable1);
+
+ // Store the encoding file to the CASC storage
+ hs->EncodingFile.pbData = pbEncodingFile;
+ hs->EncodingFile.cbData = cbEncodingFile;
// Allocate the array of encoding segments
pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
- pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumberOfSegments);
+ pbStartOfSegment = (LPBYTE)(pEncodingSegment + dwNumSegments);
// Go through all encoding segments and verify them
- for(DWORD i = 0; i < dwNumberOfSegments; i++)
+ for(DWORD i = 0; i < dwNumSegments; i++)
{
+ PCASC_ENCODING_ENTRY pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment;
+
// Check if there is enough space in the buffer
if((pbStartOfSegment + CASC_ENCODING_SEGMENT_SIZE) > (pbEncodingFile + cbEncodingFile))
{
@@ -837,8 +829,7 @@ static int LoadEncodingFile(TCascStorage * hs)
// break;
// }
- // Check if the encoding key matches
- pEncodingEntry = (PCASC_ENCODING_ENTRY)pbStartOfSegment;
+ // Check if the encoding key matches with the expected first value
if(memcmp(pEncodingEntry->EncodingKey, pEncodingSegment->FirstEncodingKey, MD5_HASH_SIZE))
{
nError = ERROR_FILE_CORRUPT;
@@ -856,239 +847,10 @@ static int LoadEncodingFile(TCascStorage * hs)
if(nError == ERROR_SUCCESS)
{
pEncodingSegment = (PFILE_ENCODING_SEGMENT)(pbEncodingFile + sizeof(CASC_ENCODING_HEADER) + dwSegmentsPos);
- nError = CreateMapOfEncodingKeys(hs, pEncodingSegment, dwNumberOfSegments);
- }
- return nError;
-}
-
-typedef struct _CHECK_ROOT_ENTRY_INPUT
-{
- ULONGLONG FileNameHash;
- DWORD SumValue;
- DWORD EncodingKey[4];
-
-} CHECK_ROOT_ENTRY_INPUT, *PCHECK_ROOT_ENTRY_INPUT;
-
-typedef struct _CHECK_ROOT_ENTRY_OUTPUT
-{
- DWORD field_0;
- DWORD field_4;
- DWORD field_8;
- bool field_C;
-
-} CHECK_ROOT_ENTRY_OUTPUT, *PCHECK_ROOT_ENTRY_OUTPUT;
-
-
-// WoW6: 00413F61
-static bool EnlargeHashTableIfMoreThan75PercentUsed(PCASC_ROOT_HASH_TABLE pRootTable, DWORD NewItemCount)
-{
- // Don't relocate anything, just check
- assert((double)NewItemCount / (double)pRootTable->TableSize < .75);
- return true;
-}
-
-// WOW6: 00414402
-// Finds an existing root table entry or a free one
-PCASC_ROOT_ENTRY CascRootTable_FindFreeEntryWithEnlarge(
- PCASC_ROOT_HASH_TABLE pRootTable,
- PCASC_ROOT_ENTRY pNewEntry)
-{
- PCASC_ROOT_ENTRY pEntry;
- DWORD TableIndex;
-
- // The table size must be a power of two
- assert((pRootTable->TableSize & (pRootTable->TableSize - 1)) == 0);
-
- // Make sure that number of occupied items is never bigger
- // than 75% of the table size
- if(!EnlargeHashTableIfMoreThan75PercentUsed(pRootTable, pRootTable->ItemCount + 1))
- return NULL;
-
- // Get the start index of the table
- TableIndex = (DWORD)(pNewEntry->FileNameHash) & (pRootTable->TableSize - 1);
-
- // If that entry is already occupied, move to a next entry
- for(;;)
- {
- // Check that entry if it's free or not
- pEntry = pRootTable->TablePtr + TableIndex;
- if(pEntry->SumValue == 0)
- break;
-
- // Is the found entry equal to the existing one?
- if(pEntry->FileNameHash == pNewEntry->FileNameHash)
- break;
-
- // Move to the next entry
- TableIndex = (TableIndex + 1) & (pRootTable->TableSize - 1);
- }
-
- // Either return a free entry or an existing one
- return pEntry;
-}
-
-// WOW6: 004145D1
-static void CascRootTable_InsertTableEntry(
- PCASC_ROOT_HASH_TABLE pRootTable,
- PCASC_ROOT_ENTRY pNewEntry)
-{
- PCASC_ROOT_ENTRY pEntry;
-
- // Find an existing entry or an empty one
- pEntry = CascRootTable_FindFreeEntryWithEnlarge(pRootTable, pNewEntry);
- assert(pEntry != NULL);
-
- // If that entry is not used yet, fill it in
- if(pEntry->FileNameHash == 0)
- {
- *pEntry = *pNewEntry;
- pRootTable->ItemCount++;
+ nError = CreateMapOfEncodingKeys(hs, pEncodingSegment, dwNumSegments);
}
-}
-
-static int LoadWowRootFileLocales(
- TCascStorage * hs,
- LPBYTE pbRootFile,
- DWORD cbRootFile,
- DWORD dwLocaleMask,
- bool bLoadBlocksWithFlags80,
- BYTE HighestBitValue)
-{
- CASC_ROOT_ENTRY NewRootEntry;
- ROOT_BLOCK_INFO BlockInfo;
- LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
- LPBYTE pbFilePointer;
-
- // Now parse the root file
- for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
- {
- // Validate the file locale block
- pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
- if(pbFilePointer == NULL)
- break;
-
- // WoW.exe (build 19116): Entries with flag 0x100 set are skipped
- if(BlockInfo.pLocaleBlockHdr->Flags & 0x100)
- continue;
-
- // WoW.exe (build 19116): Entries with flag 0x80 set are skipped if arg_4 is set to FALSE (which is by default)
- if(bLoadBlocksWithFlags80 == 0 && (BlockInfo.pLocaleBlockHdr->Flags & 0x80))
- continue;
- // WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to arg_8 are skipped
- if((BYTE)(BlockInfo.pLocaleBlockHdr->Flags >> 0x1F) != HighestBitValue)
- continue;
-
- // WoW.exe (build 19116): Locales other than defined mask are skipped too
- if((BlockInfo.pLocaleBlockHdr->Locales & dwLocaleMask) == 0)
- continue;
-
- // Reset the sum value
- NewRootEntry.SumValue = 0;
-
- // WoW.exe (build 19116): Blocks with zero files are skipped
- for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
- {
- // (004147A3) Prepare the CASC_ROOT_ENTRY structure
- NewRootEntry.FileNameHash = BlockInfo.pRootEntries[i].FileNameHash;
- NewRootEntry.SumValue = NewRootEntry.SumValue + BlockInfo.pInt32Array[i];
- NewRootEntry.Locales = BlockInfo.pLocaleBlockHdr->Locales;
- NewRootEntry.EncodingKey[0] = BlockInfo.pRootEntries[i].EncodingKey[0];
- NewRootEntry.EncodingKey[1] = BlockInfo.pRootEntries[i].EncodingKey[1];
- NewRootEntry.EncodingKey[2] = BlockInfo.pRootEntries[i].EncodingKey[2];
- NewRootEntry.EncodingKey[3] = BlockInfo.pRootEntries[i].EncodingKey[3];
-
- // Insert the root table item to the hash table
- CascRootTable_InsertTableEntry(&hs->RootTable, &NewRootEntry);
- NewRootEntry.SumValue++;
- }
- }
-
- return 1;
-}
-
-// WoW.exe: 004146C7 (BuildManifest::Load)
-static int LoadWowRootFileWithParams(
- TCascStorage * hs,
- LPBYTE pbRootFile,
- DWORD cbRootFile,
- DWORD dwLocaleBits,
- BYTE HighestBitValue)
-{
- // Load the locale as-is
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, dwLocaleBits, false, HighestBitValue);
-
- // If we wanted enGB, we also load enUS for the missing files
- if(dwLocaleBits == CASC_LOCALE_ENGB)
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
-
- if(dwLocaleBits == CASC_LOCALE_PTPT)
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
-
- return ERROR_SUCCESS;
-}
-
-/*
- // Code from WoW.exe
- if(dwLocaleBits == CASC_LOCALE_DUAL_LANG)
- {
- // Is this english version of WoW?
- if(arg_4 == CASC_LOCALE_BIT_ENUS)
- {
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, false, HighestBitValue);
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
- return ERROR_SUCCESS;
- }
-
- // Is this portuguese version of WoW?
- if(arg_4 == CASC_LOCALE_BIT_PTBR)
- {
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, false, HighestBitValue);
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
- }
- }
-
- LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << arg_4), false, HighestBitValue);
-*/
-
-static int LoadWowRootFile(
- TCascStorage * hs,
- LPBYTE pbRootFile,
- DWORD cbRootFile,
- DWORD dwLocaleMask)
-{
- int nError;
-
- // Dump the root file, if needed
-#ifdef CASC_DUMP_ROOT_FILE
- //CascDumpRootFile(hs,
- // pbRootFile,
- // cbRootFile,
- // "\\casc_root_%build%.txt",
- // _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"),
- // CASC_DUMP_ROOT_FILE);
-#endif
-
- // Allocate root table entries. Note that the initial size
- // of the root table is set to 0x00200000 by World of Warcraft 6.x
- hs->RootTable.TablePtr = CASC_ALLOC(CASC_ROOT_ENTRY, CASC_INITIAL_ROOT_TABLE_SIZE);
- hs->RootTable.TableSize = CASC_INITIAL_ROOT_TABLE_SIZE;
- if(hs->RootTable.TablePtr == NULL)
- return ERROR_NOT_ENOUGH_MEMORY;
-
- // Clear the entire table
- memset(hs->RootTable.TablePtr, 0, CASC_INITIAL_ROOT_TABLE_SIZE * sizeof(CASC_ROOT_ENTRY));
-
- // Load the root file
- nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 0);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 1);
- if(nError != ERROR_SUCCESS)
- return nError;
-
- return ERROR_SUCCESS;
+ return nError;
}
static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask)
@@ -1100,49 +862,70 @@ static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask)
int nError = ERROR_SUCCESS;
// Sanity checks
- assert(hs->RootTable.TablePtr == NULL);
- assert(hs->RootTable.ItemCount == 0);
- assert(hs->ppEncodingEntries != NULL);
+ assert(hs->pEncodingMap != NULL);
+ assert(hs->pRootHandler == NULL);
// Locale: The default parameter is 0 - in that case,
// we assign the default locale, loaded from the .build.info file
if(dwLocaleMask == 0)
dwLocaleMask = hs->dwDefaultLocale;
- // The root file is either MNDX file (Heroes of the Storm)
- // or a file containing an array of root entries (World of Warcraft 6.0+)
- // Note: The "root" key file's MD5 hash is equal to its name
- // in the configuration
+ // Load the entire ROOT file to memory
if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile))
nError = GetLastError();
- // Load the entire ROOT file to memory
+ // Load the entire file to memory
if(nError == ERROR_SUCCESS)
{
- // Load the necessary part of the ENCODING file to memory
pbRootFile = LoadRootFileToMemory(hFile, &cbRootFile);
- if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK))
- nError = ERROR_FILE_CORRUPT;
-
- // Close the encoding file
CascCloseFile(hFile);
}
- // Check if the file is a MNDX file
- if(nError == ERROR_SUCCESS)
+ // Check if the version of the ROOT file
+ if(nError == ERROR_SUCCESS && pbRootFile != NULL)
{
FileSignature = (PDWORD)pbRootFile;
- if(FileSignature[0] == CASC_MNDX_SIGNATURE)
+ switch(FileSignature[0])
{
- nError = LoadMndxRootFile(hs, pbRootFile, cbRootFile);
- }
- else
- {
- // WOW6: 00415000
- nError = LoadWowRootFile(hs, pbRootFile, cbRootFile, dwLocaleMask);
+ case CASC_MNDX_ROOT_SIGNATURE:
+ nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile);
+ break;
+
+ case CASC_DIABLO3_ROOT_SIGNATURE:
+ nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile);
+ break;
+
+ case CASC_OVERWATCH_ROOT_SIGNATURE:
+ nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile);
+ break;
+
+ default:
+ nError = RootHandler_CreateWoW6(hs, pbRootFile, cbRootFile, dwLocaleMask);
+ break;
}
}
+ // Insert entry for the
+ if(nError == ERROR_SUCCESS)
+ {
+ InsertExtraFile(hs, "ENCODING", &hs->EncodingKey);
+ InsertExtraFile(hs, "ROOT", &hs->RootKey);
+ InsertExtraFile(hs, "DOWNLOAD", &hs->DownloadKey);
+ InsertExtraFile(hs, "INSTALL", &hs->InstallKey);
+ }
+
+#ifdef _DEBUG
+ if(nError == ERROR_SUCCESS)
+ {
+ //RootFile_Dump(hs,
+ // pbRootFile,
+ // cbRootFile,
+ // _T("\\casc_root_%build%.txt"),
+ // _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"),
+ // DUMP_LEVEL_INDEX_ENTRIES);
+ }
+#endif
+
// Free the root file
CASC_FREE(pbRootFile);
return nError;
@@ -1154,19 +937,19 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
if(hs != NULL)
{
- // Free the MNDX info
- if(hs->pPackages != NULL)
- CASC_FREE(hs->pPackages);
- if(hs->pMndxInfo != NULL)
- FreeMndxInfo(hs->pMndxInfo);
+ // Free the root handler
+ if(hs->pRootHandler != NULL)
+ RootHandler_Close(hs->pRootHandler);
+ hs->pRootHandler = NULL;
+
+ // Free the extra encoding entries
+ Array_Free(&hs->ExtraEntries);
// Free the pointers to file entries
- if(hs->RootTable.TablePtr != NULL)
- CASC_FREE(hs->RootTable.TablePtr);
- if(hs->ppEncodingEntries != NULL)
- CASC_FREE(hs->ppEncodingEntries);
- if(hs->pEncodingHeader != NULL)
- CASC_FREE(hs->pEncodingHeader);
+ if(hs->pEncodingMap != NULL)
+ Map_Free(hs->pEncodingMap);
+ if(hs->EncodingFile.pbData != NULL)
+ CASC_FREE(hs->EncodingFile.pbData);
if(hs->pIndexEntryMap != NULL)
Map_Free(hs->pIndexEntryMap);
@@ -1195,25 +978,24 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
CASC_FREE(hs->szRootPath);
if(hs->szDataPath != NULL)
CASC_FREE(hs->szDataPath);
+ if(hs->szBuildFile != NULL)
+ CASC_FREE(hs->szBuildFile);
if(hs->szIndexPath != NULL)
CASC_FREE(hs->szIndexPath);
if(hs->szUrlPath != NULL)
CASC_FREE(hs->szUrlPath);
- // Fre the blob arrays
- QUERY_KEY_FreeArray(hs->pArchiveArray);
- QUERY_KEY_FreeArray(hs->pPatchArchiveArray);
- QUERY_KEY_FreeArray(hs->pEncodingKeys);
-
// Free the blobs
- QUERY_KEY_Free(&hs->CdnConfigKey);
- QUERY_KEY_Free(&hs->CdnBuildKey);
- QUERY_KEY_Free(&hs->ArchiveGroup);
- QUERY_KEY_Free(&hs->PatchArchiveGroup);
- QUERY_KEY_Free(&hs->RootKey);
- QUERY_KEY_Free(&hs->PatchKey);
- QUERY_KEY_Free(&hs->DownloadKey);
- QUERY_KEY_Free(&hs->InstallKey);
+ FreeCascBlob(&hs->CdnConfigKey);
+ FreeCascBlob(&hs->CdnBuildKey);
+ FreeCascBlob(&hs->ArchivesGroup);
+ FreeCascBlob(&hs->ArchivesKey);
+ FreeCascBlob(&hs->PatchArchivesKey);
+ FreeCascBlob(&hs->RootKey);
+ FreeCascBlob(&hs->PatchKey);
+ FreeCascBlob(&hs->DownloadKey);
+ FreeCascBlob(&hs->InstallKey);
+ FreeCascBlob(&hs->EncodingKey);
// Free the storage structure
hs->szClassName = NULL;
@@ -1266,6 +1048,13 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE
nError = LoadEncodingFile(hs);
}
+ // Initialize the dynamic array for extra files
+ // Reserve space for 0x20 encoding entries
+ if(nError == ERROR_SUCCESS)
+ {
+ nError = Array_Create(&hs->ExtraEntries, CASC_ENCODING_ENTRY_1, CASC_EXTRA_FILES);
+ }
+
// Load the index files
if(nError == ERROR_SUCCESS)
{
@@ -1309,8 +1098,7 @@ bool WINAPI CascGetStorageInfo(
break;
case CascStorageFeatures:
- if(hs->pMndxInfo != NULL)
- dwInfoValue |= CASC_FEATURE_LISTFILE;
+ dwInfoValue |= (hs->pRootHandler->dwRootFlags & ROOT_FLAG_HAS_NAMES) ? CASC_FEATURE_LISTFILE : 0;
break;
case CascStorageGameInfo:
@@ -1342,8 +1130,6 @@ bool WINAPI CascGetStorageInfo(
return true;
}
-
-
bool WINAPI CascCloseStorage(HANDLE hStorage)
{
TCascStorage * hs;
diff --git a/dep/CascLib/src/CascPort.h b/dep/CascLib/src/CascPort.h
index 3bf1efde4b8..5d0190e07cc 100644
--- a/dep/CascLib/src/CascPort.h
+++ b/dep/CascLib/src/CascPort.h
@@ -176,6 +176,7 @@
#define _tcsrchr strrchr
#define _tcsstr strstr
#define _tcsspn strspn
+ #define _tcsncmp strncmp
#define _tprintf printf
#define _stprintf sprintf
#define _tremove remove
diff --git a/dep/CascLib/src/CascReadFile.cpp b/dep/CascLib/src/CascReadFile.cpp
index 83fdd0d2096..72eb2c4b647 100644
--- a/dep/CascLib/src/CascReadFile.cpp
+++ b/dep/CascLib/src/CascReadFile.cpp
@@ -98,9 +98,10 @@ static int LoadFileFrames(TCascFile * hf)
else
nError = GetLastError();
- // Note: Do not take the FileSize from the sum of frames.
- // This value is invalid when loading the ENCODING file.
-// hf->FileSize = FileSize;
+ // Note: on ENCODING file, this value is almost always bigger
+ // then the real size of ENCODING. We handle this problem
+ // by calculating size of the ENCODIG file from its header.
+ hf->FileSize = FileSize;
#ifdef CASCLIB_TEST
hf->FileSize_FrameSum = FileSize;
@@ -264,6 +265,85 @@ static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer)
return NULL;
}
+static int ProcessFileFrame(
+ LPBYTE pbOutBuffer,
+ DWORD cbOutBuffer,
+ LPBYTE pbInBuffer,
+ DWORD cbInBuffer,
+ DWORD dwFrameIndex)
+{
+ LPBYTE pbTempBuffer;
+ LPBYTE pbWorkBuffer;
+ DWORD cbTempBuffer = CASCLIB_MAX(cbInBuffer, cbOutBuffer);
+ DWORD cbWorkBuffer = cbOutBuffer + 1;
+ DWORD dwStepCount = 0;
+ bool bWorkComplete = false;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate the temporary buffer that will serve as output
+ pbWorkBuffer = pbTempBuffer = CASC_ALLOC(BYTE, cbTempBuffer);
+ if(pbWorkBuffer == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Perform the loop
+ for(;;)
+ {
+ // Set the output buffer.
+ // Even operations: extract to temporary buffer
+ // Odd operations: extract to output buffer
+ pbWorkBuffer = (dwStepCount & 0x01) ? pbOutBuffer : pbTempBuffer;
+ cbWorkBuffer = (dwStepCount & 0x01) ? cbOutBuffer : cbTempBuffer;
+
+ // Perform the operation specific to the operation ID
+ switch(pbInBuffer[0])
+ {
+ case 'E': // Encrypted files
+ nError = CascDecrypt(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex);
+ bWorkComplete = (nError != ERROR_SUCCESS);
+ break;
+
+ case 'Z': // ZLIB compressed files
+ nError = CascDecompress(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1);
+ bWorkComplete = true;
+ break;
+
+ case 'N': // Normal stored files
+ nError = CascDirectCopy(pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1);
+ bWorkComplete = true;
+ break;
+
+ case 'F': // Recursive frames - not supported
+ default: // Unrecognized - if we unpacked something, we consider it done
+ nError = ERROR_NOT_SUPPORTED;
+ bWorkComplete = true;
+ assert(false);
+ break;
+ }
+
+ // Are we done?
+ if(bWorkComplete)
+ break;
+
+ // Set the input buffer to the work buffer
+ pbInBuffer = pbWorkBuffer;
+ cbInBuffer = cbWorkBuffer;
+ dwStepCount++;
+ }
+
+ // If the data are currently in the temporary buffer,
+ // we need to copy them to output buffer
+ if(nError == ERROR_SUCCESS && pbWorkBuffer != pbOutBuffer)
+ {
+ if(cbWorkBuffer != cbOutBuffer)
+ nError = ERROR_INSUFFICIENT_BUFFER;
+ memcpy(pbOutBuffer, pbWorkBuffer, cbOutBuffer);
+ }
+
+ // Free the temporary buffer
+ CASC_FREE(pbTempBuffer);
+ return nError;
+}
+
//-----------------------------------------------------------------------------
// Public functions
@@ -299,7 +379,7 @@ DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
}
// Make sure that the file header area is loaded
- nError = EnsureHeaderAreaIsLoaded(hf);
+ nError = EnsureFrameHeadersLoaded(hf);
if(nError != ERROR_SUCCESS)
{
SetLastError(nError);
@@ -387,7 +467,6 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
DWORD dwFilePointer = 0;
DWORD dwEndPointer = 0;
DWORD dwFrameSize;
- DWORD cbOutBuffer;
bool bReadResult;
int nError = ERROR_SUCCESS;
@@ -423,7 +502,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
{
// Get the frame
pFrame = FindFileFrame(hf, hf->FilePointer);
- if(pFrame == NULL)
+ if(pFrame == NULL || pFrame->CompressedSize < 1)
nError = ERROR_FILE_CORRUPT;
}
@@ -439,7 +518,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
// Perform block read from each file frame
while(dwFilePointer < dwEndPointer)
{
- LPBYTE pbRawData = NULL;
+ LPBYTE pbFrameData = NULL;
DWORD dwFrameStart = pFrame->FrameFileOffset;
DWORD dwFrameEnd = pFrame->FrameFileOffset + pFrame->FrameSize;
@@ -457,8 +536,8 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
}
// We also need to allocate buffer for the raw data
- pbRawData = CASC_ALLOC(BYTE, pFrame->CompressedSize);
- if(pbRawData == NULL)
+ pbFrameData = CASC_ALLOC(BYTE, pFrame->CompressedSize);
+ if(pbFrameData == NULL)
{
nError = ERROR_NOT_ENOUGH_MEMORY;
break;
@@ -466,7 +545,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
// Load the raw file data to memory
FileOffset = pFrame->FrameArchiveOffset;
- bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbRawData, pFrame->CompressedSize);
+ bReadResult = FileStream_Read(hf->pStream, &FileOffset, pbFrameData, pFrame->CompressedSize);
// Note: The raw file data size could be less than expected
// Happened in WoW build 19342 with the ROOT file. MD5 in the frame header
@@ -484,43 +563,34 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
// If the frame offset is before EOF and frame end is beyond EOF, correct it
if(FileOffset < StreamSize && dwFrameSize < pFrame->CompressedSize)
{
- memset(pbRawData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize));
+ memset(pbFrameData + dwFrameSize, 0, (pFrame->CompressedSize - dwFrameSize));
bReadResult = true;
}
}
// If the read result failed, we cannot finish reading it
- if(bReadResult == false)
- {
- CASC_FREE(pbRawData);
- nError = GetLastError();
- break;
- }
-
- // Verify the block MD5
- if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
+ if(bReadResult && VerifyDataBlockHash(pbFrameData, pFrame->CompressedSize, pFrame->md5))
{
- CASC_FREE(pbRawData);
- nError = ERROR_FILE_CORRUPT;
- break;
+ // Convert the source frame to the file cache
+ nError = ProcessFileFrame(hf->pbFileCache,
+ pFrame->FrameSize,
+ pbFrameData,
+ pFrame->CompressedSize,
+ (DWORD)(pFrame - hf->pFrames));
+ if(nError == ERROR_SUCCESS)
+ {
+ // Set the start and end of the cache
+ hf->CacheStart = dwFrameStart;
+ hf->CacheEnd = dwFrameEnd;
+ }
}
-
- // Decompress the file frame
- cbOutBuffer = pFrame->FrameSize;
- nError = CascDecompress(hf->pbFileCache, &cbOutBuffer, pbRawData, pFrame->CompressedSize);
- if(nError != ERROR_SUCCESS || cbOutBuffer != pFrame->FrameSize)
+ else
{
- CASC_FREE(pbRawData);
nError = ERROR_FILE_CORRUPT;
- break;
}
- // Set the start and end of the cache
- hf->CacheStart = dwFrameStart;
- hf->CacheEnd = dwFrameEnd;
-
- // Free the decompress buffer, if needed
- CASC_FREE(pbRawData);
+ // Free the raw frame data
+ CASC_FREE(pbFrameData);
}
// Copy the decompressed data
diff --git a/dep/CascLib/src/CascRootFile_Diablo3.cpp b/dep/CascLib/src/CascRootFile_Diablo3.cpp
new file mode 100644
index 00000000000..98a42cc3226
--- /dev/null
+++ b/dep/CascLib/src/CascRootFile_Diablo3.cpp
@@ -0,0 +1,1189 @@
+/*****************************************************************************/
+/* CascRootFile_Diablo3.cpp Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Support for loading Diablo 3 ROOT file */
+/* Note: D3 offsets refer to Diablo III.exe 2.2.0.30013 (32-bit) */
+/* SHA1: e4f17eca8aad8dde70870bf932ac3f5b85f17a1f */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 04.03.15 1.00 Lad The first version of CascRootFile_Diablo3.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+#define DIABLO3_SUBDIR_SIGNATURE 0xEAF1FE87
+#define DIABLO3_PACKAGES_SIGNATURE 0xAABB0002
+#define DIABLO3_MAX_SUBDIRS 0x20
+
+#define DIABLO3_INVALID_INDEX 0xFFFFFFFF
+#define DIABLO3_INVALID_FILE 0xFFFFFFFF
+#define DIABLO3_MAX_ASSETS 70 // Maximum possible number of assets
+#define DIABLO3_MAX_LEVEL0_LENGTH 0x10 // Maximum length of the level-0 directory name
+
+#define INVALID_FILE_INDEX 0xFFFFFFFF
+#define INVALID_ASSET_INDEX 0xFF
+
+#define ENTRY_FLAG_DIRECTORY_ENTRY 0x80 // The file is actually a directory entry
+#define ENTRY_FLAG_PLAIN_NAME 0x01 // If set, the file entry contains offset of the plain file name
+#define ENTRY_FLAG_FULL_NAME 0x02 // If set, the file entry contains offset of the full name
+#define ENTRY_FLAG_FLAGS_MASK 0xF0 // Mask for the entry flags
+#define ENTRY_FLAG_NAME_MASK 0x0F // Mask for the entry file name type
+
+// Values for CASC_FILE_ENTRY::dwFlags
+#define CASC_ENTRY_SHORT_NAME 0x000000001 // If set, the name is in format XXYYplain-name[\sub-index].ext
+#define CASC_ENTRY_HAS_SUBINDEX 0x000000002 // If set, the subitem is present in the file name (i.e. XXYYplain-name\sub-index.ext)
+
+#define SEARCH_PHASE_NAMES 0 // Searching named entry
+#define SEARCH_PHASE_FILE_IDS 1 // Searching filed by ID
+
+// Macro for constructing 64-bit integer from root-index, file-index and sub-index
+// The result value is RRAAAAAAAASSSSSS
+#define MAKE_INDEX64(ri, fi, si) (((ULONGLONG)ri << 0x38) | ((ULONGLONG)fi << 0x18) | ((ULONGLONG)si))
+#define INDEX64_ROOT_INDEX(hash) (DWORD)((hash >> 0x38) & 0x000000FF)
+#define INDEX64_FILE_INDEX(hash) (DWORD)((hash >> 0x18) & 0xFFFFFFFF)
+#define INDEX64_SUB_INDEX(hash) (DWORD)((hash >> 0x00) & 0x00FFFFFF)
+
+// On-disk structure for a file given by file number
+typedef struct _DIABLO3_FILEID1_ENTRY
+{
+ ENCODING_KEY EncodingKey; // Encoding key for the file
+ DWORD FileIndex; // File index
+} DIABLO3_FILEID1_ENTRY, *PDIABLO3_FILEID1_ENTRY;
+
+// On-disk structure for a file given by file number and suffix
+typedef struct _DIABLO3_FILEID2_ENTRY
+{
+ ENCODING_KEY EncodingKey; // Encoding key for the file
+ DWORD FileIndex; // File index
+ DWORD SubIndex; // File subindex, like "SoundBank\3D Ambience\0000.smp"
+} DIABLO3_FILEID2_ENTRY, *PDIABLO3_FILEID2_ENTRY;
+
+// On-disk structure of the named entry
+typedef struct _DIABLO3_NAMED_ENTRY
+{
+ ENCODING_KEY EncodingKey; // Encoding key for the file
+ BYTE szFileName[1]; // ASCIIZ file name (variable length)
+} DIABLO3_NAMED_ENTRY, *PDIABLO3_NAMED_ENTRY;
+
+// On-disk structure of CoreToc.dat header
+typedef struct _DIABLO3_CORE_TOC_HEADER
+{
+ DWORD EntryCounts[DIABLO3_MAX_ASSETS]; // Array of number of entries (files) for each asset (level-1 directory)
+ DWORD EntryOffsets[DIABLO3_MAX_ASSETS]; // Array of offsets of each DIABLO3_CORE_TOC_ENTRY, relative to data after header
+ DWORD Unknowns[DIABLO3_MAX_ASSETS]; // Unknown
+ DWORD Alignment;
+} DIABLO3_CORE_TOC_HEADER, *PDIABLO3_CORE_TOC_HEADER;
+
+// On-disk structure of the entry in CoreToc.dat
+typedef struct _DIABLO3_CORE_TOC_ENTRY
+{
+ DWORD AssetIndex; // Index of the Diablo3 asset (aka directory)
+ DWORD FileIndex; // File index
+ DWORD NameOffset; // Offset of the plain file name
+
+} DIABLO3_CORE_TOC_ENTRY, *PDIABLO3_CORE_TOC_ENTRY;
+
+// In-memory structure of parsed directory header
+typedef struct _DIABLO3_DIR_HEADER
+{
+ LPBYTE pbEntries1;
+ LPBYTE pbEntries2;
+ LPBYTE pbEntries3;
+ DWORD dwEntries1;
+ DWORD dwEntries2;
+ DWORD dwEntries3;
+} DIABLO3_DIR_HEADER, *PDIABLO3_DIR_HEADER;
+
+// In-memory structure of loaded CoreTOC.dat
+typedef struct _DIABLO3_CORE_TOC
+{
+ DIABLO3_CORE_TOC_HEADER Hdr; // Header of CoreTOC.dat
+
+ LPBYTE pbCoreToc; // Content of the CoreTOC.dat file
+ DIABLO3_CORE_TOC_ENTRY Entries[1]; // Buffer for storing the entries (variable length)
+
+} DIABLO3_CORE_TOC, *PDIABLO3_CORE_TOC;
+
+// On-disk structure of Packages.dat header
+typedef struct _DIABLO3_PACKAGES_DAT_HEADER
+{
+ DWORD Signature;
+ DWORD NumberOfNames;
+} DIABLO3_PACKAGES_DAT_HEADER, *PDIABLO3_PACKAGES_DAT_HEADER;
+
+// Structure for conversion DirectoryID -> Directory name
+typedef struct _DIABLO3_ASSET_INFO
+{
+ const char * szDirectoryName; // Directory name
+ const char * szExtension;
+
+} DIABLO3_ASSET_INFO;
+typedef const DIABLO3_ASSET_INFO * PDIABLO3_ASSET_INFO;
+
+// In-memory structure of a file entry in the linear file list
+typedef struct _CASC_FILE_ENTRY
+{
+ ENCODING_KEY EncodingKey; // Encoding key
+ ULONGLONG FileNameHash; // Hash of the full file name
+ DWORD dwFileName; // Offset of the name (in name's dynamic array)
+ DWORD dwFlags; // Entry flags (see CASC_ENTRY_XXXX)
+
+ DWORD NameOffset; // Offset of the name (in name's dynamic array)
+ USHORT SubIndex; // File\SubFile index
+ BYTE AssetIndex; // Asset index (aka directory index)
+ BYTE EntryFlags; // Entry flags
+} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY;
+
+//-----------------------------------------------------------------------------
+// Structure definitions for Diablo3 root file
+
+struct TRootHandler_Diablo3 : public TRootHandler
+{
+ // Linear global list of all files
+ DYNAMIC_ARRAY FileTable;
+
+ // Linear global list of names
+ DYNAMIC_ARRAY FileNames;
+
+ // Global map of FileName -> FileEntry
+ PCASC_MAP pRootMap;
+};
+
+//-----------------------------------------------------------------------------
+// Local variables
+
+static const DIABLO3_ASSET_INFO Assets[] =
+{
+// DIR-NAME EXTENSION
+// ========== =========
+ {NULL, NULL}, // 0x00
+ {"Actor", "acr"}, // 0x01
+ {"Adventure", "adv"}, // 0x02
+ {NULL, NULL}, // 0x03
+ {NULL, NULL}, // 0x04
+ {"AmbientSound", "ams"}, // 0x05
+ {"Anim", "ani"}, // 0x06
+ {"Anim2D", "an2"}, // 0x07
+ {"AnimSet", "ans"}, // 0x08
+ {"Appearance", "app"}, // 0x09
+ {NULL, NULL}, // 0x0A
+ {"Cloth", "clt"}, // 0x0B
+ {"Conversation", "cnv"}, // 0x0C
+ {NULL, NULL}, // 0x0D
+ {"EffectGroup", "efg"}, // 0x0E
+ {"Encounter", "enc"}, // 0x0F
+ {NULL, NULL}, // 0x10
+ {"Explosion", "xpl"}, // 0x11
+ {NULL, NULL}, // 0x12
+ {"Font", "fnt"}, // 0x13
+ {"GameBalance", "gam"}, // 0x14
+ {"Globals", "glo"}, // 0x15
+ {"LevelArea", "lvl"}, // 0x16
+ {"Light", "lit"}, // 0x17
+ {"MarkerSet", "mrk"}, // 0x18
+ {"Monster", "mon"}, // 0x19
+ {"Observer", "obs"}, // 0x1A
+ {"Particle", "prt"}, // 0x1B
+ {"Physics", "phy"}, // 0x1C
+ {"Power", "pow"}, // 0x1D
+ {NULL, NULL}, // 0x1E
+ {"Quest", "qst"}, // 0x1F
+ {"Rope", "rop"}, // 0x20
+ {"Scene", "scn"}, // 0x21
+ {"SceneGroup", "scg"}, // 0x22
+ {NULL, NULL}, // 0x23
+ {"ShaderMap", "shm"}, // 0x24
+ {"Shaders", "shd"}, // 0x25
+ {"Shakes", "shk"}, // 0x26
+ {"SkillKit", "skl"}, // 0x27
+ {"Sound", "snd"}, // 0x28
+ {"SoundBank", "sbk"}, // 0x29
+ {"StringList", "stl"}, // 0x2A
+ {"Surface", "srf"}, // 0x2B
+ {"Textures", "tex"}, // 0x2C
+ {"Trail", "trl"}, // 0x2D
+ {"UI", "ui"}, // 0x2E
+ {"Weather", "wth"}, // 0x2F
+ {"Worlds", "wrl"}, // 0x30
+ {"Recipe", "rcp"}, // 0x31
+ {NULL, NULL}, // 0x32
+ {"Condition", "cnd"}, // 0x33
+ {NULL, NULL}, // 0x34
+ {NULL, NULL}, // 0x35
+ {NULL, NULL}, // 0x36
+ {NULL, NULL}, // 0x37
+ {"Act", "act"}, // 0x38
+ {"Material", "mat"}, // 0x39
+ {"QuestRange", "qsr"}, // 0x3A
+ {"Lore", "lor"}, // 0x3B
+ {"Reverb", "rev"}, // 0x3C
+ {"PhysMesh", "phm"}, // 0x3D
+ {"Music", "mus"}, // 0x3E
+ {"Tutorial", "tut"}, // 0x3F
+ {"BossEncounter", "bos"}, // 0x40
+ {NULL, NULL}, // 0x41
+ {"Accolade", "aco"}, // 0x42
+};
+
+static const DIABLO3_ASSET_INFO UnknownAsset = {"Unknown", "xxx"};
+
+#define DIABLO3_ASSET_COUNT (sizeof(Assets) / sizeof(Assets[0]))
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex)
+{
+ if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL)
+ return &Assets[dwAssetIndex];
+ return &UnknownAsset;
+}
+
+static DWORD VerifyNamedFileEntry(LPBYTE pbNamedEntry, LPBYTE pbFileEnd)
+{
+ LPBYTE pbFileName = ((PDIABLO3_NAMED_ENTRY)pbNamedEntry)->szFileName;
+
+ // Find the end of the name
+ while(pbFileName < pbFileEnd && pbFileName[0] != 0)
+ pbFileName++;
+
+ // Did we get past the end of the root file?
+ if(pbFileName >= pbFileEnd)
+ return 0;
+ pbFileName++;
+
+ // Return the length of the structure
+ return (DWORD)(pbFileName - pbNamedEntry);
+}
+
+static char * FindPackageName(
+ PCASC_MAP pPackageMap,
+ const char * szAssetName,
+ const char * szPlainName)
+{
+ char szFileName[MAX_PATH+1];
+ size_t nLength;
+
+ // Construct the name without extension and find it in the map
+ nLength = sprintf(szFileName, "%s\\%s", szAssetName, szPlainName);
+ return (char *)Map_FindString(pPackageMap, szFileName, szFileName + nLength);
+}
+
+static size_t CreateShortName(
+ PCASC_MAP pPackageMap,
+ DWORD dwRootIndex, // Level-0-dir: Index of the root subdirectory
+ DWORD dwAssetIndex, // Level-1-dir: Index of the asset name
+ const char * szPlainName, // Plain name of the file, without extension
+ DWORD dwSubIndex,
+ char * szBuffer)
+{
+ PDIABLO3_ASSET_INFO pAssetInfo = GetAssetInfo(dwAssetIndex);
+ const char * szPackageName = NULL;
+ const char * szFormat;
+ size_t nLength;
+
+ // Write the level-0 directory index as 2-digit hexa number
+ assert(dwRootIndex < 0x100);
+ *szBuffer++ = IntToHexChar[dwRootIndex >> 0x04];
+ *szBuffer++ = IntToHexChar[dwRootIndex & 0x0F];
+
+ // Write the level-1 directory index as 2-digit hexa number
+ assert(dwAssetIndex < 0x100);
+ *szBuffer++ = IntToHexChar[dwAssetIndex >> 0x04];
+ *szBuffer++ = IntToHexChar[dwAssetIndex & 0x0F];
+
+ // Construct the file name with ending "." for extension
+ szFormat = (dwSubIndex != DIABLO3_INVALID_INDEX) ? "%s\\%04u." : "%s.";
+ nLength = sprintf(szBuffer, szFormat, szPlainName, dwSubIndex);
+
+ // Try to fixup the file extension from the package name.
+ // File extensions are not predictable because for subitems,
+ // they are not always equal to the main items:
+ //
+ // SoundBank\3D Ambience.sbk
+ // SoundBank\3D Ambience\0000.smp
+ // SoundBank\3D Ambience\0002.smp
+ // ...
+ // SoundBank\Angel.sbk
+ // SoundBank\Angel\0000.fsb
+ // SoundBank\Angel\0002.fsb
+ //
+ // We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible
+ //
+ if(pPackageMap != NULL)
+ {
+ // Retrieve the asset name
+ szPackageName = FindPackageName(pPackageMap, pAssetInfo->szDirectoryName, szBuffer);
+ if(szPackageName != NULL)
+ {
+ strcpy(szBuffer, szPackageName + strlen(pAssetInfo->szDirectoryName) + 1);
+ nLength = strlen(szBuffer);
+ }
+ }
+
+ // If we havent't found the package, we either use the default asset extension or "xxx"
+ if(szPackageName == NULL)
+ {
+ if(dwSubIndex == DIABLO3_INVALID_INDEX)
+ {
+ strcpy(szBuffer + nLength, pAssetInfo->szExtension);
+ nLength += strlen(pAssetInfo->szExtension);
+ }
+ else
+ {
+ strcpy(szBuffer + nLength, "xxx");
+ nLength += 3;
+ }
+ }
+
+ // Return the length of the short file name
+ return nLength + 4;
+}
+
+static size_t CreateFileName(
+ TRootHandler_Diablo3 * pRootHandler,
+ const char * szShortName, // Short file name of the file
+ char * szBuffer)
+{
+ PCASC_FILE_ENTRY pRootEntry;
+ const char * szNameLevel0;
+ const char * szNameLevel1 = NULL;
+ DWORD dwRootIndex0 = 0;
+ DWORD dwAssetIndex = 0;
+
+ // Retrieve the level-0 and level-1 directory indexes
+ ConvertStringToInt08(szShortName+0, &dwRootIndex0);
+ ConvertStringToInt08(szShortName+2, &dwAssetIndex);
+
+ // Retrieve the name of the level-0 directory (aka root subdirectory)
+ pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootIndex0);
+ szNameLevel0 = (char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName);
+
+ // Retrieve the name of the level-1 directory (aka asset name)
+ if(dwAssetIndex < DIABLO3_ASSET_COUNT)
+ szNameLevel1 = Assets[dwAssetIndex].szDirectoryName;
+ if(szNameLevel1 == NULL)
+ szNameLevel1 = UnknownAsset.szDirectoryName;
+
+ // Copy the rest of the name as-is
+ return sprintf(szBuffer, "%s\\%s\\%s", szNameLevel0, szNameLevel1, szShortName + 4);
+}
+
+
+// Creates a map of String -> Pointer
+static PCASC_MAP CreatePackageMap(
+ LPBYTE pbPackagesDat,
+ LPBYTE pbPackagesEnd)
+{
+ PDIABLO3_PACKAGES_DAT_HEADER pDatHeader = (PDIABLO3_PACKAGES_DAT_HEADER)pbPackagesDat;
+ PCASC_MAP pPackageMap;
+
+ // Get the header
+ if((pbPackagesDat + sizeof(DIABLO3_PACKAGES_DAT_HEADER)) >= pbPackagesEnd)
+ return NULL;
+ pbPackagesDat += sizeof(DIABLO3_PACKAGES_DAT_HEADER);
+
+ // Check the signature and name count
+ if(pDatHeader->Signature != DIABLO3_PACKAGES_SIGNATURE)
+ return NULL;
+
+ // Create the map for fast search of the file name
+ pPackageMap = Map_Create(pDatHeader->NumberOfNames, KEY_LENGTH_STRING, 0);
+ if(pPackageMap != NULL)
+ {
+ char * szFileName = (char *)pbPackagesDat;
+
+ // Go as long as there is something
+ for(DWORD i = 0; i < pDatHeader->NumberOfNames; i++)
+ {
+ // Get the file extension
+ if((LPBYTE)szFileName >= pbPackagesEnd)
+ break;
+
+ // Insert the file name to the map. The file extension is not included
+ Map_InsertString(pPackageMap, szFileName, true);
+ szFileName = szFileName + strlen(szFileName) + 1;
+ }
+ }
+
+ return pPackageMap;
+}
+
+// Insert an entry with file name as-is
+static int InsertFileEntry(
+ TRootHandler_Diablo3 * pRootHandler,
+ ENCODING_KEY & EncodingKey,
+ const char * szFileName,
+ size_t cchFileName)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+
+ // We must not allow the file name array to be reallocated.
+ // Reallocating the array would cause pointers in TRootHandler_Diablo3::pRootMap
+ // become invalid
+ if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
+ {
+ assert(false);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Insert the plain name to the root handler's global name list
+ szFileName = (const char *)Array_Insert(&pRootHandler->FileNames, szFileName, cchFileName);
+ if(szFileName == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Make sure that we don't exceed the file limit at this phase
+ pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
+ assert(pFileEntry != NULL);
+
+ // Store the info into the file entry
+ pFileEntry->EncodingKey = EncodingKey;
+ pFileEntry->FileNameHash = CalcFileNameHash(szFileName);
+ pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szFileName);
+ pFileEntry->dwFlags = 0;
+
+ // Verify collisions (debug version only)
+ assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL);
+
+ // Calculate the file name hash
+ Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
+
+ // Success
+ return ERROR_SUCCESS;
+}
+
+static int ParseDirEntries_FileId1(
+ TRootHandler_Diablo3 * pRootHandler,
+ LPBYTE pbFileEntries,
+ DWORD dwFileEntries,
+ DWORD dwRootDirIndex)
+{
+ PDIABLO3_FILEID1_ENTRY pEntry = (PDIABLO3_FILEID1_ENTRY)pbFileEntries;
+ PCASC_FILE_ENTRY pFileEntry;
+
+ // Overflow test
+ if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax)
+ {
+ assert(false);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Parse the all ID1 entries in the file
+ for(DWORD i = 0; i < dwFileEntries; i++, pEntry++)
+ {
+ // Insert the file entry to the global list
+ pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
+ assert(pFileEntry != NULL);
+
+ // Fill the index entry
+ pFileEntry->EncodingKey = pEntry->EncodingKey;
+ pFileEntry->FileNameHash = MAKE_INDEX64(dwRootDirIndex, pEntry->FileIndex, 0);
+ pFileEntry->dwFlags = CASC_ENTRY_SHORT_NAME;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int ParseDirEntries_FileId2(
+ TRootHandler_Diablo3 * pRootHandler,
+ LPBYTE pbFileEntries,
+ DWORD dwFileEntries,
+ DWORD dwRootDirIndex)
+{
+ PDIABLO3_FILEID2_ENTRY pEntry = (PDIABLO3_FILEID2_ENTRY)pbFileEntries;
+ PCASC_FILE_ENTRY pFileEntry;
+
+ // Overflow test
+ if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax)
+ {
+ assert(false);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // Parse the all ID1 entries in the file
+ for(DWORD i = 0; i < dwFileEntries; i++, pEntry++)
+ {
+ // Insert the file entry to the global list
+ pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
+ assert(pFileEntry != NULL);
+
+ // Fill the index entry
+ pFileEntry->EncodingKey = pEntry->EncodingKey;
+ pFileEntry->FileNameHash = MAKE_INDEX64(dwRootDirIndex, pEntry->FileIndex, pEntry->SubIndex);
+ pFileEntry->dwFlags = CASC_ENTRY_SHORT_NAME | CASC_ENTRY_HAS_SUBINDEX;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int ParseDirEntries_Named(
+ TRootHandler_Diablo3 * pRootHandler,
+ LPBYTE pbFileEntries,
+ LPBYTE pbFileEnd,
+ DWORD dwFileEntries,
+ DWORD dwRootDirIndex)
+{
+ char szFileName[MAX_PATH+1];
+ char * szNamePtr = szFileName;
+ DWORD cbFileEntry;
+ int nError = ERROR_SUCCESS;
+
+ // Overflow test
+ if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax)
+ {
+ assert(false);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ // If we the file is not in the root directory itself,
+ // prepare the prefix for the root directory.
+ if(dwRootDirIndex != DIABLO3_INVALID_INDEX)
+ {
+ PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootDirIndex);
+ const char * szRootName = (const char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName);
+
+ // Copy the root directory name
+ while(szRootName[0] != 0)
+ *szNamePtr++ = *szRootName++;
+
+ // Append the backslash
+ *szNamePtr++ = '\\';
+ }
+
+ // Parse the file entry
+ while(pbFileEntries < pbFileEnd)
+ {
+ PDIABLO3_NAMED_ENTRY pNamedEntry = (PDIABLO3_NAMED_ENTRY)pbFileEntries;
+ DWORD cchFileName;
+
+ // Verify the named entry whether it does not go beyond the EOF
+ cbFileEntry = VerifyNamedFileEntry(pbFileEntries, pbFileEnd);
+ if(cbFileEntry == 0)
+ return ERROR_FILE_CORRUPT;
+
+ // Append the file name to the prepared file name
+ // This way we obtain the full name and the name lookup
+ // will be fully operational
+ memcpy(szNamePtr, pNamedEntry->szFileName, (cbFileEntry - sizeof(ENCODING_KEY)));
+ cchFileName = (DWORD)((szNamePtr - szFileName) + (cbFileEntry - sizeof(ENCODING_KEY)));
+
+ // Insert the named entry to the global file table
+ nError = InsertFileEntry(pRootHandler,
+ pNamedEntry->EncodingKey,
+ szFileName,
+ cchFileName);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Move the pointer to the next entry
+ pbFileEntries += cbFileEntry;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static void ResolveFullFileNames(
+ TRootHandler_Diablo3 * pRootHandler,
+ PDIABLO3_CORE_TOC_ENTRY pCoreTocEntries,
+ PCASC_MAP pPackageMap,
+ LPBYTE pbCoreTocFile,
+ DWORD dwFileIndexes)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+ char * szPlainName;
+ char * szNamePtr;
+ size_t nLength;
+ DWORD dwRootIndex;
+ DWORD dwFileIndex;
+ DWORD dwSubIndex;
+ char szShortName[MAX_PATH+1];
+ char szFullName[MAX_PATH+1];
+
+ // Parse the entire file table
+ for(size_t i = 0; i < pRootHandler->FileTable.ItemCount; i++)
+ {
+ // Retrieve the file entry at n-th position
+ pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i);
+
+ // Skip the items that already have full name
+ if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME)
+ {
+ // Retrieve the file index of that file
+ dwRootIndex = INDEX64_ROOT_INDEX(pFileEntry->FileNameHash);
+ dwFileIndex = INDEX64_FILE_INDEX(pFileEntry->FileNameHash);
+ dwSubIndex = (pFileEntry->dwFlags & CASC_ENTRY_HAS_SUBINDEX) ? INDEX64_SUB_INDEX(pFileEntry->FileNameHash) : DIABLO3_INVALID_INDEX;
+ assert(dwFileIndex < dwFileIndexes);
+
+ // Get the plain name of the file
+ szPlainName = (char *)(pbCoreTocFile + pCoreTocEntries[dwFileIndex].NameOffset);
+
+ // Create the short file name
+ nLength = CreateShortName(pPackageMap,
+ dwRootIndex,
+ pCoreTocEntries[dwFileIndex].AssetIndex,
+ szPlainName,
+ dwSubIndex,
+ szShortName);
+
+ // Insert the short name to the list of the names
+ szNamePtr = (char *)Array_Insert(&pRootHandler->FileNames, szShortName, nLength + 1);
+ pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szNamePtr);
+
+ // Create the full file name
+ nLength = CreateFileName(pRootHandler, szShortName, szFullName);
+ pFileEntry->FileNameHash = CalcFileNameHash(szFullName);
+
+ // Insert the entry to the name map. Use the mapping of FullName -> FileHash
+ Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
+ }
+ }
+}
+
+static LPBYTE LoadFileToMemory(TCascStorage * hs, LPBYTE pbEncodingKey, DWORD * pcbFileData)
+{
+ QUERY_KEY EncodingKey;
+ LPBYTE pbFileData = NULL;
+ HANDLE hFile;
+ DWORD cbBytesRead = 0;
+ DWORD cbFileData = 0;
+
+ // Open the file by encoding key
+ EncodingKey.pbData = pbEncodingKey;
+ EncodingKey.cbData = MD5_HASH_SIZE;
+ if(CascOpenFileByEncodingKey((HANDLE)hs, &EncodingKey, 0, &hFile))
+ {
+ // Retrieve the file size
+ cbFileData = CascGetFileSize(hFile, NULL);
+ if(cbFileData > 0)
+ {
+ pbFileData = CASC_ALLOC(BYTE, cbFileData);
+ if(pbFileData != NULL)
+ {
+ CascReadFile(hFile, pbFileData, cbFileData, &cbBytesRead);
+ }
+ }
+
+ // Close the file
+ CascCloseFile(hFile);
+ }
+
+ // Give the file to the caller
+ if(pcbFileData != NULL)
+ pcbFileData[0] = cbBytesRead;
+ return pbFileData;
+}
+
+static LPBYTE LoadFileToMemory(TCascStorage * hs, const char * szFileName, DWORD * pcbFileData)
+{
+ LPBYTE pbEncodingKey = NULL;
+ LPBYTE pbFileData = NULL;
+
+ // Try to find encoding key for the file
+ pbEncodingKey = RootHandler_GetKey(hs->pRootHandler, szFileName);
+ if(pbEncodingKey != NULL)
+ pbFileData = LoadFileToMemory(hs, pbEncodingKey, pcbFileData);
+
+ return pbFileData;
+}
+
+static int ParseDirectoryHeader(
+ PDIABLO3_DIR_HEADER pDirHeader,
+ LPBYTE pbDirFile,
+ LPBYTE pbFileEnd)
+{
+ DWORD dwSignature = 0;
+
+ //
+ // Structure of a Diablo3 directory file
+ // 1) Signature (4 bytes)
+ // 2) Number of DIABLO3_FILEID1_ENTRY entries (4 bytes)
+ // 3) Array of DIABLO3_FILEID1_ENTRY entries
+ // 4) Number of DIABLO3_FILEID2_ENTRY entries (4 bytes)
+ // 5) Array of DIABLO3_FILEID2_ENTRY entries
+ // 6) Number of DIABLO3_NAMED_ENTRY entries (4 bytes)
+ // 7) Array of DIABLO3_NAMED_ENTRY entries
+ //
+
+ // Prepare the header signature
+ memset(pDirHeader, 0, sizeof(DIABLO3_DIR_HEADER));
+
+ // Get the signature
+ if((pbDirFile + sizeof(DWORD)) >= pbFileEnd)
+ return ERROR_BAD_FORMAT;
+ dwSignature = *(PDWORD)pbDirFile;
+
+ // Check the signature
+ if(dwSignature != CASC_DIABLO3_ROOT_SIGNATURE && dwSignature != DIABLO3_SUBDIR_SIGNATURE)
+ return ERROR_BAD_FORMAT;
+ pbDirFile += sizeof(DWORD);
+
+ // Subdirectories have extra two arrays
+ if(dwSignature == DIABLO3_SUBDIR_SIGNATURE)
+ {
+ // Get the number of DIABLO3_FILEID1_ENTRY items
+ if((pbDirFile + sizeof(DWORD)) >= pbFileEnd)
+ return ERROR_BAD_FORMAT;
+ pDirHeader->dwEntries1 = *(PDWORD)pbDirFile;
+
+ // Get the array of DIABLO3_FILEID1_ENTRY
+ pDirHeader->pbEntries1 = (pbDirFile + sizeof(DWORD));
+ pbDirFile = pbDirFile + sizeof(DWORD) + pDirHeader->dwEntries1 * sizeof(DIABLO3_FILEID1_ENTRY);
+
+ // Get the number of DIABLO3_FILEID2_ENTRY items
+ if((pbDirFile + sizeof(DWORD)) >= pbFileEnd)
+ return ERROR_BAD_FORMAT;
+ pDirHeader->dwEntries2 = *(PDWORD)pbDirFile;
+
+ // Get the array of DIABLO3_FILEID2_ENTRY
+ pDirHeader->pbEntries2 = (pbDirFile + sizeof(DWORD));
+ pbDirFile = pbDirFile + sizeof(DWORD) + pDirHeader->dwEntries2 * sizeof(DIABLO3_FILEID2_ENTRY);
+ }
+
+ // Get the pointer and length DIABLO3_NAMED_ENTRY array
+ if((pbDirFile + sizeof(DWORD)) >= pbFileEnd)
+ return ERROR_BAD_FORMAT;
+ pDirHeader->dwEntries3 = *(PDWORD)pbDirFile;
+ pDirHeader->pbEntries3 = (pbDirFile + sizeof(DWORD));
+ return ERROR_SUCCESS;
+}
+
+static DWORD ScanDirectoryFile(
+ TCascStorage * hs,
+ LPBYTE pbRootFile,
+ LPBYTE pbFileEnd)
+{
+ PDIABLO3_NAMED_ENTRY pNamedEntry;
+ DIABLO3_DIR_HEADER RootHeader;
+ DIABLO3_DIR_HEADER DirHeader;
+ LPBYTE pbSubDir;
+ DWORD dwTotalFileCount;
+ DWORD cbNamedEntry;
+ DWORD cbSubDir;
+ int nError;
+
+ // Parse the directory header in order to retrieve the items
+ nError = ParseDirectoryHeader(&RootHeader, pbRootFile, pbFileEnd);
+ if(nError != ERROR_SUCCESS)
+ return 0;
+
+ // Add the root directory's entries
+ dwTotalFileCount = RootHeader.dwEntries1 + RootHeader.dwEntries2 + RootHeader.dwEntries3;
+
+ // Parse the named entries
+ for(DWORD i = 0; i < RootHeader.dwEntries3; i++)
+ {
+ // Get the this named entry
+ if((cbNamedEntry = VerifyNamedFileEntry(RootHeader.pbEntries3, pbFileEnd)) == 0)
+ return 0;
+ pNamedEntry = (PDIABLO3_NAMED_ENTRY)RootHeader.pbEntries3;
+ RootHeader.pbEntries3 += cbNamedEntry;
+
+ // Load the subdirectory to memory
+ pbSubDir = LoadFileToMemory(hs, pNamedEntry->EncodingKey.Value, &cbSubDir);
+ if(pbSubDir != NULL)
+ {
+ // Count the files in the subdirectory
+ if(ParseDirectoryHeader(&DirHeader, pbSubDir, pbSubDir + cbSubDir) == ERROR_SUCCESS)
+ {
+ dwTotalFileCount += DirHeader.dwEntries1 + DirHeader.dwEntries2 + DirHeader.dwEntries3;
+ }
+
+ // Free the subdirectory
+ CASC_FREE(pbSubDir);
+ }
+ }
+
+ // Return the total number of entries
+ return dwTotalFileCount;
+}
+
+static int ParseDirectoryFile(
+ TRootHandler_Diablo3 * pRootHandler,
+ LPBYTE pbDirFile,
+ LPBYTE pbFileEnd,
+ DWORD dwRootDirIndex)
+{
+ DIABLO3_DIR_HEADER DirHeader;
+ int nError;
+
+ // Sanity checks
+ assert(pRootHandler->FileTable.ItemArray != NULL);
+ assert(pRootHandler->FileTable.ItemCount < pRootHandler->FileTable.ItemCountMax);
+
+ // Parse the directory header in order to retrieve the items
+ nError = ParseDirectoryHeader(&DirHeader, pbDirFile, pbFileEnd);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Process all DIABLO3_FILEID1_ENTRY entries. These are for files
+ // belonging to an asset group, without subitem number.
+ // Example: "SoundBank\SoundFile.smp"
+ // We skip inserting them to the name map, because the names are not known yet
+ if(DirHeader.pbEntries1 && DirHeader.dwEntries1)
+ {
+ assert(dwRootDirIndex != DIABLO3_INVALID_INDEX);
+ nError = ParseDirEntries_FileId1(pRootHandler, DirHeader.pbEntries1, DirHeader.dwEntries1, dwRootDirIndex);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+ // Parse all DIABLO3_FILEID2_ENTRY entries. These are for files
+ // belonging to an asset group, with a subitem number.
+ // Example: "SoundBank\SoundFile\0001.smp"
+ // We skip inserting them to the name map, because the names are not known yet
+ if(DirHeader.pbEntries2 && DirHeader.dwEntries2)
+ {
+ assert(dwRootDirIndex != DIABLO3_INVALID_INDEX);
+ nError = ParseDirEntries_FileId2(pRootHandler, DirHeader.pbEntries2, DirHeader.dwEntries2, dwRootDirIndex);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+
+ // Parse all named entries. These are for files with arbitrary names,
+ // and they do not belong to an asset.
+ if(DirHeader.pbEntries3 && DirHeader.dwEntries3)
+ {
+ nError = ParseDirEntries_Named(pRootHandler, DirHeader.pbEntries3, pbFileEnd, DirHeader.dwEntries3, dwRootDirIndex);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+ }
+
+ // Give the directory to the caller
+ return nError;
+}
+
+static int ParseCoreTOC(
+ TRootHandler_Diablo3 * pRootHandler,
+ PCASC_MAP pPackageMap,
+ LPBYTE pbCoreTocFile,
+ LPBYTE pbCoreTocEnd)
+{
+ PDIABLO3_CORE_TOC_HEADER pTocHeader;
+ PDIABLO3_CORE_TOC_ENTRY pSortedEntries;
+ PDIABLO3_CORE_TOC_ENTRY pTocEntry;
+ LPBYTE pbCoreTocNames;
+ DWORD dwFileIndexes = 0;
+ DWORD i;
+
+ // Check the space for header
+ if((pbCoreTocFile + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbCoreTocEnd)
+ return ERROR_FILE_CORRUPT;
+ pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbCoreTocFile;
+ pbCoreTocFile += sizeof(DIABLO3_CORE_TOC_HEADER);
+
+ // Calculate space needed for allocation
+ for(i = 0; i < DIABLO3_MAX_ASSETS; i++)
+ {
+ // Get the first entry
+ pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]);
+
+ // Find out the entry with the maximum index
+ for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++)
+ {
+ if(pTocEntry->FileIndex > dwFileIndexes)
+ dwFileIndexes = pTocEntry->FileIndex + 1;
+ pTocEntry++;
+ }
+ }
+
+ // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs
+ pSortedEntries = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwFileIndexes);
+ if(pSortedEntries != NULL)
+ {
+ // Initialize all entries to invalid
+ memset(pSortedEntries, 0xFF, dwFileIndexes * sizeof(DIABLO3_CORE_TOC_ENTRY));
+
+ // Populate the linear array with the entries
+ for(i = 0; i < DIABLO3_MAX_ASSETS; i++)
+ {
+ // Set the pointers
+ pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]);
+ pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]);
+
+ // Setup the entries
+ for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++)
+ {
+ pSortedEntries[pTocEntry->FileIndex].AssetIndex = pTocEntry->AssetIndex;
+ pSortedEntries[pTocEntry->FileIndex].FileIndex = pTocEntry->FileIndex;
+ pSortedEntries[pTocEntry->FileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocFile) + pTocEntry->NameOffset;
+ pTocEntry++;
+ }
+ }
+
+ // Now use the linear array to resolve the asset indexes and plain names
+ ResolveFullFileNames(pRootHandler, pSortedEntries, pPackageMap, pbCoreTocFile, dwFileIndexes);
+ CASC_FREE(pSortedEntries);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Implementation of Diablo III root file
+
+static int D3Handler_Insert(TRootHandler_Diablo3 * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey)
+{
+ ENCODING_KEY EncodingKey;
+ DWORD dwFileIndex;
+
+ // Don't let the number of items to overflow
+ if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Insert the item
+ EncodingKey = *(PENCODING_KEY)pbEncodingKey;
+ dwFileIndex = InsertFileEntry(pRootHandler,
+ EncodingKey,
+ szFileName,
+ strlen(szFileName) + 1);
+ return (dwFileIndex != INVALID_FILE_INDEX) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY;
+}
+
+static LPBYTE D3Handler_Search(TRootHandler_Diablo3 * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+ const char * szSrcName = NULL;
+
+ // Are we still inside the root directory range?
+ while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount)
+ {
+ // Get the n-th directory and the file name
+ pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1);
+ szSrcName = (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName);
+
+ // This is either a full file name or an abbreviated name
+ if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME)
+ {
+ CreateFileName(pRootHandler, szSrcName, pSearch->szFileName);
+ }
+ else
+ {
+ strcpy(pSearch->szFileName, szSrcName);
+ }
+
+ // Prepare for the next search
+ pSearch->IndexLevel1++;
+ return pFileEntry->EncodingKey.Value;
+ }
+
+ // No more entries
+ return NULL;
+}
+
+static void D3Handler_EndSearch(TRootHandler_Diablo3 * /* pRootHandler */, TCascSearch * /* pSearch */)
+{
+ // Do nothing
+}
+
+static LPBYTE D3Handler_GetKey(TRootHandler_Diablo3 * pRootHandler, const char * szFileName)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+ ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
+
+ // Find the file in the name table
+ pFileEntry = (PCASC_FILE_ENTRY)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL);
+ return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL;
+}
+
+static void D3Handler_Close(TRootHandler_Diablo3 * pRootHandler)
+{
+ if(pRootHandler != NULL)
+ {
+ // Free the file map
+ Map_Free(pRootHandler->pRootMap);
+
+ // Free the array of the file entries and file names
+ Array_Free(&pRootHandler->FileTable);
+ Array_Free(&pRootHandler->FileNames);
+
+ // Free the root file itself
+ CASC_FREE(pRootHandler);
+ }
+}
+
+/*
+static void DumpRootFile(TDumpContext * dc, LPBYTE pbFileData, LPBYTE pbFileDataEnd)
+{
+ char szMD5Buffer[MD5_STRING_SIZE+1];
+ DWORD dwSignature;
+ DWORD dwItemCount;
+ DWORD i;
+
+ dwSignature = *(PDWORD)pbFileData;
+ if(dwSignature != CASC_DIABLO3_SUBDIR_SIGNATURE)
+ return;
+ pbFileData += sizeof(DWORD);
+
+ // Dump items that contain EncodingKey + AssetId
+ dwItemCount = *(PDWORD)pbFileData;
+ pbFileData += sizeof(DWORD);
+ for(i = 0; i < dwItemCount; i++)
+ {
+ PCASC_DIABLO3_ASSET_ENTRY pEntry = (PCASC_DIABLO3_ASSET_ENTRY)pbFileData;
+
+ if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd)
+ return;
+ pbFileData += sizeof(*pEntry);
+
+ dump_print(dc, "%s %08X\n", StringFromMD5(pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId);
+ }
+
+ // Terminate with two newlines
+ dump_print(dc, "\n");
+
+ // Dump items that contain EncodingKey + AssetId + FileNumber
+ dwItemCount = *(PDWORD)pbFileData;
+ pbFileData += sizeof(DWORD);
+ for(i = 0; i < dwItemCount; i++)
+ {
+ PCASC_DIABLO3_ASSET_ENTRY2 pEntry = (PCASC_DIABLO3_ASSET_ENTRY2)pbFileData;
+
+ if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd)
+ return;
+ pbFileData += sizeof(*pEntry);
+
+ dump_print(dc, "%s %08X %08X\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId, pEntry->FileNumber);
+ }
+
+ // Terminate with two newlines
+ dump_print(dc, "\n");
+
+ // Dump items that contain EncodingKey + FileName
+ dwItemCount = *(PDWORD)pbFileData;
+ pbFileData += sizeof(DWORD);
+ for(i = 0; i < dwItemCount; i++)
+ {
+ PDIABLO3_NAMED_ENTRY pEntry = (PDIABLO3_NAMED_ENTRY)pbFileData;
+ DWORD dwEntrySize = VerifyNamedFileEntry(pbFileData, pbFileDataEnd);
+
+ if((pbFileData + dwEntrySize) > pbFileDataEnd)
+ return;
+ pbFileData += dwEntrySize;
+
+ dump_print(dc, "%s %s\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->szFileName);
+ }
+
+ dump_print(dc, "\n\n");
+}
+*/
+//-----------------------------------------------------------------------------
+// Public functions
+
+int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
+{
+ TRootHandler_Diablo3 * pRootHandler;
+ PCASC_MAP pPackageMap = NULL;
+ LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
+ LPBYTE pbPackagesDat = NULL;
+ DWORD dwTotalFileCount;
+ DWORD cbPackagesDat = 0;
+ int nError;
+
+ // Allocate the root handler object
+ hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Diablo3, 1);
+ if(pRootHandler == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill-in the handler functions
+ memset(pRootHandler, 0, sizeof(TRootHandler_Diablo3));
+ pRootHandler->Insert = (ROOT_INSERT)D3Handler_Insert;
+ pRootHandler->Search = (ROOT_SEARCH)D3Handler_Search;
+ pRootHandler->EndSearch = (ROOT_ENDSEARCH)D3Handler_EndSearch;
+ pRootHandler->GetKey = (ROOT_GETKEY)D3Handler_GetKey;
+ pRootHandler->Close = (ROOT_CLOSE)D3Handler_Close;
+
+ // Fill-in the flags
+ pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES;
+
+ // Scan the total number of files in the root directories
+ // Reserve space for extra files
+ dwTotalFileCount = ScanDirectoryFile(hs, pbRootFile, pbRootFileEnd);
+ if(dwTotalFileCount == 0)
+ return ERROR_FILE_CORRUPT;
+ dwTotalFileCount += CASC_EXTRA_FILES;
+
+ // Allocate the global linear file table
+ // Note: This is about 18 MB of memory for Diablo III PTR build 30013
+ nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, dwTotalFileCount);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Allocate global buffer for file names.
+ // The size of the buffer was taken from Diablo III build 30013
+ nError = Array_Create(&pRootHandler->FileNames, char, 0x01000000);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Create map of ROOT_ENTRY -> FileEntry
+ pRootHandler->pRootMap = Map_Create(dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash));
+ if(pRootHandler->pRootMap == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Parse the ROOT file and insert all entries in the file table
+ nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFileEnd, DIABLO3_INVALID_INDEX);
+ if(nError == ERROR_SUCCESS)
+ {
+ size_t dwRootEntries = pRootHandler->FileTable.ItemCount;
+
+ // We expect the number of level-0 to be less than maximum
+ assert(dwRootEntries < DIABLO3_MAX_SUBDIRS);
+
+ // Now parse the all root items and load them
+ for(size_t i = 0; i < dwRootEntries; i++)
+ {
+ PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i);
+
+ // Load the entire file to memory
+ pbRootFile = LoadFileToMemory(hs, pRootEntry->EncodingKey.Value, &cbRootFile);
+ if(pbRootFile != NULL)
+ {
+ nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFile + cbRootFile, i);
+ CASC_FREE(pbRootFile);
+ }
+ }
+ }
+
+ // Note: The file "Base\Data_D3\PC\Misc\Packages.dat" contains the names
+ // of the files (without level-0 and level-1 directory). We can use these
+ // names for supplying the missing extensions
+ if(nError == ERROR_SUCCESS)
+ {
+ // Load the entire file to memory
+ pbPackagesDat = LoadFileToMemory(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat", &cbPackagesDat);
+ if(pbPackagesDat != NULL)
+ {
+ pPackageMap = CreatePackageMap(pbPackagesDat, pbPackagesDat + cbPackagesDat);
+ }
+ }
+
+ // Vast majorify of files at this moment don't have names.
+ // We can load the Base\CoreTOC.dat file in order
+ // to get directory asset indexes, file names and extensions
+ if(nError == ERROR_SUCCESS)
+ {
+ LPBYTE pbCoreTOC;
+ DWORD cbCoreTOC = 0;
+
+ // Load the entire file to memory
+ pbCoreTOC = LoadFileToMemory(hs, "Base\\CoreTOC.dat", &cbCoreTOC);
+ if(pbCoreTOC != NULL)
+ {
+ ParseCoreTOC(pRootHandler, pPackageMap, pbCoreTOC, pbCoreTOC + cbCoreTOC);
+ CASC_FREE(pbCoreTOC);
+ }
+ }
+
+ // Free the packages map
+ if(pPackageMap != NULL)
+ Map_Free(pPackageMap);
+ if(pbPackagesDat != NULL)
+ CASC_FREE(pbPackagesDat);
+ return nError;
+}
diff --git a/dep/CascLib/src/CascMndxRoot.cpp b/dep/CascLib/src/CascRootFile_Mndx.cpp
index 328afee8ba5..bf17290a0af 100644
--- a/dep/CascLib/src/CascMndxRoot.cpp
+++ b/dep/CascLib/src/CascRootFile_Mndx.cpp
@@ -12,20 +12,20 @@
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
-#include "CascMndxRoot.h"
+#include "CascMndx.h"
//-----------------------------------------------------------------------------
// Local defines
-#define CASC_MAR_SIGNATURE 0x0052414d // 'MAR\0'
+#define CASC_MAR_SIGNATURE 0x0052414d // 'MAR\0'
//-----------------------------------------------------------------------------
// Local structures
typedef struct _FILE_MNDX_HEADER
{
- DWORD Signature; // 'MNDX'
- DWORD HeaderVersion; // Must be <= 2
+ DWORD Signature; // 'MNDX'
+ DWORD HeaderVersion; // Must be <= 2
DWORD FormatVersion;
} FILE_MNDX_HEADER, *PFILE_MNDX_HEADER;
@@ -39,6 +39,57 @@ typedef struct _FILE_MAR_INFO
DWORD MarDataOffsetHi;
} FILE_MAR_INFO, *PFILE_MAR_INFO;
+typedef struct _CASC_MNDX_INFO
+{
+ BYTE RootFileName[MD5_HASH_SIZE]; // Name (aka MD5) of the root file
+ DWORD HeaderVersion; // Must be <= 2
+ DWORD FormatVersion;
+ DWORD field_1C;
+ DWORD field_20;
+ DWORD MarInfoOffset; // Offset of the first MAR entry info
+ DWORD MarInfoCount; // Number of the MAR info entries
+ DWORD MarInfoSize; // Size of the MAR info entry
+ DWORD MndxEntriesOffset;
+ DWORD MndxEntriesTotal; // Total number of MNDX root entries
+ DWORD MndxEntriesValid; // Number of valid MNDX root entries
+ DWORD MndxEntrySize; // Size of one MNDX root entry
+ struct _MAR_FILE * pMarFile1; // File name list for the packages
+ struct _MAR_FILE * pMarFile2; // File name list for names stripped of package names
+ struct _MAR_FILE * pMarFile3; // File name list for complete names
+// PCASC_ROOT_ENTRY_MNDX pMndxEntries;
+// PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
+ bool bRootFileLoaded; // true if the root info file was properly loaded
+
+} CASC_MNDX_INFO, *PCASC_MNDX_INFO;
+
+typedef struct _CASC_MNDX_PACKAGE
+{
+ char * szFileName; // Pointer to file name
+ size_t nLength; // Length of the file name
+
+} CASC_MNDX_PACKAGE, *PCASC_MNDX_PACKAGE;
+
+typedef struct _CASC_MNDX_PACKAGES
+{
+ char * szNameBuffer; // Pointer to the buffer for file names
+ size_t NameEntries; // Number of name entries in Names
+ size_t NameBufferUsed; // Number of bytes used in the name buffer
+ size_t NameBufferMax; // Total size of the name buffer
+
+ CASC_MNDX_PACKAGE Packages[1]; // List of packages
+
+} CASC_MNDX_PACKAGES, *PCASC_MNDX_PACKAGES;
+
+// Root file entry for CASC storages with MNDX root file (Heroes of the Storm)
+// Corresponds to the in-file structure
+typedef struct _CASC_ROOT_ENTRY_MNDX
+{
+ DWORD Flags; // High 8 bits: Flags, low 24 bits: package index
+ BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file
+ DWORD FileSize; // Uncompressed file size, in bytes
+
+} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX;
+
//-----------------------------------------------------------------------------
// Testing functions prototypes
@@ -2734,14 +2785,14 @@ static void MAR_FILE_Destructor(PMAR_FILE pMarFile)
#define CASC_PACKAGES_INIT 0x10
#define CASC_PACKAGES_DELTA 0x10
-static PCASC_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMax)
+static PCASC_MNDX_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMax)
{
- PCASC_PACKAGES pPackages;
+ PCASC_MNDX_PACKAGES pPackages;
size_t cbToAllocate;
// Allocate space
- cbToAllocate = sizeof(CASC_PACKAGES) + (nNameEntries * sizeof(CASC_PACKAGE)) + nNameBufferMax;
- pPackages = (PCASC_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
+ cbToAllocate = sizeof(CASC_MNDX_PACKAGES) + (nNameEntries * sizeof(CASC_MNDX_PACKAGE)) + nNameBufferMax;
+ pPackages = (PCASC_MNDX_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
if(pPackages != NULL)
{
// Fill the structure
@@ -2757,8 +2808,8 @@ static PCASC_PACKAGES AllocatePackages(size_t nNameEntries, size_t nNameBufferMa
return pPackages;
}
-static PCASC_PACKAGES InsertToPackageList(
- PCASC_PACKAGES pPackages,
+static PCASC_MNDX_PACKAGES InsertToPackageList(
+ PCASC_MNDX_PACKAGES pPackages,
const char * szFileName,
size_t cchFileName,
size_t nPackageIndex)
@@ -2777,11 +2828,11 @@ static PCASC_PACKAGES InsertToPackageList(
// If any of the two variables overflowed, we need to reallocate the name list
if(nNewNameEntries > pPackages->NameEntries || nNewNameBufferMax > pPackages->NameBufferMax)
{
- PCASC_PACKAGES pOldPackages = pPackages;
+ PCASC_MNDX_PACKAGES pOldPackages = pPackages;
// Allocate new name list
- cbToAllocate = sizeof(CASC_PACKAGES) + (nNewNameEntries * sizeof(CASC_PACKAGE)) + nNewNameBufferMax;
- pPackages = (PCASC_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
+ cbToAllocate = sizeof(CASC_MNDX_PACKAGES) + (nNewNameEntries * sizeof(CASC_MNDX_PACKAGE)) + nNewNameBufferMax;
+ pPackages = (PCASC_MNDX_PACKAGES)CASC_ALLOC(BYTE, cbToAllocate);
if(pPackages == NULL)
return NULL;
@@ -2822,17 +2873,17 @@ static PCASC_PACKAGES InsertToPackageList(
return pPackages;
}
-static int LoadPackageNames(TCascStorage * hs)
+static int LoadPackageNames(PCASC_MNDX_INFO pMndxInfo, PCASC_MNDX_PACKAGES * ppPackages)
{
TMndxFindResult Struct1C;
- PCASC_PACKAGES pPackages = NULL;
+ PCASC_MNDX_PACKAGES pPackages = NULL;
PMAR_FILE pMarFile;
// Sanity checks
- assert(hs->pMndxInfo != NULL);
+ assert(pMndxInfo != NULL);
// Prepare the file name search in the top level directory
- pMarFile = hs->pMndxInfo->pMarFile1;
+ pMarFile = pMndxInfo->pMarFile1;
Struct1C.SetSearchPath("", 0);
// Allocate initial name list structure
@@ -2856,21 +2907,34 @@ static int LoadPackageNames(TCascStorage * hs)
return ERROR_NOT_ENOUGH_MEMORY;
}
- // Set the name list to the CASC storage structure
- hs->pPackages = pPackages;
+ // Give the packages to the caller
+ if(ppPackages != NULL)
+ ppPackages[0] = pPackages;
return ERROR_SUCCESS;
}
-PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
+//-----------------------------------------------------------------------------
+// Implementation of root file functions
+
+struct TRootHandler_MNDX : public TRootHandler
{
- PCASC_PACKAGE pMatching = NULL;
- PCASC_PACKAGE pPackage;
+ CASC_MNDX_INFO MndxInfo;
+
+ PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
+ PCASC_ROOT_ENTRY_MNDX pMndxEntries;
+ PCASC_MNDX_PACKAGES pPackages; // Linear list of present packages
+};
+
+PCASC_MNDX_PACKAGE FindMndxPackage(TRootHandler_MNDX * pRootHandler, const char * szFileName)
+{
+ PCASC_MNDX_PACKAGE pMatching = NULL;
+ PCASC_MNDX_PACKAGE pPackage;
size_t nMaxLength = 0;
size_t nLength = strlen(szFileName);
// Packages must be loaded
- assert(hs->pPackages != NULL);
- pPackage = hs->pPackages->Packages;
+ assert(pRootHandler->pPackages != NULL);
+ pPackage = pRootHandler->pPackages->Packages;
//FILE * fp = fopen("E:\\packages.txt", "wt");
//for(size_t i = 0; i < hs->pPackages->NameEntries; i++, pPackage++)
@@ -2881,7 +2945,7 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
//fclose(fp);
// Find the longest matching name
- for(size_t i = 0; i < hs->pPackages->NameEntries; i++, pPackage++)
+ for(size_t i = 0; i < pRootHandler->pPackages->NameEntries; i++, pPackage++)
{
if(pPackage->szFileName != NULL && pPackage->nLength < nLength && pPackage->nLength > nMaxLength)
{
@@ -2898,11 +2962,49 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
return pMatching;
}
-static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndxFindResult * pStruct1C)
+int SearchMndxInfo(TRootHandler_MNDX * pRootHandler, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry)
+{
+ PCASC_ROOT_ENTRY_MNDX pRootEntry;
+ PCASC_MNDX_INFO pMndxInfo = &pRootHandler->MndxInfo;
+ TMndxFindResult Struct1C;
+
+ // Search the database for the file name
+ if(pMndxInfo->bRootFileLoaded)
+ {
+ Struct1C.SetSearchPath(szFileName, strlen(szFileName));
+
+ // Search the file name in the second MAR info (the one with stripped package names)
+ if(MAR_FILE_SearchFile(pMndxInfo->pMarFile2, &Struct1C) != ERROR_SUCCESS)
+ return ERROR_FILE_NOT_FOUND;
+
+ // The found MNDX index must fall into range of valid MNDX entries
+ if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid)
+ {
+ // HOTS: E945F4
+ pRootEntry = pRootHandler->ppValidEntries[Struct1C.FileNameIndex];
+ while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage)
+ {
+ // The highest bit serves as a terminator if set
+ if(pRootEntry->Flags & 0x80000000)
+ return ERROR_FILE_NOT_FOUND;
+
+ pRootEntry++;
+ }
+
+ // Give the root entry pointer to the caller
+ if(ppRootEntry != NULL)
+ ppRootEntry[0] = pRootEntry;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ return ERROR_FILE_NOT_FOUND;
+}
+
+static LPBYTE FillFindData(TRootHandler_MNDX * pRootHandler, TCascSearch * pSearch, TMndxFindResult * pStruct1C, PDWORD PtrFileSize)
{
PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL;
- TCascStorage * hs = pSearch->hs;
- PCASC_PACKAGE pPackage;
+ PCASC_MNDX_PACKAGE pPackage;
char * szStrippedPtr;
char szStrippedName[MAX_PATH+1];
int nError;
@@ -2911,40 +3013,134 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx
assert(pStruct1C->cchFoundPath < MAX_PATH);
// Fill the file name
- memcpy(pFindData->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath);
- pFindData->szFileName[pStruct1C->cchFoundPath] = 0;
- pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
- pFindData->dwFileSize = CASC_INVALID_SIZE;
+ memcpy(pSearch->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath);
+ pSearch->szFileName[pStruct1C->cchFoundPath] = 0;
// Fill the file size
- pPackage = FindMndxPackage(hs, pFindData->szFileName);
- if(pPackage != NULL)
- {
- // Cut the package name off the full path
- szStrippedPtr = pFindData->szFileName + pPackage->nLength;
- while(szStrippedPtr[0] == '/')
- szStrippedPtr++;
+ pPackage = FindMndxPackage(pRootHandler, pSearch->szFileName);
+ if(pPackage == NULL)
+ return NULL;
- // We need to convert the stripped name to lowercase, replacing backslashes with slashes
- NormalizeFileName_LowerSlash(szStrippedName, szStrippedPtr, MAX_PATH);
+ // Cut the package name off the full path
+ szStrippedPtr = pSearch->szFileName + pPackage->nLength;
+ while(szStrippedPtr[0] == '/')
+ szStrippedPtr++;
- // Search the package
- nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntry);
- if(nError == ERROR_SUCCESS)
- {
- pFindData->dwFileSize = pRootEntry->FileSize;
- }
+ // We need to convert the stripped name to lowercase, replacing backslashes with slashes
+ NormalizeFileName_LowerSlash(szStrippedName, szStrippedPtr, MAX_PATH);
+
+ // Search the package
+ nError = SearchMndxInfo(pRootHandler, szStrippedName, (DWORD)(pPackage - pRootHandler->pPackages->Packages), &pRootEntry);
+ if(nError != ERROR_SUCCESS)
+ return NULL;
+
+ // Give the file size
+ if(PtrFileSize != NULL)
+ PtrFileSize[0] = pRootEntry->FileSize;
+ return pRootEntry->EncodingKey;
+}
+
+static int MndxHandler_Insert(TRootHandler_MNDX *, const char *, LPBYTE)
+{
+ return ERROR_NOT_SUPPORTED;
+}
+
+static LPBYTE MndxHandler_Search(TRootHandler_MNDX * pRootHandler, TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD /* PtrLocaleFlags */)
+{
+ TMndxFindResult * pStruct1C = NULL;
+ PCASC_MNDX_INFO pMndxInfo = &pRootHandler->MndxInfo;
+ PMAR_FILE pMarFile = pMndxInfo->pMarFile3;
+ bool bFindResult = false;
+
+ // If the first time, allocate the structure for the search result
+ if(pSearch->pRootContext == NULL)
+ {
+ // Create the new search structure
+ pStruct1C = new TMndxFindResult;
+ if(pStruct1C == NULL)
+ return NULL;
+
+ // Setup the search mask
+ pStruct1C->SetSearchPath("", 0);
+ pSearch->pRootContext = pStruct1C;
}
- return true;
+
+ // Make shortcut for the search structure
+ assert(pSearch->pRootContext != NULL);
+ pStruct1C = (TMndxFindResult *)pSearch->pRootContext;
+
+ // Search the next file name (our code)
+ pMarFile->pDatabasePtr->sub_1956CE0(pStruct1C, &bFindResult);
+ if(bFindResult == false)
+ return NULL;
+
+ // Give the file size and encoding key
+ return FillFindData(pRootHandler, pSearch, pStruct1C, PtrFileSize);
+}
+
+static void MndxHandler_EndSearch(TRootHandler_MNDX * /* pRootHandler */, TCascSearch * pSearch)
+{
+ if(pSearch != NULL)
+ delete (TMndxFindResult *)pSearch->pRootContext;
+ pSearch->pRootContext = NULL;
+}
+
+static LPBYTE MndxHandler_GetKey(TRootHandler_MNDX * pRootHandler, const char * szFileName)
+{
+ PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL;
+ PCASC_MNDX_PACKAGE pPackage;
+ char * szStrippedName;
+ char szNormName[MAX_PATH+1];
+ int nError;
+
+ // Convert the file name to lowercase + slashes
+ NormalizeFileName_LowerSlash(szNormName, szFileName, MAX_PATH);
+
+ // Find the package number
+ pPackage = FindMndxPackage(pRootHandler, szNormName);
+ if(pPackage == NULL)
+ return NULL;
+
+ // Cut the package name off the full path
+ szStrippedName = szNormName + pPackage->nLength;
+ while(szStrippedName[0] == '/')
+ szStrippedName++;
+
+ // Find the root entry
+ nError = SearchMndxInfo(pRootHandler, szStrippedName, (DWORD)(pPackage - pRootHandler->pPackages->Packages), &pRootEntry);
+ if(nError != ERROR_SUCCESS || pRootEntry == NULL)
+ return NULL;
+
+ // Return the encoding key
+ return pRootEntry->EncodingKey;
+}
+
+static void MndxHandler_Close(TRootHandler_MNDX * pRootHandler)
+{
+ if(pRootHandler->MndxInfo.pMarFile1 != NULL)
+ MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile1);
+ if(pRootHandler->MndxInfo.pMarFile2 != NULL)
+ MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile2);
+ if(pRootHandler->MndxInfo.pMarFile3 != NULL)
+ MAR_FILE_Destructor(pRootHandler->MndxInfo.pMarFile3);
+ if(pRootHandler->ppValidEntries != NULL)
+ CASC_FREE(pRootHandler->ppValidEntries);
+ if(pRootHandler->pMndxEntries != NULL)
+ CASC_FREE(pRootHandler->pMndxEntries);
+ if(pRootHandler->pPackages != NULL)
+ CASC_FREE(pRootHandler->pPackages);
+
+ CASC_FREE(pRootHandler);
}
//-----------------------------------------------------------------------------
// Public functions - MNDX info
-int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
+int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
PFILE_MNDX_HEADER pMndxHeader = (PFILE_MNDX_HEADER)pbRootFile;
PCASC_MNDX_INFO pMndxInfo;
+ TRootHandler_MNDX * pRootHandler;
FILE_MAR_INFO MarInfo;
PMAR_FILE pMarFile;
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
@@ -2957,13 +3153,24 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(pMndxHeader->Signature != CASC_MNDX_SIGNATURE || pMndxHeader->FormatVersion > 2 || pMndxHeader->FormatVersion < 1)
return ERROR_BAD_FORMAT;
- // Allocate space for the CASC_MNDX_INFO structure
- pMndxInfo = CASC_ALLOC(CASC_MNDX_INFO, 1);
- if(pMndxInfo == NULL)
+ // Allocate the structure for the MNDX root file
+ hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_MNDX, 1);
+ if(pRootHandler == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
+ // Fill-in the handler functions
+ memset(pRootHandler, 0, sizeof(TRootHandler_MNDX));
+ pRootHandler->Insert = (ROOT_INSERT)MndxHandler_Insert;
+ pRootHandler->Search = (ROOT_SEARCH)MndxHandler_Search;
+ pRootHandler->EndSearch = (ROOT_ENDSEARCH)MndxHandler_EndSearch;
+ pRootHandler->GetKey = (ROOT_GETKEY)MndxHandler_GetKey;
+ pRootHandler->Close = (ROOT_CLOSE) MndxHandler_Close;
+ pMndxInfo = &pRootHandler->MndxInfo;
+
+ // Fill-in the flags
+ pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES;
+
// Copy the header into the MNDX info
- memset(pMndxInfo, 0, sizeof(CASC_MNDX_INFO));
pMndxInfo->HeaderVersion = pMndxHeader->HeaderVersion;
pMndxInfo->FormatVersion = pMndxHeader->FormatVersion;
dwFilePointer += sizeof(FILE_MNDX_HEADER);
@@ -3047,10 +3254,10 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(nError == ERROR_SUCCESS && FileNameCount == pMndxInfo->MndxEntriesValid)
{
cbToAllocate = pMndxInfo->MndxEntriesTotal * pMndxInfo->MndxEntrySize;
- pMndxInfo->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate);
- if(pMndxInfo->pMndxEntries != NULL)
+ pRootHandler->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate);
+ if(pRootHandler->pMndxEntries != NULL)
{
- if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pMndxInfo->pMndxEntries, cbToAllocate))
+ if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pRootHandler->pMndxEntries, cbToAllocate))
nError = ERROR_FILE_CORRUPT;
}
else
@@ -3064,15 +3271,15 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(nError == ERROR_SUCCESS)
{
assert(pMndxInfo->MndxEntriesValid <= pMndxInfo->MndxEntriesTotal);
- pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1);
- if(pMndxInfo->ppValidEntries != NULL)
+ pRootHandler->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1);
+ if(pRootHandler->ppValidEntries != NULL)
{
- PCASC_ROOT_ENTRY_MNDX pRootEntry = pMndxInfo->pMndxEntries;
+ PCASC_ROOT_ENTRY_MNDX pRootEntry = pRootHandler->pMndxEntries;
DWORD ValidEntryCount = 1; // edx
DWORD nIndex1 = 0;
// The first entry is always valid
- pMndxInfo->ppValidEntries[nIndex1++] = pMndxInfo->pMndxEntries;
+ pRootHandler->ppValidEntries[nIndex1++] = pRootHandler->pMndxEntries;
// Put the remaining entries
for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pRootEntry++)
@@ -3082,7 +3289,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
if(pRootEntry->Flags & 0x80000000)
{
- pMndxInfo->ppValidEntries[nIndex1++] = pRootEntry + 1;
+ pRootHandler->ppValidEntries[nIndex1++] = pRootEntry + 1;
ValidEntryCount++;
}
}
@@ -3090,131 +3297,28 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
// Verify the final number of valid entries
if((ValidEntryCount - 1) != pMndxInfo->MndxEntriesValid)
nError = ERROR_BAD_FORMAT;
-
- // Mark the MNDX info as fully loaded
- pMndxInfo->bRootFileLoaded = true;
}
else
nError = ERROR_NOT_ENOUGH_MEMORY;
}
- // Save the MNDX info to the archive storage
+ // Load the MNDX packages
if(nError == ERROR_SUCCESS)
{
- // Store the MNDX database into the archive
- hs->pMndxInfo = pMndxInfo;
- pMndxInfo = NULL;
+ nError = LoadPackageNames(pMndxInfo, &pRootHandler->pPackages);
+ pMndxInfo->bRootFileLoaded = (nError == ERROR_SUCCESS);
+ }
#if defined(_DEBUG) && defined(_X86_) && defined(CASCLIB_TEST)
-// CascDumpNameFragTable("E:\\casc-name-fragment-table.txt", hs->pMndxInfo->pMarFile1);
-// CascDumpFileNames("E:\\casc-listfile.txt", hs->pMndxInfo->pMarFile1);
- TestMndxRootFile(hs->pMndxInfo);
+// CascDumpNameFragTable("E:\\casc-name-fragment-table.txt", pMndxInfo->pMarFile1);
+// CascDumpFileNames("E:\\casc-listfile.txt", pMndxInfo->pMarFile1);
+// TestMndxRootFile(pRootHandler);
#endif
- // Load the top level entries
- nError = LoadPackageNames(hs);
- }
-
- // If anything failed, free the memory remaining allocated
- if(nError != ERROR_SUCCESS)
- {
- if(pMndxInfo != NULL)
- FreeMndxInfo(pMndxInfo);
- pMndxInfo = NULL;
- }
+ // Return the result
return nError;
}
-int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry)
-{
- PCASC_ROOT_ENTRY_MNDX pRootEntry;
- TMndxFindResult Struct1C;
-
- // Search the database for the file name
- if(pMndxInfo->bRootFileLoaded)
- {
- Struct1C.SetSearchPath(szFileName, strlen(szFileName));
-
- // Search the file name in the second MAR info (the one with stripped package names)
- if(MAR_FILE_SearchFile(pMndxInfo->pMarFile2, &Struct1C) != ERROR_SUCCESS)
- return ERROR_FILE_NOT_FOUND;
-
- // The found MNDX index must fall into range of valid MNDX entries
- if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid)
- {
- // HOTS: E945F4
- pRootEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex];
- while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage)
- {
- // The highest bit serves as a terminator if set
- if(pRootEntry->Flags & 0x80000000)
- return ERROR_FILE_NOT_FOUND;
-
- pRootEntry++;
- }
-
- // Give the root entry pointer to the caller
- if(ppRootEntry != NULL)
- ppRootEntry[0] = pRootEntry;
- return ERROR_SUCCESS;
- }
- }
-
- return ERROR_FILE_NOT_FOUND;
-}
-
-bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
-{
- TMndxFindResult * pStruct1C = NULL;
- PCASC_MNDX_INFO pMndxInfo = pSearch->hs->pMndxInfo;
- PMAR_FILE pMarFile = pMndxInfo->pMarFile3;
- bool bFindResult = false;
-
- // Sanity checks
- assert(pMndxInfo != NULL);
-
- // If the first time, allocate the structure for the search result
- if(pSearch->pStruct1C == NULL)
- {
- // Create the new search structure
- pSearch->pStruct1C = pStruct1C = new TMndxFindResult;
- if(pSearch->pStruct1C == NULL)
- return false;
-
- // Setup the search mask
- pStruct1C->SetSearchPath("", 0);
- }
-
- // Make shortcut for the search structure
- assert(pSearch->pStruct1C != NULL);
- pStruct1C = (TMndxFindResult *)pSearch->pStruct1C;
-
- // Search the next file name (our code)
- pMarFile->pDatabasePtr->sub_1956CE0(pStruct1C, &bFindResult);
- if(bFindResult)
- return FillFindData(pSearch, pFindData, pStruct1C);
-
- return false;
-}
-
-void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo)
-{
- if(pMndxInfo != NULL)
- {
- if(pMndxInfo->pMarFile1 != NULL)
- MAR_FILE_Destructor(pMndxInfo->pMarFile1);
- if(pMndxInfo->pMarFile2 != NULL)
- MAR_FILE_Destructor(pMndxInfo->pMarFile2);
- if(pMndxInfo->pMarFile3 != NULL)
- MAR_FILE_Destructor(pMndxInfo->pMarFile3);
- if(pMndxInfo->ppValidEntries != NULL)
- CASC_FREE(pMndxInfo->ppValidEntries);
- if(pMndxInfo->pMndxEntries != NULL)
- CASC_FREE(pMndxInfo->pMndxEntries);
- CASC_FREE(pMndxInfo);
- }
-}
-
//----------------------------------------------------------------------------
// Unit tests
diff --git a/dep/CascLib/src/CascMndxRoot_x86.asm b/dep/CascLib/src/CascRootFile_Mndx_x86.asm
index c0e787d0872..bf6d6d0b489 100644
--- a/dep/CascLib/src/CascMndxRoot_x86.asm
+++ b/dep/CascLib/src/CascRootFile_Mndx_x86.asm
@@ -6,9 +6,9 @@ ASSUME FS: NOTHING
TMndxFindResult struc ; (sizeof=0x1C) ; XREF: GAME_OBJECT_03F7E848::sub_E94500_r
szSearchMask dd ? ; XREF: TMndxFindResult__SetPath+2D_w
- ; TMndxFindResult__Constructor+4_w ...
+ ; TMndxFindResult__Constructor+4_w
cchSearchMask dd ? ; XREF: TMndxFindResult__SetPath:loc_1956E9A_w
- ; TMndxFindResult__Constructor+6_w ...
+ ; TMndxFindResult__Constructor+6_w
field_8 dd ? ; XREF: TMndxFindResult__Constructor+9_w
szFoundPath dd ? ; XREF: TMndxFindResult__Constructor+C_w
; TFileNameDatabase__FindFileInDatabase+55_w
@@ -17,7 +17,7 @@ cchFoundPath dd ? ; XREF: TMndxFindResult__Constructor+F_w
FileNameIndex dd ? ; XREF: TMndxFindResult__Constructor+12_w
; TFileNameDatabase__FindFileInDatabase+6B_w
pStruct40 dd ? ; XREF: MAR_FILE__FindFileInDatabase+19_r
- ; TMndxFindResult__SetPath:loc_1956E8C_r ...
+ ; TMndxFindResult__SetPath:loc_1956E8C_r
TMndxFindResult ends
; ---------------------------------------------------------------------------
@@ -25,9 +25,9 @@ TMndxFindResult ends
TRIPLET struc ; (sizeof=0xC)
BitIndex dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+39_r
NextKey dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+8C_r
- ; TSparseArray__GetItemValue:loc_1959B8F_r ...
+ ; TSparseArray__GetItemValue:loc_1959B8F_r
Distance dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+3E_r
- ; TSparseArray__GetItemValue:loc_1959BBE_r ...
+ ; TSparseArray__GetItemValue:loc_1959BBE_r
TRIPLET ends
; ---------------------------------------------------------------------------
@@ -44,33 +44,33 @@ NAME_ENTRY ends
TStruct14 struc ; (sizeof=0x14) ; XREF: TFileNameDatabase::sub_1959460r
HashValue dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+44r
- ; TGenericArray__InsertItem_STRUCT14+46w ...
+ ; TGenericArray__InsertItem_STRUCT14+46w
field_4 dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+48r
- ; TGenericArray__InsertItem_STRUCT14+4Bw ...
+ ; TGenericArray__InsertItem_STRUCT14+4Bw
field_8 dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+4Er
- ; TGenericArray__InsertItem_STRUCT14+51w ...
+ ; TGenericArray__InsertItem_STRUCT14+51w
field_C dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+54r
- ; TGenericArray__InsertItem_STRUCT14+57w ...
+ ; TGenericArray__InsertItem_STRUCT14+57w
field_10 dd ? ; XREF: TGenericArray__InsertItem_STRUCT14+5Ar
- ; TGenericArray__InsertItem_STRUCT14+5Dw ...
+ ; TGenericArray__InsertItem_STRUCT14+5Dw
TStruct14 ends
; ---------------------------------------------------------------------------
TGenericArray struc ; (sizeof=0x15) ; XREF: TGenericArray::LoadDwordsArrayWithCopyr
- ; TFileNameDatabase::LoadBytesr ...
+ ; TFileNameDatabase::LoadBytesr
DataBuffer dd ? ; XREF: TMndxFindResult__CreateStruct40+24w
- ; TMndxFindResult__CreateStruct40+35w ...
+ ; TMndxFindResult__CreateStruct40+35w
field_4 dd ? ; XREF: TMndxFindResult__CreateStruct40+26w
- ; TMndxFindResult__CreateStruct40+38w ...
+ ; TMndxFindResult__CreateStruct40+38w
ItemArray dd ? ; XREF: TMndxFindResult__CreateStruct40+29w
- ; TMndxFindResult__CreateStruct40+3Bw ...
+ ; TMndxFindResult__CreateStruct40+3Bw
ItemCount dd ? ; XREF: TMndxFindResult__CreateStruct40+2Cw
- ; TMndxFindResult__CreateStruct40+3Ew ...
+ ; TMndxFindResult__CreateStruct40+3Ew
MaxItemCount dd ? ; XREF: TFileNameDatabasePtr__CreateDatabase+27o
- ; TMndxFindResult__CreateStruct40+2Fw ...
+ ; TMndxFindResult__CreateStruct40+2Fw
bIsValidArray db ? ; XREF: LoadAndVerifyIndexFileHeader+31w
- ; TMndxFindResult__CreateStruct40+32w ...
+ ; TMndxFindResult__CreateStruct40+32w
TGenericArray ends
; ---------------------------------------------------------------------------
@@ -81,7 +81,7 @@ DataBuffer dd ? ; XREF: TArchiveDatabase__Destructor+64
; TArchiveDatabase__Constructor+1A3w
field_4 dd ? ; XREF: TArchiveDatabase__Constructor+1A9w
ItemArray dd ? ; XREF: sub_1957350+31r
- ; sub_19573D0+1Fr ...
+ ; sub_19573D0+1Fr
ItemCount dd ? ; XREF: TArchiveDatabase__Constructor+1B5w
MaxItemCount dd ? ; XREF: TArchiveDatabase__Constructor+1BBw
bIsValidArray db ? ; XREF: TArchiveDatabase__Constructor+1C1w
@@ -89,9 +89,9 @@ bIsValidArray db ? ; XREF: TArchiveDatabase__Constructor+1C
db ? ; undefined
db ? ; undefined
BitsPerEntry dd ? ; XREF: sub_1957350+1Fr
- ; sub_19573D0+6r ...
+ ; sub_19573D0+6r
EntryBitMask dd ? ; XREF: sub_1957350:loc_19573B1r
- ; sub_19573D0:loc_1957419r ...
+ ; sub_19573D0:loc_1957419r
TotalEntries dd ? ; XREF: TGenericArrayEx__LoadFromStream:loc_195861Bw
; TArchiveDatabase__Constructor+1D3w
TBitEntryArray ends
@@ -100,50 +100,50 @@ TBitEntryArray ends
TStruct40 struc ; (sizeof=0x40)
array_00 TGenericArray <> ; XREF: TMndxFindResult__CreateStruct40+24w
- ; TMndxFindResult__CreateStruct40+26w ...
+ ; TMndxFindResult__CreateStruct40+26w
db ? ; undefined
db ? ; undefined
db ? ; undefined
array_18 TGenericArray <> ; XREF: TMndxFindResult__CreateStruct40+35w
- ; TMndxFindResult__CreateStruct40+38w ...
+ ; TMndxFindResult__CreateStruct40+38w
db ? ; undefined
db ? ; undefined
db ? ; undefined
HashValue dd ? ; XREF: TMndxFindResult__CreateStruct40+47w
- ; TFileNameDatabase__CheckNextPathFragment+1Ar ...
+ ; TFileNameDatabase__CheckNextPathFragment+1Ar
CharIndex dd ? ; XREF: TMndxFindResult__CreateStruct40+4Aw
- ; TFileNameDatabase__CheckNextPathFragment+11r ...
+ ; TFileNameDatabase__CheckNextPathFragment+11r
ItemCount dd ? ; XREF: TMndxFindResult__CreateStruct40+4Dw
- ; TStruct40__InitSearchBuffers+6Bw ...
+ ; TStruct40__InitSearchBuffers+6Bw
SearchPhase dd ? ; XREF: TMndxFindResult__CreateStruct40+50w
- ; TFileNameDatabase__FindFileInDatabase+17w ...
+ ; TFileNameDatabase__FindFileInDatabase+17w
TStruct40 ends
; ---------------------------------------------------------------------------
TSparseArray struc ; (sizeof=0x65) ; XREF: TSparseArray::LoadFromStream_Exchange_r
- ; TNameIndexStruct_r ...
+ ; TNameIndexStruct_r
ItemIsPresent TGenericArray <> ; XREF: LoadAndVerifyIndexFileHeader+31_w
- ; TFileNameDatabase__Destructor+4C_r ...
+ ; TFileNameDatabase__Destructor+4C_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
TotalItemCount dd ? ; XREF: TSparseArray__ExchangeWith+4D_r
- ; TSparseArray__ExchangeWith+56_w ...
+ ; TSparseArray__ExchangeWith+56_w
ValidItemCount dd ? ; XREF: TFileNameDatabasePtr__GetFileNameCount:loc_1956D32_r
- ; TSparseArray__ExchangeWith+59_r ...
+ ; TSparseArray__ExchangeWith+59_r
ArrayTriplets_20 TGenericArray <> ; XREF: TFileNameDatabase__Destructor+40_r
- ; TFileNameDatabase__Destructor+94_r ...
+ ; TFileNameDatabase__Destructor+94_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
ArrayDwords_38 TGenericArray <> ; XREF: TFileNameDatabasePtr__CreateDatabase+27_o
- ; TFileNameDatabase__Destructor+34_r ...
+ ; TFileNameDatabase__Destructor+34_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
ArrayDwords_50 TGenericArray <> ; XREF: TFileNameDatabase__Destructor+28_r
- ; TFileNameDatabase__Destructor+7C_r ...
+ ; TFileNameDatabase__Destructor+7C_r
TSparseArray ends
; ---------------------------------------------------------------------------
@@ -151,12 +151,12 @@ TSparseArray ends
TNameIndexStruct struc ; (sizeof=0x7D) ; XREF: TNameIndexStruct::LoadFromStream_Exchange_r
; TFileNameDatabaser
NameFragments TGenericArray <> ; XREF: TFileNameDatabase__Destructor+58_r
- ; TFileNameDatabase__LoadFromStream+82_r ...
+ ; TFileNameDatabase__LoadFromStream+82_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
Struct68 TSparseArray <> ; XREF: LoadAndVerifyIndexFileHeader+31_w
- ; TFileNameDatabase__Destructor+28_r ...
+ ; TFileNameDatabase__Destructor+28_r
TNameIndexStruct ends
; ---------------------------------------------------------------------------
@@ -164,11 +164,11 @@ TNameIndexStruct ends
TByteStream struc ; (sizeof=0x18) ; XREF: TFileNameDatabasePtr::CreateDatabase_r
; TFileNameDatabase_r
pbData dd ? ; XREF: TByteStream__Constructor+4_w
- ; TByteStream__IsMarDataValid+2_r ...
+ ; TByteStream__IsMarDataValid+2_r
pvMappedFile dd ? ; XREF: TByteStream__Constructor+6_w
; TByteStream__Destructor+3_r
cbData dd ? ; XREF: TByteStream__Constructor+9_w
- ; TByteStream__GetBytes:loc_1959A05_r ...
+ ; TByteStream__GetBytes:loc_1959A05_r
field_C dd ? ; XREF: TByteStream__Constructor+C_w
hFile dd ? ; XREF: TByteStream__Constructor+F_w
; TByteStream__Destructor:loc_19599D2_r
@@ -183,11 +183,11 @@ TStruct10 struc ; (sizeof=0x10) ; XREF: TStruct10::sub_1957800_r
field_0 dd ? ; XREF: sub_19572E0+24_w
; TFileNameDatabase__Constructor+21A_w
field_4 dd ? ; XREF: sub_1956FD0+28_w
- ; sub_1956FD0:loc_1957001_w ...
+ ; sub_1956FD0:loc_1957001_w
field_8 dd ? ; XREF: sub_19572E0+4A_w
- ; sub_19572E0+5B_w ...
+ ; sub_19572E0+5B_w
field_C dd ? ; XREF: sub_1957050:loc_1957074_w
- ; sub_1957050:loc_1957081_w ...
+ ; sub_1957050:loc_1957081_w
TStruct10 ends
; ---------------------------------------------------------------------------
@@ -195,52 +195,52 @@ TStruct10 ends
TFileNameDatabasePtr struc ; (sizeof=0x4) ; XREF: TFileNameDatabase_r
; MAR_FILE_r
pDatabase dd ? ; XREF: MAR_FILE__Constructor+1D_w
- ; MAR_FILE__CreateDatabase+22_w ...
+ ; MAR_FILE__CreateDatabase+22_w
TFileNameDatabasePtr ends
; ---------------------------------------------------------------------------
TFileNameDatabase struc ; (sizeof=0x240) ; XREF: TFileNameDatabase::LoadFromStream_Exchange_r
Struct68_00 TSparseArray <> ; XREF: TFileNameDatabasePtr__CreateDatabase+27_o
- ; TFileNameDatabase__Destructor+D9_r ...
+ ; TFileNameDatabase__Destructor+D9_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
FileNameIndexes TSparseArray <> ; XREF: TFileNameDatabasePtr__GetFileNameCount:loc_1956D32_r
- ; TFileNameDatabase__Destructor+AC_r ...
+ ; TFileNameDatabase__Destructor+AC_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
Struct68_D0 TSparseArray <> ; XREF: TFileNameDatabase__Destructor+7C_r
- ; TFileNameDatabase__Destructor+88_r ...
+ ; TFileNameDatabase__Destructor+88_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
FrgmDist_LoBits TGenericArray <> ; XREF: TFileNameDatabase__Destructor+70_r
- ; TFileNameDatabase__CheckNextPathFragment:loc_1957AC9_r ...
+ ; TFileNameDatabase__CheckNextPathFragment:loc_1957AC9_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
FrgmDist_HiBits TBitEntryArray <> ; XREF: TFileNameDatabase__Destructor+64_r
- ; TFileNameDatabase__CheckNextPathFragment+119_r ...
+ ; TFileNameDatabase__CheckNextPathFragment+119_r
IndexStruct_174 TNameIndexStruct <> ; XREF: TFileNameDatabase__Destructor+28_r
- ; TFileNameDatabase__Destructor+34_r ...
+ ; TFileNameDatabase__Destructor+34_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
NextDB TFileNameDatabasePtr <> ; XREF: TFileNameDatabase__Destructor+1D_o
- ; TFileNameDatabase__CheckNextPathFragment+52_r ...
+ ; TFileNameDatabase__CheckNextPathFragment+52_r
NameFragTable TGenericArray <> ; XREF: TFileNameDatabase__Destructor+E_r
- ; TFileNameDatabase__CheckNextPathFragment+2F_r ...
+ ; TFileNameDatabase__CheckNextPathFragment+2F_r
db ? ; undefined
db ? ; undefined
db ? ; undefined
NameFragIndexMask dd ? ; XREF: TFileNameDatabase__CheckNextPathFragment+26_r
- ; sub_1957B80:loc_1957B95_r ...
+ ; sub_1957B80:loc_1957B95_r
field_214 dd ? ; XREF: TFileNameDatabase__Constructor+20E_w
; TFileNameDatabase__LoadFromStream+110_w
Struct10 TStruct10 <> ; XREF: TFileNameDatabase__Constructor+21A_w
- ; TFileNameDatabase__Constructor+224_w ...
+ ; TFileNameDatabase__Constructor+224_w
MarStream TByteStream <> ; XREF: TFileNameDatabase__Destructor+3_o
; TFileNameDatabase__Constructor+214_o
TFileNameDatabase ends
@@ -249,11 +249,11 @@ TFileNameDatabase ends
MAR_FILE struc ; (sizeof=0xC)
pDatabasePtr TFileNameDatabasePtr <> ; XREF: MAR_FILE__Constructor+1D_w
- ; MAR_FILE__CreateDatabase+22_w ...
+ ; MAR_FILE__CreateDatabase+22_w
pbMarFileData dd ? ; XREF: MAR_FILE__Constructor+1A_w
- ; MAR_FILE__CreateDatabase+1B_r ...
+ ; MAR_FILE__CreateDatabase+1B_r
cbMarFileData dd ? ; XREF: MAR_FILE__Constructor+12_w
- ; MAR_FILE__CreateDatabase+18_r ...
+ ; MAR_FILE__CreateDatabase+18_r
MAR_FILE ends
.DATA
@@ -368,7 +368,7 @@ operator_delete ENDP
TSparseArray__GetItemValue proc near ; CODE XREF: sub_1957350+1A_p
- ; TFileNameDatabase__CheckNextPathFragment+103_p ...
+ ; TFileNameDatabase__CheckNextPathFragment+103_p
arg_0 = dword ptr 8
@@ -451,7 +451,7 @@ loc_1959BDE: ; CODE XREF: TSparseArray__GetItemValue+
add esi, eax
loc_1959BE0: ; CODE XREF: TSparseArray__GetItemValue+26_j
- ; TSparseArray__GetItemValue+45_j ...
+ ; TSparseArray__GetItemValue+45_j
mov ebx, edi ; jumptable 01959B88 default case
shr ebx, 5
test bl, 1
@@ -525,7 +525,7 @@ TSparseArray__GetItemValue endp
TArchiveDatabase__sub_1959CB0 proc near
; CODE XREF: TFileNameDatabase__CheckNextPathFragment+A1_p
- ; sub_1958B00+E8_p ...
+ ; sub_1958B00+E8_p
pThis = dword ptr -4
dwKey = dword ptr 8
@@ -614,7 +614,7 @@ loc_1959D55: ; CODE XREF: TFileNameDatabase__FindNext
mov ecx, [ebp+pThis]
loc_1959D5F: ; CODE XREF: TFileNameDatabase__FindNextMatch+62_j
- ; TFileNameDatabase__FindNextMatch+7C_j ...
+ ; TFileNameDatabase__FindNextMatch+7C_j
mov ecx, [ecx+28h]
lea esi, [eax+eax*2]
mov edi, [ecx+esi*4] ; EDI = Struct68_00.ArrayTriplets_20.ItemArray[eax]
@@ -709,7 +709,7 @@ loc_1959E49: ; CODE XREF: TFileNameDatabase__FindNext
lea edx, [edx+esi-1C0h]
loc_1959E53: ; CODE XREF: TFileNameDatabase__FindNextMatch+FE_j
- ; TFileNameDatabase__FindNextMatch+10B_j ...
+ ; TFileNameDatabase__FindNextMatch+10B_j
mov eax, [ebp+pThis]
mov ebx, [eax+8]
mov ecx, [ebx+edi*4]
@@ -809,7 +809,7 @@ TArchiveDatabase__sub_1959CB0 endp
; Attributes: bp-based frame
TNameIndexStruct__CheckNameFragment proc near ; CODE XREF: TFileNameDatabase__CheckNextPathFragment+6B_p
- ; TFileNameDatabase__CheckNextPathFragment+191_p ...
+ ; TFileNameDatabase__CheckNextPathFragment+191_p
pThis = dword ptr -4
pUnknownStruct1C= dword ptr 8
@@ -845,7 +845,7 @@ loc_195A1A1: ; CODE XREF: TNameIndexStruct__CheckName
jb short loc_195A1A1
loc_195A1BD: ; CODE XREF: TNameIndexStruct__CheckNameFragment+2C_j
- ; TNameIndexStruct__CheckNameFragment+5Ej ...
+ ; TNameIndexStruct__CheckNameFragment+5Ej
pop edi
pop esi
xor al, al
@@ -907,7 +907,7 @@ TNameIndexStruct__CheckNameFragment endp
; Attributes: bp-based frame
sub_1957350 proc near ; CODE XREF: sub_1957B80+D8p
- ; sub_1957B80:loc_1957C73p ...
+ ; sub_1957B80:loc_1957C73p
arg_0 = dword ptr 8
@@ -971,7 +971,7 @@ sub_1957350 endp
; Attributes: bp-based frame
sub_1959F50 proc near ; CODE XREF: sub_1957B80+14C_p
- ; sub_1958980+62_p ...
+ ; sub_1958980+62_p
var_4 = dword ptr -4
arg_0 = dword ptr 8
@@ -1046,7 +1046,7 @@ loc_1959FCA: ; CODE XREF: sub_1959F50+76_j
mov ecx, [ebp+var_4]
loc_1959FD4: ; CODE XREF: sub_1959F50+51_j
- ; sub_1959F50+5B_j ...
+ ; sub_1959F50+5B_j
mov edi, [ecx+28h]
lea esi, [eax+eax*2]
sub edx, [edi+esi*4]
@@ -1124,7 +1124,7 @@ loc_195A064: ; CODE XREF: sub_1959F50+CD_j
sub edx, esi
loc_195A066: ; CODE XREF: sub_1959F50+B5_j
- ; sub_1959F50+BC_j ...
+ ; sub_1959F50+BC_j
mov ebx, [ecx+8]
mov esi, [ebx+edi*4]
mov eax, esi
@@ -1223,7 +1223,7 @@ sub_1959F50 endp
; Attributes: bp-based frame
sub_1957B80 proc near ; CODE XREF: TFileNameDatabase__CheckNextPathFragment+5E_p
- ; TFileNameDatabase__CheckNextPathFragment+180_p ...
+ ; TFileNameDatabase__CheckNextPathFragment+180_p
pStruct40 = dword ptr -4
arg_0 = dword ptr 8
@@ -1300,7 +1300,7 @@ loc_1957C05: ; CODE XREF: sub_1957B80+6C_j
jb loc_1957B95
loc_1957C25: ; CODE XREF: sub_1957B80+67_j
- ; sub_1957B80+7C_j ...
+ ; sub_1957B80+7C_j
pop edi
pop esi
xor al, al
@@ -1765,7 +1765,7 @@ TFileNameDatabase__FindFileInDatabase endp
; Attributes: bp-based frame
TGenericArray__SetMaxItems_BYTE proc near ; CODE XREF: sub_19582E0+2Cp
- ; TStruct40__InitSearchBuffers+2Cp ...
+ ; TStruct40__InitSearchBuffers+2Cp
ByteCount = dword ptr 8
@@ -1820,7 +1820,7 @@ TGenericArray__SetMaxItems_BYTE endp
TGenericArray__SetMaxItems_STRUCT14 proc near
; CODE XREF: TGenericArray__InsertItem_STRUCT14+2Cp
- ; TGenericArray__sub_19583A0+2Dp ...
+ ; TGenericArray__sub_19583A0+2Dp
var_4 = dword ptr -4
ItemCount = dword ptr 8
@@ -2086,7 +2086,7 @@ TGenericArray__InsertItem_STRUCT14 endp
; Attributes: bp-based frame
CopyNameFragment proc near ; CODE XREF: sub_1958980+DAp
- ; sub_1958D70+6Dp ...
+ ; sub_1958D70+6Dp
pThis = dword ptr -4
pStruct1C = dword ptr 8
@@ -2284,7 +2284,7 @@ CopyNameFragment endp
; Attributes: bp-based frame
sub_1958D70 proc near ; CODE XREF: sub_1958980+C9p
- ; sub_1958D70+59p ...
+ ; sub_1958D70+59p
var_C = dword ptr -0Ch
var_8 = dword ptr -8
@@ -2582,7 +2582,7 @@ sub_1958D70 endp
; Attributes: bp-based frame
TFileNameDatabase__sub_1959CB0 proc near ; CODE XREF: TFileNameDatabase__CheckNextPathFragment+A1p
- ; TFileNameDatabase__sub_1958B00+E8p ...
+ ; TFileNameDatabase__sub_1958B00+E8p
pThis = dword ptr -4
dwKey = dword ptr 8
@@ -2671,7 +2671,7 @@ loc_1959D55: ; CODE XREF: TFileNameDatabase__sub_1959
mov ecx, [ebp+pThis]
loc_1959D5F: ; CODE XREF: TFileNameDatabase__sub_1959CB0+62j
- ; TFileNameDatabase__sub_1959CB0+7Cj ...
+ ; TFileNameDatabase__sub_1959CB0+7Cj
mov ecx, [ecx+TFileNameDatabase.Struct68_00.ArrayTriplets_20.ItemArray]
lea esi, [eax+eax*2]
mov edi, [ecx+esi*4] ; EDI = Struct68_00.ArrayTriplets_20.ItemArray[eax]
@@ -2766,7 +2766,7 @@ loc_1959E49: ; CODE XREF: TFileNameDatabase__sub_1959
lea edx, [edx+esi-1C0h]
loc_1959E53: ; CODE XREF: TFileNameDatabase__sub_1959CB0+FEj
- ; TFileNameDatabase__sub_1959CB0+10Bj ...
+ ; TFileNameDatabase__sub_1959CB0+10Bj
mov eax, [ebp+pThis]
mov ebx, [eax+TFileNameDatabase.Struct68_00.ItemIsPresent.ItemArray]
mov ecx, [ebx+edi*4]
@@ -2866,7 +2866,7 @@ TFileNameDatabase__sub_1959CB0 endp
; Attributes: bp-based frame
TNameIndexStruct__CheckAndCopyNameFragment proc near ; CODE XREF: TArchiveDatabase__sub_1958B00+74p
- ; TArchiveDatabase__sub_1958B00+1E0p ...
+ ; TArchiveDatabase__sub_1958B00+1E0p
var_C = dword ptr -0Ch
pThis = dword ptr -8
@@ -3206,7 +3206,7 @@ TNameIndexStruct__CheckAndCopyNameFragment endp
; Attributes: bp-based frame
sub_1959010 proc near ; CODE XREF: TArchiveDatabase__sub_1958B00+67p
- ; TArchiveDatabase__sub_1958B00+1CFp ...
+ ; TArchiveDatabase__sub_1958B00+1CFp
pFragmentInfo = dword ptr -0Ch
var_8 = dword ptr -8
@@ -3262,7 +3262,7 @@ loc_195907F: ; CODE XREF: sub_1959010+5Ej
jnz loc_195912E
loc_1959087: ; CODE XREF: sub_1959010+90j
- ; sub_1959010+1F3j ...
+ ; sub_1959010+1F3j
pop edi
pop esi
xor al, al
@@ -3962,14 +3962,14 @@ loc_19594B0: ; CODE XREF: TFileNameDatabase__sub_1959
; ---------------------------------------------------------------------------
loc_1959522: ; CODE XREF: TFileNameDatabase__sub_1959460+26Bj
- ; TFileNameDatabase__sub_1959460+2D9j ...
+ ; TFileNameDatabase__sub_1959460+2D9j
mov ebx, [ebp+pThis]
jmp short loc_1959530
; ---------------------------------------------------------------------------
align 10h
loc_1959530: ; CODE XREF: TFileNameDatabase__sub_1959460+23j
- ; TFileNameDatabase__sub_1959460+97j ...
+ ; TFileNameDatabase__sub_1959460+97j
mov edx, [esi+TStruct40.ItemCount]
cmp edx, [esi+TStruct40.array_18.ItemCount]
jnz loc_19595BD
diff --git a/dep/CascLib/src/CascRootFile_Ovr.cpp b/dep/CascLib/src/CascRootFile_Ovr.cpp
new file mode 100644
index 00000000000..205353c0120
--- /dev/null
+++ b/dep/CascLib/src/CascRootFile_Ovr.cpp
@@ -0,0 +1,199 @@
+/*****************************************************************************/
+/* CascRootFile_Ovr.cpp Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Support for loading Overwatch ROOT file */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 28.10.15 1.00 Lad The first version of CascRootFile_Ovr.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Structure definitions for Overwatch root file
+
+typedef struct _CASC_FILE_ENTRY
+{
+ ENCODING_KEY EncodingKey; // Encoding key
+ ULONGLONG FileNameHash; // File name hash
+ DWORD dwFileName; // Offset of the file name in the name cache
+} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY;
+
+struct TRootHandler_Ovr : public TRootHandler
+{
+ // Linear global list of file entries
+ DYNAMIC_ARRAY FileTable;
+
+ // Linear global list of names
+ DYNAMIC_ARRAY FileNames;
+
+ // Global map of FileName -> FileEntry
+ PCASC_MAP pRootMap;
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static int InsertFileEntry(
+ TRootHandler_Ovr * pRootHandler,
+ const char * szFileName,
+ LPBYTE pbEncodingKey)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+ size_t nLength = strlen(szFileName);
+
+ // Attempt to insert the file name to the global buffer
+ szFileName = (char *)Array_Insert(&pRootHandler->FileNames, szFileName, nLength + 1);
+ if(szFileName == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Attempt to insert the entry to the array of file entries
+ pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
+ if(pFileEntry == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill the file entry
+ pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey;
+ pFileEntry->FileNameHash = CalcFileNameHash(szFileName);
+ pFileEntry->dwFileName = Array_IndexOf(&pRootHandler->FileNames, szFileName);
+
+ // Insert the file entry to the map
+ assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL);
+ Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Implementation of Overwatch root file
+
+static int OvrHandler_Insert(
+ TRootHandler_Ovr * pRootHandler,
+ const char * szFileName,
+ LPBYTE pbEncodingKey)
+{
+ return InsertFileEntry(pRootHandler, szFileName, pbEncodingKey);
+}
+
+static LPBYTE OvrHandler_Search(TRootHandler_Ovr * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+
+ // Are we still inside the root directory range?
+ while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount)
+ {
+ // Retrieve the file item
+ pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1);
+ strcpy(pSearch->szFileName, (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName));
+
+ // Prepare the pointer to the next search
+ pSearch->IndexLevel1++;
+ return pFileEntry->EncodingKey.Value;
+ }
+
+ // No more entries
+ return NULL;
+}
+
+static void OvrHandler_EndSearch(TRootHandler_Ovr * /* pRootHandler */, TCascSearch * /* pSearch */)
+{
+ // Do nothing
+}
+
+static LPBYTE OvrHandler_GetKey(TRootHandler_Ovr * pRootHandler, const char * szFileName)
+{
+ ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
+
+ return (LPBYTE)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL);
+}
+
+static void OvrHandler_Close(TRootHandler_Ovr * pRootHandler)
+{
+ if(pRootHandler != NULL)
+ {
+ // Free the file map
+ if(pRootHandler->pRootMap)
+ Map_Free(pRootHandler->pRootMap);
+ pRootHandler->pRootMap = NULL;
+
+ // Free the array of the file names and file items
+ Array_Free(&pRootHandler->FileTable);
+ Array_Free(&pRootHandler->FileNames);
+
+ // Free the root file itself
+ CASC_FREE(pRootHandler);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
+{
+ TRootHandler_Ovr * pRootHandler;
+ ENCODING_KEY KeyBuffer;
+ QUERY_KEY EncodingKey = {KeyBuffer.Value, MD5_HASH_SIZE};
+ void * pTextFile;
+ size_t nLength;
+ char szOneLine[0x200];
+ char szFileName[MAX_PATH+1];
+ DWORD dwFileCountMax = hs->pEncodingMap->TableSize;
+ int nError = ERROR_SUCCESS;
+
+ // Allocate the root handler object
+ hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Ovr, 1);
+ if(pRootHandler == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill-in the handler functions
+ memset(pRootHandler, 0, sizeof(TRootHandler_Ovr));
+ pRootHandler->Insert = (ROOT_INSERT)OvrHandler_Insert;
+ pRootHandler->Search = (ROOT_SEARCH)OvrHandler_Search;
+ pRootHandler->EndSearch = (ROOT_ENDSEARCH)OvrHandler_EndSearch;
+ pRootHandler->GetKey = (ROOT_GETKEY)OvrHandler_GetKey;
+ pRootHandler->Close = (ROOT_CLOSE)OvrHandler_Close;
+
+ // Fill-in the flags
+ pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES;
+
+ // Allocate the linear array of file entries
+ nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, 0x10000);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Allocate the buffer for the file names
+ nError = Array_Create(&pRootHandler->FileNames, char, 0x10000);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Create map of ROOT_ENTRY -> FileEntry
+ pRootHandler->pRootMap = Map_Create(dwFileCountMax, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash));
+ if(pRootHandler->pRootMap == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Parse the ROOT file
+ pTextFile = ListFile_FromBuffer(pbRootFile, cbRootFile);
+ if(pTextFile != NULL)
+ {
+ // Skip the first line, containing "#MD5|CHUNK_ID|FILENAME|INSTALLPATH"
+ ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine));
+
+ // Parse the next lines
+ while((nLength = ListFile_GetNextLine(pTextFile, szOneLine, _maxchars(szOneLine))) > 0)
+ {
+ // Parse the line
+ nError = ParseRootFileLine(szOneLine, szOneLine + nLength, &EncodingKey, szFileName, _maxchars(szFileName));
+ if(nError == ERROR_SUCCESS)
+ {
+ InsertFileEntry(pRootHandler, szFileName, KeyBuffer.Value);
+ }
+ }
+
+ ListFile_Free(pTextFile);
+ }
+
+ // Succeeded
+ return nError;
+}
diff --git a/dep/CascLib/src/CascRootFile_WoW6.cpp b/dep/CascLib/src/CascRootFile_WoW6.cpp
new file mode 100644
index 00000000000..0c631f82b00
--- /dev/null
+++ b/dep/CascLib/src/CascRootFile_WoW6.cpp
@@ -0,0 +1,531 @@
+/*****************************************************************************/
+/* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */
+/*---------------------------------------------------------------------------*/
+/* Storage functions for CASC */
+/* Note: WoW6 offsets refer to WoW.exe 6.0.3.19116 (32-bit) */
+/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.04.14 1.00 Lad The first version of CascOpenStorage.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+#define ROOT_SEARCH_PHASE_INITIALIZING 0
+#define ROOT_SEARCH_PHASE_LISTFILE 1
+#define ROOT_SEARCH_PHASE_NAMELESS 2
+#define ROOT_SEARCH_PHASE_FINISHED 2
+
+// On-disk version of locale block
+typedef struct _FILE_LOCALE_BLOCK
+{
+ DWORD NumberOfFiles; // Number of entries
+ DWORD Flags;
+ DWORD Locales; // File locale mask (CASC_LOCALE_XXX)
+
+ // Followed by a block of 32-bit integers (count: NumberOfFiles)
+ // Followed by the MD5 and file name hash (count: NumberOfFiles)
+
+} FILE_LOCALE_BLOCK, *PFILE_LOCALE_BLOCK;
+
+// On-disk version of root entry
+typedef struct _FILE_ROOT_ENTRY
+{
+ ENCODING_KEY EncodingKey; // MD5 of the file
+ ULONGLONG FileNameHash; // Jenkins hash of the file name
+
+} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
+
+
+typedef struct _CASC_ROOT_BLOCK
+{
+ PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
+ PDWORD FileDataIds; // Pointer to the array of File Data IDs
+ PFILE_ROOT_ENTRY pRootEntries;
+
+} CASC_ROOT_BLOCK, *PCASC_ROOT_BLOCK;
+
+// Root file entry for CASC storages without MNDX root file (World of Warcraft 6.0+)
+// Does not match to the in-file structure of the root entry
+typedef struct _CASC_FILE_ENTRY
+{
+ ENCODING_KEY EncodingKey; // File encoding key (MD5)
+ ULONGLONG FileNameHash; // Jenkins hash of the file name
+ DWORD FileDataId; // File Data Index
+ DWORD Locales; // Locale flags of the file
+
+} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY;
+
+struct TRootHandler_WoW6 : public TRootHandler
+{
+ // Linear global list of file entries
+ DYNAMIC_ARRAY FileTable;
+
+ // Global map of FileName -> FileEntry
+ PCASC_MAP pRootMap;
+
+ // For counting files
+ DWORD dwTotalFileCount;
+ DWORD FileDataId;
+};
+
+// Prototype for root file parsing routine
+typedef int (*PARSE_ROOT)(TRootHandler_WoW6 * pRootHandler, PCASC_ROOT_BLOCK pBlockInfo);
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static bool IsFileDataIdName(const char * szFileName)
+{
+ BYTE BinaryValue[4];
+
+ // File name must begin with "File", case insensitive
+ if(AsciiToUpperTable_BkSlash[szFileName[0]] == 'F' &&
+ AsciiToUpperTable_BkSlash[szFileName[1]] == 'I' &&
+ AsciiToUpperTable_BkSlash[szFileName[2]] == 'L' &&
+ AsciiToUpperTable_BkSlash[szFileName[3]] == 'E')
+ {
+ // Then, 8 hexadecimal digits must follow
+ if(ConvertStringToBinary(szFileName + 4, 8, BinaryValue) == ERROR_SUCCESS)
+ {
+ // Must be followed by an extension or end-of-string
+ return (szFileName[0x0C] == 0 || szFileName[0x0C] == '.');
+ }
+ }
+
+ return false;
+}
+
+// Search by FileDataId
+PCASC_FILE_ENTRY FindRootEntry(DYNAMIC_ARRAY & FileTable, DWORD FileDataId)
+{
+ PCASC_FILE_ENTRY pStartEntry = (PCASC_FILE_ENTRY)FileTable.ItemArray;
+ PCASC_FILE_ENTRY pMidlEntry;
+ PCASC_FILE_ENTRY pEndEntry = pStartEntry + FileTable.ItemCount - 1;
+ int nResult;
+
+ // Perform binary search on the table
+ while(pStartEntry < pEndEntry)
+ {
+ // Calculate the middle of the interval
+ pMidlEntry = pStartEntry + ((pEndEntry - pStartEntry) / 2);
+
+ // Did we find it?
+ nResult = (int)FileDataId - (int)pMidlEntry->FileDataId;
+ if(nResult == 0)
+ return pMidlEntry;
+
+ // Move the interval to the left or right
+ (nResult < 0) ? pEndEntry = pMidlEntry : pStartEntry = pMidlEntry + 1;
+ }
+
+ return NULL;
+}
+
+// Search by file name hash
+// Also used in CascSearchFile
+PCASC_FILE_ENTRY FindRootEntry(PCASC_MAP pRootMap, const char * szFileName, DWORD * PtrTableIndex)
+{
+ // Calculate the HASH value of the normalized file name
+ ULONGLONG FileNameHash = CalcFileNameHash(szFileName);
+
+ // Perform the hash search
+ return (PCASC_FILE_ENTRY)Map_FindObject(pRootMap, &FileNameHash, PtrTableIndex);
+}
+
+LPBYTE VerifyLocaleBlock(PCASC_ROOT_BLOCK pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
+{
+ // Validate the file locale block
+ pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
+ pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
+ if(pbFilePointer > pbFileEnd)
+ return NULL;
+
+ // Validate the array of 32-bit integers
+ pBlockInfo->FileDataIds = (PDWORD)pbFilePointer;
+ pbFilePointer = (LPBYTE)(pBlockInfo->FileDataIds + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
+ if(pbFilePointer > pbFileEnd)
+ return NULL;
+
+ // Validate the array of root entries
+ pBlockInfo->pRootEntries = (PFILE_ROOT_ENTRY)pbFilePointer;
+ pbFilePointer = (LPBYTE)(pBlockInfo->pRootEntries + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
+ if(pbFilePointer > pbFileEnd)
+ return NULL;
+
+ // Return the position of the next block
+ return pbFilePointer;
+}
+
+static int ParseRoot_CountFiles(
+ TRootHandler_WoW6 * pRootHandler,
+ PCASC_ROOT_BLOCK pRootBlock)
+{
+ // Add the file count to the total file count
+ pRootHandler->dwTotalFileCount += pRootBlock->pLocaleBlockHdr->NumberOfFiles;
+ return ERROR_SUCCESS;
+}
+
+static int ParseRoot_AddRootEntries(
+ TRootHandler_WoW6 * pRootHandler,
+ PCASC_ROOT_BLOCK pRootBlock)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+
+ // Sanity checks
+ assert(pRootHandler->FileTable.ItemArray != NULL);
+ assert(pRootHandler->FileTable.ItemCountMax != 0);
+
+ // WoW.exe (build 19116): Blocks with zero files are skipped
+ for(DWORD i = 0; i < pRootBlock->pLocaleBlockHdr->NumberOfFiles; i++)
+ {
+ // Create new entry, with overflow check
+ if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
+ return ERROR_INSUFFICIENT_BUFFER;
+ pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
+
+ // (004147A3) Prepare the CASC_FILE_ENTRY structure
+ pFileEntry->FileNameHash = pRootBlock->pRootEntries[i].FileNameHash;
+ pFileEntry->FileDataId = pRootHandler->FileDataId + pRootBlock->FileDataIds[i];
+ pFileEntry->Locales = pRootBlock->pLocaleBlockHdr->Locales;
+ pFileEntry->EncodingKey = pRootBlock->pRootEntries[i].EncodingKey;
+
+ // Also, insert the entry to the map
+ Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
+
+ // Update the local File Data Id
+ assert((pFileEntry->FileDataId + 1) > pFileEntry->FileDataId);
+ pRootHandler->FileDataId = pFileEntry->FileDataId + 1;
+
+ // Move to the next root entry
+ pFileEntry++;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static int ParseWowRootFileInternal(
+ TRootHandler_WoW6 * pRootHandler,
+ PARSE_ROOT pfnParseRoot,
+ LPBYTE pbRootFile,
+ LPBYTE pbRootFileEnd,
+ DWORD dwLocaleMask,
+ bool bLoadBlocksWithFlags80,
+ BYTE HighestBitValue)
+{
+ CASC_ROOT_BLOCK RootBlock;
+
+ // Now parse the root file
+ while(pbRootFile < pbRootFileEnd)
+ {
+ // Validate the file locale block
+ pbRootFile = VerifyLocaleBlock(&RootBlock, pbRootFile, pbRootFileEnd);
+ if(pbRootFile == NULL)
+ break;
+
+ // WoW.exe (build 19116): Entries with flag 0x100 set are skipped
+ if(RootBlock.pLocaleBlockHdr->Flags & 0x100)
+ continue;
+
+ // WoW.exe (build 19116): Entries with flag 0x80 set are skipped if arg_4 is set to FALSE (which is by default)
+ if((RootBlock.pLocaleBlockHdr->Flags & 0x80) && bLoadBlocksWithFlags80 == 0)
+ continue;
+
+ // WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to arg_8 are skipped
+ if((RootBlock.pLocaleBlockHdr->Flags >> 0x1F) != HighestBitValue)
+ continue;
+
+ // WoW.exe (build 19116): Locales other than defined mask are skipped too
+ if((RootBlock.pLocaleBlockHdr->Locales & dwLocaleMask) == 0)
+ continue;
+
+ // Now call the custom function
+ pfnParseRoot(pRootHandler, &RootBlock);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+/*
+ // Code from WoW.exe
+ if(dwLocaleMask == CASC_LOCALE_DUAL_LANG)
+ {
+ // Is this english version of WoW?
+ if(arg_4 == CASC_LOCALE_BIT_ENUS)
+ {
+ LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, false, HighestBitValue);
+ LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
+ return ERROR_SUCCESS;
+ }
+
+ // Is this portuguese version of WoW?
+ if(arg_4 == CASC_LOCALE_BIT_PTBR)
+ {
+ LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, false, HighestBitValue);
+ LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
+ }
+ }
+
+ LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << arg_4), false, HighestBitValue);
+*/
+
+static int ParseWowRootFile2(
+ TRootHandler_WoW6 * pRootHandler,
+ PARSE_ROOT pfnParseRoot,
+ LPBYTE pbRootFile,
+ LPBYTE pbRootFileEnd,
+ DWORD dwLocaleMask,
+ BYTE HighestBitValue)
+{
+ // Load the locale as-is
+ ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, false, HighestBitValue);
+
+ // If we wanted enGB, we also load enUS for the missing files
+ if(dwLocaleMask == CASC_LOCALE_ENGB)
+ ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_ENUS, false, HighestBitValue);
+
+ if(dwLocaleMask == CASC_LOCALE_PTPT)
+ ParseWowRootFileInternal(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, CASC_LOCALE_PTBR, false, HighestBitValue);
+
+ return ERROR_SUCCESS;
+}
+
+// WoW.exe: 004146C7 (BuildManifest::Load)
+static int ParseWowRootFile(
+ TRootHandler_WoW6 * pRootHandler,
+ PARSE_ROOT pfnParseRoot,
+ LPBYTE pbRootFile,
+ LPBYTE pbRootFileEnd,
+ DWORD dwLocaleMask)
+{
+ ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 0);
+ ParseWowRootFile2(pRootHandler, pfnParseRoot, pbRootFile, pbRootFileEnd, dwLocaleMask, 1);
+ return ERROR_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Implementation of WoW6 root file
+
+static int WowHandler_Insert(
+ TRootHandler_WoW6 * pRootHandler,
+ const char * szFileName,
+ LPBYTE pbEncodingKey)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+ DWORD FileDataId = 0;
+
+ // Don't let the number of items to overflow
+ if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Insert the item to the linear file list
+ pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1);
+ if(pFileEntry != NULL)
+ {
+ // Get the file data ID of the previous item (0 if this is the first one)
+ if(pRootHandler->FileTable.ItemCount > 1)
+ FileDataId = pFileEntry[-1].FileDataId;
+
+ // Fill-in the new entry
+ pFileEntry->EncodingKey = *(PENCODING_KEY)pbEncodingKey;
+ pFileEntry->FileNameHash = CalcFileNameHash(szFileName);
+ pFileEntry->FileDataId = FileDataId + 1;
+ pFileEntry->Locales = CASC_LOCALE_ALL;
+
+ // Verify collisions (debug version only)
+ assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL);
+
+ // Insert the entry to the map
+ Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash);
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static LPBYTE WowHandler_Search(TRootHandler_WoW6 * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD PtrLocaleFlags)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+
+ // Only if we have a listfile
+ if(pSearch->pCache != NULL)
+ {
+ // Keep going through the listfile
+ while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
+ {
+ // Find the root entry
+ pFileEntry = FindRootEntry(pRootHandler->pRootMap, pSearch->szFileName, NULL);
+ if(pFileEntry != NULL)
+ {
+ // Give the caller the locale mask
+ if(PtrLocaleFlags != NULL)
+ PtrLocaleFlags[0] = pFileEntry->Locales;
+ return pFileEntry->EncodingKey.Value;
+ }
+ }
+ }
+
+ // No more files
+ return NULL;
+}
+
+static LPBYTE WowHandler_GetKey(TRootHandler_WoW6 * pRootHandler, const char * szFileName)
+{
+ PCASC_FILE_ENTRY pFileEntry;
+ DWORD FileDataId;
+ BYTE FileDataIdLE[4];
+
+ // Open by FileDataId. The file name must be as following:
+ // File########.xxx, where '#' are hexa-decimal numbers (case insensitive).
+ // Extension is ignored in that case
+ if(IsFileDataIdName(szFileName))
+ {
+ ConvertStringToBinary(szFileName + 4, 8, FileDataIdLE);
+ FileDataId = ConvertBytesToInteger_4(FileDataIdLE);
+
+ pFileEntry = FindRootEntry(pRootHandler->FileTable, FileDataId);
+ }
+ else
+ {
+ // Find by the file name hash
+ pFileEntry = FindRootEntry(pRootHandler->pRootMap, szFileName, NULL);
+ }
+
+ return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL;
+}
+
+static void WowHandler_EndSearch(TRootHandler_WoW6 * /* pRootHandler */, TCascSearch * pSearch)
+{
+ if(pSearch->pRootContext != NULL)
+ CASC_FREE(pSearch->pRootContext);
+ pSearch->pRootContext = NULL;
+}
+
+static void WowHandler_Close(TRootHandler_WoW6 * pRootHandler)
+{
+ if(pRootHandler != NULL)
+ {
+ Array_Free(&pRootHandler->FileTable);
+ Map_Free(pRootHandler->pRootMap);
+ CASC_FREE(pRootHandler);
+ }
+}
+
+#ifdef _DEBUG
+static void TRootHandlerWoW6_Dump(
+ TCascStorage * hs,
+ TDumpContext * dc, // Pointer to an opened file
+ LPBYTE pbRootFile,
+ DWORD cbRootFile,
+ const TCHAR * szListFile,
+ int nDumpLevel)
+{
+ PCASC_ENCODING_ENTRY pEncodingEntry;
+ CASC_ROOT_BLOCK BlockInfo;
+ PLISTFILE_MAP pListMap;
+ QUERY_KEY EncodingKey;
+ LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
+ LPBYTE pbFilePointer;
+ char szOneLine[0x100];
+ DWORD i;
+
+ // Create the listfile map
+ pListMap = ListFile_CreateMap(szListFile);
+
+ // Dump the root entries
+ for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
+ {
+ // Validate the root block
+ pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
+ if(pbFilePointer == NULL)
+ break;
+
+ // Dump the locale block
+ dump_print(dc, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
+ "=========================================================\n",
+ BlockInfo.pLocaleBlockHdr->Flags,
+ BlockInfo.pLocaleBlockHdr->Locales,
+ BlockInfo.pLocaleBlockHdr->NumberOfFiles);
+
+ // Dump the hashes and encoding keys
+ for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
+ {
+ // Dump the entry
+ dump_print(dc, "%08X %08X-%08X %s %s\n",
+ (DWORD)(BlockInfo.FileDataIds[i]),
+ (DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20),
+ (DWORD)(BlockInfo.pRootEntries[i].FileNameHash),
+ StringFromMD5(BlockInfo.pRootEntries[i].EncodingKey.Value, szOneLine),
+ ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash));
+
+ // Find the encoding entry in the encoding table
+ if(nDumpLevel >= DUMP_LEVEL_ENCODING_FILE)
+ {
+ EncodingKey.pbData = BlockInfo.pRootEntries[i].EncodingKey.Value;
+ EncodingKey.cbData = MD5_HASH_SIZE;
+ pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL);
+ CascDumpEncodingEntry(hs, dc, pEncodingEntry, nDumpLevel);
+ }
+ }
+
+ // Put extra newline
+ dump_print(dc, "\n");
+ }
+
+ ListFile_FreeMap(pListMap);
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int RootHandler_CreateWoW6(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask)
+{
+ TRootHandler_WoW6 * pRootHandler;
+ LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
+ int nError;
+
+ // Verify the size
+ if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK))
+ nError = ERROR_FILE_CORRUPT;
+
+ // Allocate the root handler object
+ hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_WoW6, 1);
+ if(pRootHandler == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Fill-in the handler functions
+ memset(pRootHandler, 0, sizeof(TRootHandler_WoW6));
+ pRootHandler->Insert = (ROOT_INSERT)WowHandler_Insert;
+ pRootHandler->Search = (ROOT_SEARCH)WowHandler_Search;
+ pRootHandler->EndSearch = (ROOT_ENDSEARCH)WowHandler_EndSearch;
+ pRootHandler->GetKey = (ROOT_GETKEY)WowHandler_GetKey;
+ pRootHandler->Close = (ROOT_CLOSE)WowHandler_Close;
+
+#ifdef _DEBUG
+ pRootHandler->Dump = TRootHandlerWoW6_Dump; // Support for ROOT file dump
+#endif // _DEBUG
+
+ // Count the files that are going to be loaded
+ ParseWowRootFile(pRootHandler, ParseRoot_CountFiles, pbRootFile, pbRootFileEnd, dwLocaleMask);
+ pRootHandler->dwTotalFileCount += CASC_EXTRA_FILES;
+
+ // Create linear table that will contain all root items
+ nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, pRootHandler->dwTotalFileCount);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Create the map of FileHash ->FileEntry
+ pRootHandler->pRootMap = Map_Create(pRootHandler->dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash));
+ if(pRootHandler->pRootMap == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Parse the root file again and insert all files to the map
+ ParseWowRootFile(pRootHandler, ParseRoot_AddRootEntries, pbRootFile, pbRootFileEnd, dwLocaleMask);
+ return ERROR_SUCCESS;
+}
diff --git a/dep/CascLib/src/common/Common.cpp b/dep/CascLib/src/common/Common.cpp
index 2dbdd470478..9c0f56dc9ba 100644
--- a/dep/CascLib/src/common/Common.cpp
+++ b/dep/CascLib/src/common/Common.cpp
@@ -99,7 +99,7 @@ void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength)
szTarget[cchLength] = 0;
}
-char * NewStr(const char * szString, size_t nCharsToReserve)
+char * CascNewStr(const char * szString, size_t nCharsToReserve)
{
char * szNewString = NULL;
size_t nLength;
@@ -118,7 +118,7 @@ char * NewStr(const char * szString, size_t nCharsToReserve)
return szNewString;
}
-wchar_t * NewStr(const wchar_t * szString, size_t nCharsToReserve)
+wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve)
{
wchar_t * szNewString = NULL;
size_t nLength;
@@ -137,22 +137,20 @@ wchar_t * NewStr(const wchar_t * szString, size_t nCharsToReserve)
return szNewString;
}
-TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd)
+TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd)
{
TCHAR * szNewString = NULL;
- TCHAR * szStringPtr = NULL;
- size_t nLength = (size_t)(pbStringEnd - pbStringBegin);
- if(pbStringEnd > pbStringBegin)
+ // Only if the entry is valid
+ if(szBegin != NULL && szEnd > szBegin)
{
- szNewString = szStringPtr = CASC_ALLOC(TCHAR, nLength + 1);
+ // Allocate and copy the string
+ szNewString = CASC_ALLOC(TCHAR, (szEnd - szBegin + 1));
if(szNewString != NULL)
- {
- CopyString(szStringPtr, (const char *)pbStringBegin, nLength);
- szStringPtr[nLength] = 0;
- }
+ CopyString(szNewString, szBegin, (szEnd - szBegin));
}
+ // Return the string
return szNewString;
}
@@ -218,30 +216,60 @@ TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir)
return szFullPath;
}
-void NormalizeFileName_UpperBkSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars)
+TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength)
{
- char * szTrgFileEnd = szTrgFileName + cchMaxChars;
- size_t i;
+ TCHAR * szFullPath = NULL;
+ TCHAR * szSubDir;
- // Normalize the file name: ToLower + BackSlashToSlash
- for(i = 0; szSrcFileName[i] != 0 && szTrgFileName < szTrgFileEnd; i++)
- szTrgFileName[i] = AsciiToUpperTable_BkSlash[szSrcFileName[i]];
+ // Create the subdir string
+ szSubDir = CASC_ALLOC(TCHAR, nLength + 1);
+ if(szSubDir != NULL)
+ {
+ CopyString(szSubDir, szString, nLength);
+ szFullPath = CombinePath(szPath, szSubDir);
+ CASC_FREE(szSubDir);
+ }
- assert(szSrcFileName[i] == 0);
- szTrgFileName[i] = 0;
+ return szFullPath;
}
-void NormalizeFileName_LowerSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars)
+size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars)
{
- char * szTrgFileEnd = szTrgFileName + cchMaxChars;
+ char * szNormNameEnd = szNormName + cchMaxChars;
size_t i;
// Normalize the file name: ToLower + BackSlashToSlash
- for(i = 0; szSrcFileName[i] != 0 && szTrgFileName < szTrgFileEnd; i++)
- szTrgFileName[i] = AsciiToLowerTable_Slash[szSrcFileName[i]];
+ for(i = 0; szFileName[0] != 0 && szNormName < szNormNameEnd; i++)
+ *szNormName++ = NormTable[*szFileName++];
- assert(szSrcFileName[i] == 0);
- szTrgFileName[i] = 0;
+ // Terminate the string
+ szNormName[0] = 0;
+ return i;
+}
+
+size_t NormalizeFileName_UpperBkSlash(char * szNormName, const char * szFileName, size_t cchMaxChars)
+{
+ return NormalizeFileName(AsciiToUpperTable_BkSlash, szNormName, szFileName, cchMaxChars);
+}
+
+size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, size_t cchMaxChars)
+{
+ return NormalizeFileName(AsciiToLowerTable_Slash, szNormName, szFileName, cchMaxChars);
+}
+
+ULONGLONG CalcFileNameHash(const char * szFileName)
+{
+ char szNormName[MAX_PATH+1];
+ uint32_t dwHashHigh = 0;
+ uint32_t dwHashLow = 0;
+ size_t nLength;
+
+ // Normalize the file name - convert to uppercase, slashes to backslashes
+ nLength = NormalizeFileName_UpperBkSlash(szNormName, szFileName, MAX_PATH);
+
+ // Calculate the HASH value of the normalized file name
+ hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow);
+ return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
}
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue)
@@ -256,6 +284,22 @@ int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue)
return (Digit > 0x0F) ? ERROR_BAD_FORMAT : ERROR_SUCCESS;
}
+int ConvertStringToInt08(const char * szString, PDWORD PtrValue)
+{
+ BYTE DigitOne = AsciiToUpperTable_BkSlash[szString[0]] - '0';
+ BYTE DigitTwo = AsciiToUpperTable_BkSlash[szString[1]] - '0';
+
+ // Fix the digits
+ if(DigitOne > 9)
+ DigitOne -= 'A' - '9' - 1;
+ if(DigitTwo > 9)
+ DigitTwo -= 'A' - '9' - 1;
+
+ // Combine them into a value
+ PtrValue[0] = (DigitOne << 0x04) | DigitTwo;
+ return (DigitOne <= 0x0F && DigitTwo <= 0x0F) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
+}
+
int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrValue)
{
// The number of digits must be even
@@ -290,6 +334,41 @@ int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrVa
return ERROR_SUCCESS;
}
+// Converts string blob to binary blob.
+int ConvertStringToBinary(
+ const char * szString,
+ size_t nMaxDigits,
+ LPBYTE pbBinary)
+{
+ const char * szStringEnd = szString + nMaxDigits;
+ DWORD dwCounter = 0;
+ BYTE DigitValue;
+ BYTE ByteValue = 0;
+
+ // Convert the string
+ while(szString < szStringEnd)
+ {
+ // Retrieve the digit converted to hexa
+ DigitValue = (BYTE)(AsciiToUpperTable_BkSlash[szString[0]] - '0');
+ if(DigitValue > 9)
+ DigitValue -= 'A' - '9' - 1;
+ if(DigitValue > 0x0F)
+ return ERROR_BAD_FORMAT;
+
+ // Insert the digit to the binary buffer
+ ByteValue = (ByteValue << 0x04) | DigitValue;
+ dwCounter++;
+
+ // If we reached the second digit, it means that we need
+ // to flush the byte value and move on
+ if((dwCounter & 0x01) == 0)
+ *pbBinary++ = ByteValue;
+ szString++;
+ }
+
+ return ERROR_SUCCESS;
+}
+
char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer)
{
char * szSaveBuffer = szBuffer;
@@ -308,6 +387,11 @@ char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer)
return szSaveBuffer;
}
+char * StringFromMD5(LPBYTE md5, char * szBuffer)
+{
+ return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
+}
+
//-----------------------------------------------------------------------------
// File name utilities
@@ -341,87 +425,55 @@ const char * GetPlainFileName(const char * szFileName)
bool CheckWildCard(const char * szString, const char * szWildCard)
{
- const char * szSubString;
- int nSubStringLength;
- int nMatchCount = 0;
+ const char * szWildCardPtr;
- // When the mask is empty, it never matches
- if(szWildCard == NULL || *szWildCard == 0)
- return false;
-
- // If the wildcard contains just "*", then it always matches
- if(szWildCard[0] == '*' && szWildCard[1] == 0)
- return true;
-
- // Do normal test
for(;;)
{
// If there is '?' in the wildcard, we skip one char
- while(*szWildCard == '?')
+ while(szWildCard[0] == '?')
{
+ if(szString[0] == 0)
+ return false;
+
szWildCard++;
szString++;
}
- // If there is '*', means zero or more chars. We have to
- // find the sequence after '*'
- if(*szWildCard == '*')
+ // Handle '*'
+ szWildCardPtr = szWildCard;
+ if(szWildCardPtr[0] != 0)
{
- // More stars is equal to one star
- while(*szWildCard == '*' || *szWildCard == '?')
- szWildCard++;
-
- // If we found end of the wildcard, it's a match
- if(*szWildCard == 0)
- return true;
-
- // Determine the length of the substring in szWildCard
- szSubString = szWildCard;
- while(*szSubString != 0 && *szSubString != '?' && *szSubString != '*')
- szSubString++;
- nSubStringLength = (int)(szSubString - szWildCard);
- nMatchCount = 0;
-
- // Now we have to find a substring in szString,
- // that matches the substring in szWildCard
- while(*szString != 0)
+ if(szWildCardPtr[0] == '*')
{
- // Calculate match count
- while(nMatchCount < nSubStringLength)
- {
- if(AsciiToUpperTable_BkSlash[(BYTE)szString[nMatchCount]] != AsciiToUpperTable_BkSlash[(BYTE)szWildCard[nMatchCount]])
- break;
- if(szString[nMatchCount] == 0)
- break;
- nMatchCount++;
- }
+ szWildCardPtr++;
+
+ if(szWildCardPtr[0] == '*')
+ continue;
+
+ if(szWildCardPtr[0] == 0)
+ return true;
- // If the match count has reached substring length, we found a match
- if(nMatchCount == nSubStringLength)
+ if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] == AsciiToUpperTable_BkSlash[szString[0]])
{
- szWildCard += nMatchCount;
- szString += nMatchCount;
- break;
+ if(CheckWildCard(szString, szWildCardPtr))
+ return true;
}
+ }
+ else
+ {
+ if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] != AsciiToUpperTable_BkSlash[szString[0]])
+ return false;
- // No match, move to the next char in szString
- nMatchCount = 0;
- szString++;
+ szWildCard = szWildCardPtr + 1;
}
+
+ if(szString[0] == 0)
+ return false;
+ szString++;
}
else
{
- // If we came to the end of the string, compare it to the wildcard
- if(AsciiToUpperTable_BkSlash[(BYTE)*szString] != AsciiToUpperTable_BkSlash[(BYTE)*szWildCard])
- return false;
-
- // If we arrived to the end of the string, it's a match
- if(*szString == 0)
- return true;
-
- // Otherwise, continue in comparing
- szWildCard++;
- szString++;
+ return (szString[0] == 0) ? true : false;
}
}
}
diff --git a/dep/CascLib/src/common/Common.h b/dep/CascLib/src/common/Common.h
index ae098adb084..484276a3bd7 100644
--- a/dep/CascLib/src/common/Common.h
+++ b/dep/CascLib/src/common/Common.h
@@ -29,33 +29,31 @@ extern unsigned char AsciiToUpperTable_BkSlash[256];
extern unsigned char IntToHexChar[];
//-----------------------------------------------------------------------------
-// GetLastError/SetLastError support for non-Windows platform
-
-#ifndef PLATFORM_WINDOWS
-int GetLastError();
-void SetLastError(int nError);
-#endif // PLATFORM_WINDOWS
-
-//-----------------------------------------------------------------------------
// String manipulation
void CopyString(char * szTarget, const char * szSource, size_t cchLength);
void CopyString(wchar_t * szTarget, const char * szSource, size_t cchLength);
void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength);
-char * NewStr(const char * szString, size_t nCharsToReserve);
-wchar_t * NewStr(const wchar_t * szString, size_t nCharsToReserve);
+char * CascNewStr(const char * szString, size_t nCharsToReserve);
+wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve);
-TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd);
+TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd);
TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir);
+TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength);
+
+size_t NormalizeFileName_UpperBkSlash(char * szNormName, const char * szFileName, size_t cchMaxChars);
+size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, size_t cchMaxChars);
-void NormalizeFileName_UpperBkSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars);
-void NormalizeFileName_LowerSlash(char * szTrgFileName, const char * szSrcFileName, size_t cchMaxChars);
+ULONGLONG CalcFileNameHash(const char * szFileName);
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue);
+int ConvertStringToInt08(const char * szString, PDWORD PtrValue);
int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrValue);
+int ConvertStringToBinary(const char * szString, size_t nMaxDigits, LPBYTE pbBinary);
char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer);
+char * StringFromMD5(LPBYTE md5, char * szBuffer);
//-----------------------------------------------------------------------------
// File name utilities
diff --git a/dep/CascLib/src/common/Directory.h b/dep/CascLib/src/common/Directory.h
new file mode 100644
index 00000000000..ae97dca3d76
--- /dev/null
+++ b/dep/CascLib/src/common/Directory.h
@@ -0,0 +1,29 @@
+/*****************************************************************************/
+/* Directory.h Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Directory functions for CascLib */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 30.10.15 1.00 Lad The first version of Directory.h */
+/*****************************************************************************/
+
+#ifndef __DIRECTORY_H__
+#define __DIRECTORY_H__
+
+//-----------------------------------------------------------------------------
+// Scanning a directory
+
+typedef bool (*INDEX_FILE_FOUND)(const TCHAR * szFileName, PDWORD IndexArray, PDWORD OldIndexArray, void * pvContext);
+
+bool DirectoryExists(const TCHAR * szDirectory);
+
+int ScanIndexDirectory(
+ const TCHAR * szIndexPath,
+ INDEX_FILE_FOUND pfnOnFileFound,
+ PDWORD IndexArray,
+ PDWORD OldIndexArray,
+ void * pvContext
+ );
+
+#endif // __DIRECTORY_H__
diff --git a/dep/CascLib/src/common/DumpContext.cpp b/dep/CascLib/src/common/DumpContext.cpp
new file mode 100644
index 00000000000..8783ff201ba
--- /dev/null
+++ b/dep/CascLib/src/common/DumpContext.cpp
@@ -0,0 +1,153 @@
+/*****************************************************************************/
+/* DumpContext.cpp Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Implementation of dump context */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 16.03.15 1.00 Lad Created */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "../CascLib.h"
+#include "../CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static TCHAR * FormatFileName(TCascStorage * hs, const TCHAR * szNameFormat)
+{
+ TCHAR * szFileName;
+ TCHAR * szSrc;
+ TCHAR * szTrg;
+
+ // Create copy of the file name
+ szFileName = szSrc = szTrg = CascNewStr(szNameFormat, 0);
+ if(szFileName != NULL)
+ {
+ // Format the file name
+ while(szSrc[0] != 0)
+ {
+ if(szSrc[0] == _T('%'))
+ {
+ // Replace "%build%" with a build number
+ if(!_tcsncmp(szSrc, _T("%build%"), 7))
+ {
+ szTrg += _stprintf(szTrg, _T("%u"), hs->dwBuildNumber);
+ szSrc += 7;
+ continue;
+ }
+ }
+
+ // Just copy the character
+ *szTrg++ = *szSrc++;
+ }
+
+ // Terminate the target file name
+ szTrg[0] = 0;
+ }
+
+ return szFileName;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+TDumpContext * CreateDumpContext(TCascStorage * hs, const TCHAR * szNameFormat)
+{
+ TDumpContext * dc;
+ TCHAR * szFileName;
+
+ // Validate the storage handle
+ if(hs != NULL)
+ {
+ // Calculate the number of bytes needed for dump context
+ dc = CASC_ALLOC(TDumpContext, 1);
+ if(dc != NULL)
+ {
+ // Initialize the dump context
+ memset(dc, 0, sizeof(TDumpContext));
+
+ // Format the real file name
+ szFileName = FormatFileName(hs, szNameFormat);
+ if(szFileName != NULL)
+ {
+ // Create the file
+ dc->pStream = FileStream_CreateFile(szFileName, 0);
+ if(dc->pStream != NULL)
+ {
+ // Initialize buffers
+ dc->pbBufferBegin =
+ dc->pbBufferPtr = dc->DumpBuffer;
+ dc->pbBufferEnd = dc->DumpBuffer + CASC_DUMP_BUFFER_SIZE;
+
+ // Success
+ CASC_FREE(szFileName);
+ return dc;
+ }
+
+ // File create failed --> delete the file name
+ CASC_FREE(szFileName);
+ }
+
+ // Free the dump context
+ CASC_FREE(dc);
+ }
+ }
+
+ return NULL;
+}
+
+int dump_print(TDumpContext * dc, const char * szFormat, ...)
+{
+ va_list argList;
+ char szBuffer[0x200];
+ int nLength = 0;
+
+ // Only if the dump context is valid
+ if(dc != NULL)
+ {
+ // Print the buffer using sprintf
+ va_start(argList, szFormat);
+ nLength = vsprintf(szBuffer, szFormat, argList);
+ assert(nLength < 0x200);
+ va_end(argList);
+
+ // Copy the string. Replace "\n" with "\n\r"
+ for(int i = 0; i < nLength; i++)
+ {
+ // Flush the buffer, if needed
+ if((dc->pbBufferPtr + 2) >= dc->pbBufferEnd)
+ {
+ FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin));
+ dc->pbBufferPtr = dc->pbBufferBegin;
+ }
+
+ // Copy the char
+ if(szBuffer[i] == 0x0A)
+ *dc->pbBufferPtr++ = 0x0D;
+ *dc->pbBufferPtr++ = szBuffer[i];
+ }
+ }
+
+ return nLength;
+}
+
+int dump_close(TDumpContext * dc)
+{
+ // Only if the dump context is valid
+ if(dc != NULL)
+ {
+ // Flush the dump context if there are some data
+ if(dc->pbBufferPtr > dc->pbBufferBegin)
+ FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin));
+ dc->pbBufferPtr = dc->pbBufferBegin;
+
+ // Free the file stream and the entire context
+ if(dc->pStream != NULL)
+ FileStream_Close(dc->pStream);
+ CASC_FREE(dc);
+ }
+
+ return 0;
+}
diff --git a/dep/CascLib/src/common/DumpContext.h b/dep/CascLib/src/common/DumpContext.h
new file mode 100644
index 00000000000..6f725f5b942
--- /dev/null
+++ b/dep/CascLib/src/common/DumpContext.h
@@ -0,0 +1,38 @@
+/*****************************************************************************/
+/* DumpContext.h Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Interface for TDumpContext */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 16.03.15 1.00 Lad Created */
+/*****************************************************************************/
+
+#ifndef __DUMP_CONTEXT_H__
+#define __DUMP_CONTEXT_H__
+
+//-----------------------------------------------------------------------------
+// Defines
+
+// Size of the buffer for the dump context
+#define CASC_DUMP_BUFFER_SIZE 0x10000
+
+// Structure for dump context
+struct TDumpContext
+{
+ TFileStream * pStream; // Pointer to the open stream
+ LPBYTE pbBufferBegin; // Begin of the dump buffer
+ LPBYTE pbBufferPtr; // Current dump buffer pointer
+ LPBYTE pbBufferEnd; // End of the dump buffer
+
+ BYTE DumpBuffer[CASC_DUMP_BUFFER_SIZE]; // Dump buffer
+};
+
+//-----------------------------------------------------------------------------
+// Dump context functions
+
+TDumpContext * CreateDumpContext(struct _TCascStorage * hs, const TCHAR * szNameFormat);
+int dump_print(TDumpContext * dc, const char * szFormat, ...);
+int dump_close(TDumpContext * dc);
+
+#endif // __DUMP_CONTEXT_H__
diff --git a/dep/CascLib/src/common/DynamicArray.cpp b/dep/CascLib/src/common/DynamicArray.cpp
new file mode 100644
index 00000000000..e744fbe3912
--- /dev/null
+++ b/dep/CascLib/src/common/DynamicArray.cpp
@@ -0,0 +1,101 @@
+/*****************************************************************************/
+/* DynamicArray.cpp Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Description: */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 30.10.15 1.00 Lad The first version of DynamicArray.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "../CascLib.h"
+#include "../CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static bool EnlargeArray(PDYNAMIC_ARRAY pArray, size_t NewItemCount)
+{
+ char * NewItemArray;
+ size_t ItemCountMax;
+
+ // We expect the array to be already allocated
+ assert(pArray->ItemArray != NULL);
+ assert(pArray->ItemCountMax != 0);
+
+ // Shall we enlarge the table?
+ if(NewItemCount > pArray->ItemCountMax)
+ {
+ // Calculate new table size
+ ItemCountMax = pArray->ItemCountMax;
+ while(ItemCountMax < NewItemCount)
+ ItemCountMax = ItemCountMax << 1;
+
+ // Allocate new table
+ NewItemArray = CASC_REALLOC(char, pArray->ItemArray, pArray->ItemSize * ItemCountMax);
+ if(NewItemArray == NULL)
+ return false;
+
+ // Set the new table size
+ pArray->ItemCountMax = ItemCountMax;
+ pArray->ItemArray = NewItemArray;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax)
+{
+ pArray->ItemArray = CASC_ALLOC(char, (ItemSize * ItemCountMax));
+ if(pArray->ItemArray == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ pArray->ItemCountMax = ItemCountMax;
+ pArray->ItemCount = 0;
+ pArray->ItemSize = ItemSize;
+ return ERROR_SUCCESS;
+}
+
+void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount)
+{
+ char * NewItemPtr;
+
+ // Try to enlarge the buffer, if needed
+ if(!EnlargeArray(pArray, pArray->ItemCount + NewItemCount))
+ return NULL;
+ NewItemPtr = pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize);
+
+ // Copy the old item(s), if any
+ if(NewItems != NULL)
+ memcpy(NewItemPtr, NewItems, (NewItemCount * pArray->ItemSize));
+
+ // Increment the size of the array
+ pArray->ItemCount += NewItemCount;
+ return NewItemPtr;
+}
+
+void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex)
+{
+ assert(ItemIndex < pArray->ItemCount);
+ return pArray->ItemArray + (ItemIndex * pArray->ItemSize);
+}
+
+size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr)
+{
+ char * ArrayItem = (char *)ArrayPtr;
+
+ assert(pArray->ItemArray <= ArrayItem && ArrayItem <= pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize));
+ return ((ArrayItem - pArray->ItemArray) / pArray->ItemSize);
+}
+
+void Array_Free(PDYNAMIC_ARRAY pArray)
+{
+ if(pArray != NULL && pArray->ItemArray != NULL)
+ {
+ CASC_FREE(pArray->ItemArray);
+ }
+}
diff --git a/dep/CascLib/src/common/DynamicArray.h b/dep/CascLib/src/common/DynamicArray.h
new file mode 100644
index 00000000000..11cefacdcc4
--- /dev/null
+++ b/dep/CascLib/src/common/DynamicArray.h
@@ -0,0 +1,37 @@
+/*****************************************************************************/
+/* DynamicArray.h Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Common array implementation */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 30.10.15 1.00 Lad The first version of DynamicArray.h */
+/*****************************************************************************/
+
+#ifndef __DYNAMIC_ARRAY_H__
+#define __DYNAMIC_ARRAY_H__
+
+//-----------------------------------------------------------------------------
+// Structures
+
+typedef struct _DYNAMIC_ARRAY
+{
+ char * ItemArray; // Pointer to items
+ size_t ItemCountMax; // Current number of items
+ size_t ItemCount; // Total size of the buffer
+ size_t ItemSize; // Size of the single item
+
+} DYNAMIC_ARRAY, *PDYNAMIC_ARRAY;
+
+//-----------------------------------------------------------------------------
+// Functions for managing the array
+
+int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax);
+void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount);
+void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex);
+size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr);
+void Array_Free(PDYNAMIC_ARRAY pArray);
+
+#define Array_Create(pArray, type, ItemCountMax) Array_Create_(pArray, sizeof(type), ItemCountMax)
+
+#endif // __DYNAMIC_ARRAY__
diff --git a/dep/CascLib/src/common/FileStream.cpp b/dep/CascLib/src/common/FileStream.cpp
index 8e541b44fca..ef9cc55d7ea 100644
--- a/dep/CascLib/src/common/FileStream.cpp
+++ b/dep/CascLib/src/common/FileStream.cpp
@@ -16,7 +16,6 @@
#define __CASCLIB_SELF__
#include "../CascLib.h"
#include "../CascCommon.h"
-#include "FileStream.h"
#ifdef _MSC_VER
#pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream
diff --git a/dep/CascLib/src/common/ListFile.cpp b/dep/CascLib/src/common/ListFile.cpp
index 42131a2bc2c..b9915f0f31c 100644
--- a/dep/CascLib/src/common/ListFile.cpp
+++ b/dep/CascLib/src/common/ListFile.cpp
@@ -13,117 +13,118 @@
#include "../CascCommon.h"
//-----------------------------------------------------------------------------
-// Listfile entry structure
+// Listfile cache structure
-#define CACHE_BUFFER_SIZE 0x1000 // Size of the cache buffer
+typedef struct _LISTFILE_CACHE
+{
+ char * pBegin; // The begin of the listfile cache
+ char * pPos; // Current position in the cache
+ char * pEnd; // The last character in the file cache
-typedef bool (*RELOAD_CACHE)(void * pvCacheContext, LPBYTE pbBuffer, DWORD dwBytesToRead);
-typedef void (*CLOSE_STREAM)(void * pvCacheContext);
+ // Followed by the cache (variable length)
-struct TListFileCache
-{
- RELOAD_CACHE pfnReloadCache; // Function for reloading the cache
- CLOSE_STREAM pfnCloseStream; // Function for closing the stream
- void * pvCacheContext; // Reload context passed to reload function
- char * szMask; // Self-relative pointer to file mask
- DWORD dwFileSize; // Total size of the cached file
- DWORD dwFilePos; // Position of the cache in the file
- BYTE * pBegin; // The begin of the listfile cache
- BYTE * pPos;
- BYTE * pEnd; // The last character in the file cache
-
- BYTE Buffer[CACHE_BUFFER_SIZE];
-// char MaskBuff[1] // Followed by the name mask (if any)
-};
+} LISTFILE_CACHE, *PLISTFILE_CACHE;
//-----------------------------------------------------------------------------
-// Local functions
+// Creating the listfile cache for the given amount of data
-static bool ReloadCache_ExternalFile(void * pvCacheContext, LPBYTE pbBuffer, DWORD dwBytesToRead)
+static PLISTFILE_CACHE CreateListFileCache(DWORD dwFileSize)
{
- TFileStream * pStream = (TFileStream *)pvCacheContext;
+ PLISTFILE_CACHE pCache;
+
+ // Allocate cache for one file block
+ pCache = (PLISTFILE_CACHE)CASC_ALLOC(BYTE, sizeof(LISTFILE_CACHE) + dwFileSize);
+ if(pCache != NULL)
+ {
+ // Set the initial pointers
+ pCache->pBegin =
+ pCache->pPos = (char *)(pCache + 1);
+ pCache->pEnd = pCache->pBegin + dwFileSize;
+ }
- return FileStream_Read(pStream, NULL, pbBuffer, dwBytesToRead);
+ // Return the cache
+ return pCache;
}
-static void CloseStream_ExternalFile(void * pvCacheContext)
+//-----------------------------------------------------------------------------
+// Functions for parsing an external listfile
+
+void * ListFile_OpenExternal(const TCHAR * szListFile)
{
- TFileStream * pStream = (TFileStream *)pvCacheContext;
+ PLISTFILE_CACHE pCache = NULL;
+ TFileStream * pStream;
+ ULONGLONG FileSize = 0;
+
+ // Open the external listfile
+ pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY);
+ if(pStream != NULL)
+ {
+ // Retrieve the size of the external listfile
+ FileStream_GetSize(pStream, &FileSize);
+ if(0 < FileSize && FileSize <= 0x30000000)
+ {
+ // Create the in-memory cache for the entire listfile
+ // The listfile does not have any data loaded yet
+ pCache = CreateListFileCache((DWORD)FileSize);
+ if(pCache != NULL)
+ {
+ if(!FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize))
+ {
+ ListFile_Free(pCache);
+ pCache = NULL;
+ }
+ }
+ }
- return FileStream_Close(pStream);
+ // Close the file stream
+ FileStream_Close(pStream);
+ }
+
+ return pCache;
}
+void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer)
+{
+ PLISTFILE_CACHE pCache = NULL;
+
+ // Create the in-memory cache for the entire listfile
+ // The listfile does not have any data loaded yet
+ pCache = CreateListFileCache(cbBuffer);
+ if(pCache != NULL)
+ memcpy(pCache->pBegin, pbBuffer, cbBuffer);
-// Reloads the cache. Returns number of characters
-// that has been loaded into the cache.
-static DWORD ReloadListFileCache(TListFileCache * pCache)
+ return pCache;
+}
+
+// Performs the MD5-based check on the listfile
+bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5)
{
- DWORD dwBytesToRead = 0;
+ PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile;
- // Only do something if the cache is empty
- if(pCache->pPos >= pCache->pEnd)
- {
- // Move the file position forward
- pCache->dwFilePos += CACHE_BUFFER_SIZE;
- if(pCache->dwFilePos >= pCache->dwFileSize)
- return 0;
-
- // Get the number of bytes remaining
- dwBytesToRead = pCache->dwFileSize - pCache->dwFilePos;
- if(dwBytesToRead > CACHE_BUFFER_SIZE)
- dwBytesToRead = CACHE_BUFFER_SIZE;
-
- // Load the next data chunk to the cache
- // If we didn't read anything, it might mean that the block
- // of the file is not available
- // We stop reading the file at this point, because the rest
- // of the listfile is unreliable
- if(!pCache->pfnReloadCache(pCache->pvCacheContext, pCache->Buffer, dwBytesToRead))
- return 0;
-
- // Set the buffer pointers
- pCache->pBegin =
- pCache->pPos = &pCache->Buffer[0];
- pCache->pEnd = pCache->pBegin + dwBytesToRead;
- }
+ // Must be at the beginning
+ assert(pCache->pPos == pCache->pBegin);
- return dwBytesToRead;
+ // Verify the MD5 hash for the entire block
+ return VerifyDataBlockHash(pCache->pBegin, (DWORD)(pCache->pEnd - pCache->pBegin), pbHashMD5);
}
-static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, size_t nMaxChars)
+size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd)
{
- char * szLineBegin = szLine;
- char * szLineEnd = szLine + nMaxChars - 1;
+ PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile;
char * szExtraString = NULL;
+ char * szLineBegin;
+ char * szLineEnd;
// Skip newlines, spaces, tabs and another non-printable stuff
- for(;;)
- {
- // If we need to reload the cache, do it
- if(pCache->pPos == pCache->pEnd)
- {
- if(ReloadListFileCache(pCache) == 0)
- break;
- }
-
- // If we found a non-whitespace character, stop
- if(pCache->pPos[0] > 0x20)
- break;
-
- // Skip the character
+ while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20)
pCache->pPos++;
- }
+
+ // Remember the begin of the line
+ szLineBegin = pCache->pPos;
// Copy the remaining characters
- while(szLine < szLineEnd)
+ while(pCache->pPos < pCache->pEnd)
{
- // If we need to reload the cache, do it now and resume copying
- if(pCache->pPos == pCache->pEnd)
- {
- if(ReloadListFileCache(pCache) == 0)
- break;
- }
-
// If we have found a newline, stop loading
if(pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x0A)
break;
@@ -131,119 +132,64 @@ static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, size_t nM
// Blizzard listfiles can also contain information about patch:
// Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326)
if(pCache->pPos[0] == '~')
- szExtraString = szLine;
+ szExtraString = pCache->pPos;
- // Copy the character
- *szLine++ = *pCache->pPos++;
+ // Move the position by one character forward
+ pCache->pPos++;
}
- // Terminate line with zero
- *szLine = 0;
+ // Remember the end of the line
+ szLineEnd = (szExtraString != NULL && szExtraString[0] == '~' && szExtraString[1] == 'P') ? szExtraString : pCache->pPos;
- // If there was extra string after the file name, clear it
- if(szExtraString != NULL)
- {
- if(szExtraString[0] == '~' && szExtraString[1] == 'P')
- {
- szLine = szExtraString;
- *szExtraString = 0;
- }
- }
-
- // Return the length of the line
- return (szLine - szLineBegin);
+ // Give the caller the positions of the begin and end of the line
+ pszLineBegin[0] = szLineBegin;
+ pszLineEnd[0] = szLineEnd;
+ return (size_t)(szLineEnd - szLineBegin);
}
-static TListFileCache * CreateListFileCache(RELOAD_CACHE pfnReloadCache, CLOSE_STREAM pfnCloseStream, void * pvCacheContext, DWORD dwFileSize)
+size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars)
{
- TListFileCache * pCache = NULL;
- DWORD dwBytesToRead;
-
- // Allocate cache for one file block
- pCache = (TListFileCache *)CASC_ALLOC(BYTE, sizeof(TListFileCache));
- if(pCache != NULL)
- {
- // Clear the entire structure
- memset(pCache, 0, sizeof(TListFileCache));
- pCache->pfnReloadCache = pfnReloadCache;
- pCache->pfnCloseStream = pfnCloseStream;
- pCache->pvCacheContext = pvCacheContext;
- pCache->dwFileSize = dwFileSize;
-
- // Load the file cache from the file
- dwBytesToRead = CASCLIB_MIN(CACHE_BUFFER_SIZE, dwFileSize);
- if(pfnReloadCache(pvCacheContext, pCache->Buffer, dwBytesToRead))
- {
- // Allocate pointers
- pCache->pBegin = pCache->pPos = &pCache->Buffer[0];
- pCache->pEnd = pCache->pBegin + dwBytesToRead;
- }
- else
- {
- ListFile_Free(pCache);
- pCache = NULL;
- }
- }
-
- // Return the cache
- return pCache;
-}
-
-//-----------------------------------------------------------------------------
-// Functions for parsing an external listfile
+ const char * szLineBegin = NULL;
+ const char * szLineEnd = NULL;
+ size_t nLength;
-void * ListFile_OpenExternal(const TCHAR * szListFile)
-{
- TListFileCache * pCache;
- TFileStream * pStream;
- ULONGLONG FileSize = 0;
+ // Retrieve the next line
+ nLength = ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd);
- // Open the external listfile
- pStream = FileStream_OpenFile(szListFile, STREAM_FLAG_READ_ONLY);
- if(pStream != NULL)
+ // Check the length
+ if(nLength > nMaxChars)
{
- // Retrieve the size of the external listfile
- FileStream_GetSize(pStream, &FileSize);
- if(0 < FileSize && FileSize <= 0xFFFFFFFF)
- {
- // Create the cache for the listfile
- pCache = CreateListFileCache(ReloadCache_ExternalFile, CloseStream_ExternalFile, pStream, (DWORD)FileSize);
- if(pCache != NULL)
- return pCache;
- }
-
- // Close the file stream
- FileStream_Close(pStream);
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
}
- return NULL;
+ // Copy the line to the user buffer
+ memcpy(szBuffer, szLineBegin, nLength);
+ szBuffer[nLength] = 0;
+ return nLength;
}
size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars)
{
- TListFileCache * pCache = (TListFileCache *)pvListFile;
size_t nLength = 0;
- int nError = ERROR_INVALID_PARAMETER;
+ int nError = ERROR_SUCCESS;
// Check for parameters
- if(pCache != NULL)
+ for(;;)
{
- for(;;)
+ // Read the (next) line
+ nLength = ListFile_GetNextLine(pvListFile, szBuffer, nMaxChars);
+ if(nLength == 0)
{
- // Read the (next) line
- nLength = ReadListFileLine(pCache, szBuffer, nMaxChars);
- if(nLength == 0)
- {
- nError = ERROR_NO_MORE_FILES;
- break;
- }
+ nError = ERROR_NO_MORE_FILES;
+ break;
+ }
- // If some mask entered, check it
- if(CheckWildCard(szBuffer, szMask))
- {
- nError = ERROR_SUCCESS;
- break;
- }
+ // If some mask entered, check it
+ if(CheckWildCard(szBuffer, szMask))
+ {
+ nError = ERROR_SUCCESS;
+ break;
}
}
@@ -254,14 +200,9 @@ size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer,
void ListFile_Free(void * pvListFile)
{
- TListFileCache * pCache = (TListFileCache *)pvListFile;
-
- // Valid parameter check
- if(pCache != NULL)
+ if(pvListFile != NULL)
{
- if(pCache->pfnCloseStream != NULL)
- pCache->pfnCloseStream(pCache->pvCacheContext);
- CASC_FREE(pCache);
+ CASC_FREE(pvListFile);
}
}
@@ -293,11 +234,8 @@ static PLISTFILE_MAP ListMap_Create()
static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szFileName, size_t nLength)
{
PLISTFILE_ENTRY pListEntry;
- char szFileName2[MAX_PATH+1];
size_t cbToAllocate;
size_t cbEntrySize;
- uint32_t dwHashHigh = 0;
- uint32_t dwHashLow = 0;
// Make sure there is enough space in the list map
cbEntrySize = sizeof(LISTFILE_ENTRY) + nLength;
@@ -314,14 +252,10 @@ static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szF
// Get the pointer to the first entry
pListEntry = (PLISTFILE_ENTRY)((LPBYTE)(pListMap + 1) + pListMap->cbBuffer);
-
- // Get the name hash
- NormalizeFileName_UpperBkSlash(szFileName2, szFileName, MAX_PATH);
- hashlittle2(szFileName2, nLength, &dwHashHigh, &dwHashLow);
-
- // Calculate the HASH value of the normalized file name
- pListEntry->FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
+ pListEntry->FileNameHash = CalcFileNameHash(szFileName);
pListEntry->cbEntrySize = (DWORD)cbEntrySize;
+
+ // Copy the file name to the entry
memcpy(pListEntry->szFileName, szFileName, nLength);
pListEntry->szFileName[nLength] = 0;
@@ -357,7 +291,7 @@ static PLISTFILE_MAP ListMap_Finish(PLISTFILE_MAP pListMap)
pbEntry += pListEntry->cbEntrySize;
// Insert the entry to the map
- Map_InsertObject(pMap, pListEntry);
+ Map_InsertObject(pMap, pListEntry, &pListEntry->FileNameHash);
}
return pListMap;
@@ -408,7 +342,7 @@ const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash)
PLISTFILE_ENTRY pListEntry = NULL;
if(pListMap != NULL)
- pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash);
+ pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash, NULL);
return (pListEntry != NULL) ? pListEntry->szFileName : "";
}
diff --git a/dep/CascLib/src/common/ListFile.h b/dep/CascLib/src/common/ListFile.h
index 9815160e1ea..84bec3ed751 100644
--- a/dep/CascLib/src/common/ListFile.h
+++ b/dep/CascLib/src/common/ListFile.h
@@ -37,6 +37,10 @@ typedef struct _LISTFILE_MAP
// Functions for parsing an external listfile
void * ListFile_OpenExternal(const TCHAR * szListFile);
+void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer);
+bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5);
+size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd);
+size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars);
size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars);
void ListFile_Free(void * pvListFile);
diff --git a/dep/CascLib/src/common/Map.cpp b/dep/CascLib/src/common/Map.cpp
index 70697a158ab..30ae8ea0531 100644
--- a/dep/CascLib/src/common/Map.cpp
+++ b/dep/CascLib/src/common/Map.cpp
@@ -15,51 +15,82 @@
//-----------------------------------------------------------------------------
// Local functions
-static DWORD CalcHashIndex(PCASC_MAP pMap, void * pvIdentifier)
+// Returns the extension, right after "."
+static const char * String_GetExtension(const char * szString)
{
+ const char * szExtension = strrchr(szString, '.');
+ return (szExtension != NULL) ? szExtension + 1 : NULL;
+}
+
+static DWORD CalcHashIndex_Key(PCASC_MAP pMap, void * pvKey)
+{
+ LPBYTE pbKey = (LPBYTE)pvKey;
DWORD dwHash = 0x7EEE7EEE;
- // Is it a string table?
- if(pMap->KeyLength == KEY_LENGTH_STRING)
- {
- char * szString = (char *)pvIdentifier;
+ // Construct the hash from the first 8 digits
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[0];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[1];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[2];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[3];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[4];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[5];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[6];
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[7];
- for(size_t i = 0; szString[i] != 0; i++)
- dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ szString[i];
- }
- else
- {
- LPBYTE pbHash = (LPBYTE)pvIdentifier;
+ // Return the hash limited by the table size
+ return (dwHash % pMap->TableSize);
+}
+
+static DWORD CalcHashIndex_String(PCASC_MAP pMap, const char * szString, const char * szStringEnd)
+{
+ LPBYTE pbKeyEnd = (LPBYTE)szStringEnd;
+ LPBYTE pbKey = (LPBYTE)szString;
+ DWORD dwHash = 0x7EEE7EEE;
- // Construct the hash from the first 4 digits
- for(size_t i = 0; i < pMap->KeyLength; i++)
- dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbHash[i];
+ // Hash the string itself
+ while(pbKey < pbKeyEnd)
+ {
+ dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]];
+ pbKey++;
}
// Return the hash limited by the table size
return (dwHash % pMap->TableSize);
}
-static bool CompareIdentifier(PCASC_MAP pMap, void * pvTableEntry, void * pvIdentifier)
+static bool CompareObject_Key(PCASC_MAP pMap, void * pvObject, void * pvKey)
{
- // Is it a string table?
- if(pMap->KeyLength == KEY_LENGTH_STRING)
- {
- char * szTableEntry = (char *)pvTableEntry;
- char * szIdentifier = (char *)pvIdentifier;
+ LPBYTE pbObjectKey = (LPBYTE)pvObject + pMap->KeyOffset;
- return (strcmp(szTableEntry, szIdentifier) == 0);
- }
- else
+ return (memcmp(pbObjectKey, pvKey, pMap->KeyLength) == 0);
+}
+
+static bool CompareObject_String(
+ PCASC_MAP pMap,
+ const char * szExistingString,
+ const char * szString,
+ const char * szStringEnd)
+{
+ // Keep compiler happy
+ CASCLIB_UNUSED(pMap);
+
+ // Compare the whole part, case insensitive
+ while(szString < szStringEnd)
{
- return (memcmp(pvTableEntry, pvIdentifier, pMap->KeyLength) == 0);
+ if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString])
+ return false;
+
+ szExistingString++;
+ szString++;
}
+
+ return true;
}
//-----------------------------------------------------------------------------
// Public functions
-PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwMemberOffset)
+PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset)
{
PCASC_MAP pMap;
size_t cbToAllocate;
@@ -76,7 +107,7 @@ PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwMemberOffset)
memset(pMap, 0, cbToAllocate);
pMap->KeyLength = dwKeyLength;
pMap->TableSize = dwTableSize;
- pMap->MemberOffset = dwMemberOffset;
+ pMap->KeyOffset = dwKeyOffset;
}
// Return the allocated map
@@ -104,24 +135,28 @@ size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray)
return pMap->ItemCount;
}
-void * Map_FindObject(PCASC_MAP pMap, void * pvIdentifier)
+void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex)
{
- void * pvTableEntry;
+ void * pvObject;
DWORD dwHashIndex;
// Verify pointer to the map
if(pMap != NULL)
{
// Construct the main index
- dwHashIndex = CalcHashIndex(pMap, pvIdentifier);
+ dwHashIndex = CalcHashIndex_Key(pMap, pvKey);
while(pMap->HashTable[dwHashIndex] != NULL)
{
// Get the pointer at that position
- pvTableEntry = pMap->HashTable[dwHashIndex];
+ pvObject = pMap->HashTable[dwHashIndex];
// Compare the hash
- if(CompareIdentifier(pMap, pvTableEntry, pvIdentifier))
- return ((LPBYTE)pvTableEntry - pMap->MemberOffset);
+ if(CompareObject_Key(pMap, pvObject, pvKey))
+ {
+ if(PtrIndex != NULL)
+ PtrIndex[0] = dwHashIndex;
+ return pvObject;
+ }
// Move to the next entry
dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
@@ -132,9 +167,47 @@ void * Map_FindObject(PCASC_MAP pMap, void * pvIdentifier)
return NULL;
}
-bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
+bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey)
+{
+ void * pvExistingObject;
+ DWORD dwHashIndex;
+
+ // Verify pointer to the map
+ if(pMap != NULL)
+ {
+ // Limit check
+ if((pMap->ItemCount + 1) >= pMap->TableSize)
+ return false;
+
+ // Construct the hash index
+ dwHashIndex = CalcHashIndex_Key(pMap, pvKey);
+ while(pMap->HashTable[dwHashIndex] != NULL)
+ {
+ // Get the pointer at that position
+ pvExistingObject = pMap->HashTable[dwHashIndex];
+
+ // Check if hash being inserted conflicts with an existing hash
+ if(CompareObject_Key(pMap, pvExistingObject, pvKey))
+ return false;
+
+ // Move to the next entry
+ dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
+ }
+
+ // Insert at that position
+ pMap->HashTable[dwHashIndex] = pvNewObject;
+ pMap->ItemCount++;
+ return true;
+ }
+
+ // Failed
+ return false;
+}
+
+bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension)
{
- void * pvTableEntry;
+ const char * szExistingString;
+ const char * szStringEnd = NULL;
DWORD dwHashIndex;
// Verify pointer to the map
@@ -144,15 +217,21 @@ bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
if((pMap->ItemCount + 1) >= pMap->TableSize)
return false;
+ // Retrieve the length of the string without extension
+ if(bCutExtension)
+ szStringEnd = String_GetExtension(szString);
+ if(szStringEnd == NULL)
+ szStringEnd = szString + strlen(szString);
+
// Construct the hash index
- dwHashIndex = CalcHashIndex(pMap, pvIdentifier);
+ dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd);
while(pMap->HashTable[dwHashIndex] != NULL)
{
// Get the pointer at that position
- pvTableEntry = pMap->HashTable[dwHashIndex];
+ szExistingString = (const char *)pMap->HashTable[dwHashIndex];
// Check if hash being inserted conflicts with an existing hash
- if(CompareIdentifier(pMap, pvTableEntry, pvIdentifier))
+ if(CompareObject_String(pMap, szExistingString, szString, szStringEnd))
return false;
// Move to the next entry
@@ -160,7 +239,7 @@ bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
}
// Insert at that position
- pMap->HashTable[dwHashIndex] = pvIdentifier;
+ pMap->HashTable[dwHashIndex] = (void *)szString;
pMap->ItemCount++;
return true;
}
@@ -169,6 +248,34 @@ bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier)
return false;
}
+const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd)
+{
+ const char * szExistingString;
+ DWORD dwHashIndex;
+
+ // Verify pointer to the map
+ if(pMap != NULL)
+ {
+ // Construct the main index
+ dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd);
+ while(pMap->HashTable[dwHashIndex] != NULL)
+ {
+ // Get the pointer at that position
+ szExistingString = (const char *)pMap->HashTable[dwHashIndex];
+
+ // Compare the hash
+ if(CompareObject_String(pMap, szExistingString, szString, szStringEnd))
+ return szExistingString;
+
+ // Move to the next entry
+ dwHashIndex = (dwHashIndex + 1) % pMap->TableSize;
+ }
+ }
+
+ // Not found, sorry
+ return NULL;
+}
+
void Map_Free(PCASC_MAP pMap)
{
if(pMap != NULL)
diff --git a/dep/CascLib/src/common/Map.h b/dep/CascLib/src/common/Map.h
index 40ea4238b81..7b0c1321e6c 100644
--- a/dep/CascLib/src/common/Map.h
+++ b/dep/CascLib/src/common/Map.h
@@ -20,20 +20,23 @@ typedef struct _CASC_MAP
{
size_t TableSize;
size_t ItemCount; // Number of items in the map
- size_t MemberOffset; // How far is the hash from the begin of the structure (in bytes)
+ size_t KeyOffset; // How far is the hash from the begin of the structure (in bytes)
size_t KeyLength; // Length of the hash key
void * HashTable[1]; // Pointer table
} CASC_MAP, *PCASC_MAP;
+typedef bool (*MAP_COMPARE)(PCASC_MAP pMap, void * pvObject, void * pvKey);
+
//-----------------------------------------------------------------------------
// Functions
-PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwMemberOffset);
+PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset);
size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray);
-void * Map_FindObject(PCASC_MAP pMap, void * pvIdentifier);
-bool Map_InsertObject(PCASC_MAP pMap, void * pvIdentifier);
-void Map_Sort(PCASC_MAP pMap);
+void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex);
+bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey);
+const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd);
+bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension);
void Map_Free(PCASC_MAP pMap);
#endif // __HASHTOPTR_H__
diff --git a/dep/CascLib/src/common/RootHandler.cpp b/dep/CascLib/src/common/RootHandler.cpp
new file mode 100644
index 00000000000..df9953f3ea6
--- /dev/null
+++ b/dep/CascLib/src/common/RootHandler.cpp
@@ -0,0 +1,78 @@
+/*****************************************************************************/
+/* RootHandler.cpp Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Implementation of root handler */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 09.03.15 1.00 Lad Created */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "../CascLib.h"
+#include "../CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Common support
+
+int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey)
+{
+ if(pRootHandler == NULL || pRootHandler->Insert == NULL)
+ return ERROR_NOT_SUPPORTED;
+
+ return pRootHandler->Insert(pRootHandler, szFileName, pbEncodingKey);
+}
+
+LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags)
+{
+ // Check if the root structure is valid at all
+ if(pRootHandler == NULL)
+ return NULL;
+
+ return pRootHandler->Search(pRootHandler, pSearch, PtrFileSize, PtrLocaleFlags);
+}
+
+void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch)
+{
+ // Check if the root structure is valid at all
+ if(pRootHandler != NULL)
+ {
+ pRootHandler->EndSearch(pRootHandler, pSearch);
+ }
+}
+
+LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName)
+{
+ // Check if the root structure is valid at all
+ if(pRootHandler == NULL)
+ return NULL;
+
+ return pRootHandler->GetKey(pRootHandler, szFileName);
+}
+
+void RootHandler_Dump(TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel)
+{
+ TDumpContext * dc;
+
+ // Only if the ROOT provider suports the dump option
+ if(hs->pRootHandler != NULL && hs->pRootHandler->Dump != NULL)
+ {
+ // Create the dump file
+ dc = CreateDumpContext(hs, szNameFormat);
+ if(dc != NULL)
+ {
+ // Dump the content and close the file
+ hs->pRootHandler->Dump(hs, dc, pbRootHandler, cbRootHandler, szListFile, nDumpLevel);
+ dump_close(dc);
+ }
+ }
+}
+
+void RootHandler_Close(TRootHandler * pRootHandler)
+{
+ // Check if the root structure is allocated at all
+ if(pRootHandler != NULL)
+ {
+ pRootHandler->Close(pRootHandler);
+ }
+}
diff --git a/dep/CascLib/src/common/RootHandler.h b/dep/CascLib/src/common/RootHandler.h
new file mode 100644
index 00000000000..e1869e351cc
--- /dev/null
+++ b/dep/CascLib/src/common/RootHandler.h
@@ -0,0 +1,88 @@
+/*****************************************************************************/
+/* RootHandler.h Copyright (c) Ladislav Zezula 2015 */
+/*---------------------------------------------------------------------------*/
+/* Interface for root handlers */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 09.03.15 1.00 Lad Created */
+/*****************************************************************************/
+
+#ifndef __ROOT_HANDLER_H__
+#define __ROOT_HANDLER_H__
+
+//-----------------------------------------------------------------------------
+// Defines
+
+#define CASC_MNDX_ROOT_SIGNATURE 0x58444E4D // 'MNDX'
+#define CASC_DIABLO3_ROOT_SIGNATURE 0x8007D0C4
+#define CASC_OVERWATCH_ROOT_SIGNATURE 0x35444D23 // '#MD5'
+
+#define ROOT_FLAG_HAS_NAMES 0x00000001 // The root file contains file names
+
+#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file
+#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file
+#define DUMP_LEVEL_INDEX_ENTRIES 3 // Dump root file + encoding file + index entries
+
+//-----------------------------------------------------------------------------
+// Root file function prototypes
+
+typedef int (*ROOT_INSERT)(
+ struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
+ const char * szFileName, // Pointer to the file name
+ LPBYTE pbEncodingKey // Pointer to the encoding key of the file name
+ );
+
+typedef LPBYTE (*ROOT_SEARCH)(
+ struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
+ struct _TCascSearch * pSearch, // Pointer to the initialized search structure
+ PDWORD PtrFileSize, // Pointer to receive file size (optional)
+ PDWORD PtrLocaleFlags // Pointer to receive locale flags (optional)
+ );
+
+typedef void (*ROOT_ENDSEARCH)(
+ struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
+ struct _TCascSearch * pSearch // Pointer to the initialized search structure
+ );
+
+typedef LPBYTE (*ROOT_GETKEY)(
+ struct TRootHandler * pRootHandler, // Pointer to an initialized root handler
+ const char * szFileName // Pointer to the name of a file
+ );
+
+typedef void (*ROOT_DUMP)(
+ struct _TCascStorage * hs, // Pointer to the open storage
+ TDumpContext * dc, // Opened dump context
+ LPBYTE pbRootHandler, // Pointer to the loaded ROOT file
+ DWORD cbRootHandler, // Length of the loaded ROOT file
+ const TCHAR * szListFile,
+ int nDumpLevel
+ );
+
+typedef void (*ROOT_CLOSE)(
+ struct TRootHandler * pRootHandler // Pointer to an initialized root handler
+ );
+
+struct TRootHandler
+{
+ ROOT_INSERT Insert; // Inserts an existing file name
+ ROOT_SEARCH Search; // Performs the root file search
+ ROOT_ENDSEARCH EndSearch; // Performs cleanup after searching
+ ROOT_GETKEY GetKey; // Retrieves encoding key for a file name
+ ROOT_DUMP Dump;
+ ROOT_CLOSE Close; // Closing the root file
+
+ DWORD dwRootFlags; // Root flags - see the ROOT_FLAG_XXX
+};
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey);
+LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags);
+void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch);
+LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName);
+void RootHandler_Dump(struct _TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel);
+void RootHandler_Close(TRootHandler * pRootHandler);
+
+#endif // __ROOT_HANDLER_H__
diff --git a/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c b/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c
index 537516d80d9..1cd875f5260 100644
--- a/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c
+++ b/dep/CascLib/src/libtomcrypt/src/misc/crypt_argchk.c
@@ -19,8 +19,8 @@
#if (ARGTYPE == 0)
void crypt_argchk(char *v, char *s, int d)
{
- fprintf(stderr, "LTC_ARGCHK '%s' failure on line %d of file %s\n",
- v, d, s);
+ fprintf(stderr, "LTC_ARGCHK '%s' failure on line %d of file %s\n",
+ v, d, s);
(void)raise(SIGABRT);
}
#endif