/*****************************************************************************/ /* 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; }