diff --git a/dep/CascLib/CMakeLists.txt b/dep/CascLib/CMakeLists.txt index 429ebfb78e8d7..83b7ea3849358 100644 --- a/dep/CascLib/CMakeLists.txt +++ b/dep/CascLib/CMakeLists.txt @@ -1,7 +1,7 @@ set(HEADER_FILES src/CascCommon.h src/CascLib.h - src/CascMndxRoot.h + src/CascMndx.h src/CascPort.h src/common/Common.h src/common/FileStream.h @@ -13,19 +13,26 @@ set(HEADER_FILES set(SRC_FILES src/common/Common.cpp src/common/Directory.cpp + src/common/DumpContext.cpp + src/common/DynamicArray.cpp src/common/FileStream.cpp src/common/ListFile.cpp src/common/Map.cpp + src/common/RootHandler.cpp src/jenkins/lookup3.c - src/CascBuildCfg.cpp src/CascCommon.cpp src/CascDecompress.cpp + src/CascDecrypt.cpp src/CascDumpData.cpp + src/CascFiles.cpp src/CascFindFile.cpp - src/CascMndxRoot.cpp src/CascOpenFile.cpp src/CascOpenStorage.cpp src/CascReadFile.cpp + src/CascRootFile_Diablo3.cpp + src/CascRootFile_Mndx.cpp + src/CascRootFile_Ovr.cpp + src/CascRootFile_WoW6.cpp ) set(TOMCRYPT_FILES diff --git a/dep/CascLib/src/CascBuildCfg.cpp b/dep/CascLib/src/CascBuildCfg.cpp deleted file mode 100644 index f39f09d886ac7..0000000000000 --- 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 8ad7d716b8281..34c3df66b5cdf 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 7a2b4071dc642..42e158bfcb259 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 2858bcec5ab17..290b08d64d85e 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 0000000000000..5f4dc77dfe87f --- /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 5dc9110b6cdfe..3c0e385ac07aa 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 @@ -49,160 +49,6 @@ static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1, return 0; } -//----------------------------------------------------------------------------- -// 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 @@ -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 0000000000000..8709ea09e360c --- /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 0bfe16cae1d1c..bea2e30874717 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 0000000000000..cb5f9166e49a1 --- /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 330a4b2bb49c1..bad32eb2dba8d 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 similarity index 95% rename from dep/CascLib/src/CascMndxRoot.h rename to dep/CascLib/src/CascMndx.h index bd93f230845a7..d1b6653d4fe40 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 2b8c3d3c4ada7..c4c27a3f6a656 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 6a8d83ee9034a..c3d623df9f004 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 3bf1efde4b80e..5d0190e07ccb2 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 83fdd0d209699..72eb2c4b647b3 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 0000000000000..98a42cc3226c3 --- /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 similarity index 91% rename from dep/CascLib/src/CascMndxRoot.cpp rename to dep/CascLib/src/CascRootFile_Mndx.cpp index 328afee8ba598..bf17290a0afb9 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 similarity index 98% rename from dep/CascLib/src/CascMndxRoot_x86.asm rename to dep/CascLib/src/CascRootFile_Mndx_x86.asm index c0e787d0872fc..bf6d6d0b48988 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 0000000000000..205353c0120d6 --- /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 0000000000000..0c631f82b00e4 --- /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 2dbdd47047869..9c0f56dc9ba50 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 ae098adb084b5..484276a3bd70d 100644 --- a/dep/CascLib/src/common/Common.h +++ b/dep/CascLib/src/common/Common.h @@ -28,14 +28,6 @@ extern unsigned char AsciiToLowerTable_Slash[256]; 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 @@ -43,19 +35,25 @@ 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 0000000000000..ae97dca3d7601 --- /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 0000000000000..8783ff201ba2b --- /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 0000000000000..6f725f5b942da --- /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 0000000000000..e744fbe39122e --- /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 0000000000000..11cefacdcc4fe --- /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 8e541b44fcaa8..ef9cc55d7eaae 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 42131a2bc2ca5..b9915f0f31ccf 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 9815160e1ea92..84bec3ed751ad 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 70697a158ab64..30ae8ea053182 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 40ea4238b813d..7b0c1321e6cc9 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 0000000000000..df9953f3ea6c7 --- /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 0000000000000..e1869e351cca0 --- /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 537516d80d9e4..1cd875f52607f 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