aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/CascFiles.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dep/CascLib/src/CascFiles.cpp')
-rw-r--r--dep/CascLib/src/CascFiles.cpp981
1 files changed, 981 insertions, 0 deletions
diff --git a/dep/CascLib/src/CascFiles.cpp b/dep/CascLib/src/CascFiles.cpp
new file mode 100644
index 00000000000..8709ea09e36
--- /dev/null
+++ b/dep/CascLib/src/CascFiles.cpp
@@ -0,0 +1,981 @@
+/*****************************************************************************/
+/* CascFiles.cpp Copyright (c) Ladislav Zezula 2014 */
+/*---------------------------------------------------------------------------*/
+/* Various text file parsers */
+/*---------------------------------------------------------------------------*/
+/* Date Ver Who Comment */
+/* -------- ---- --- ------- */
+/* 29.04.14 1.00 Lad The first version of CascBuildCfg.cpp */
+/* 30.10.15 1.00 Lad Renamed to CascFiles.cpp */
+/*****************************************************************************/
+
+#define __CASCLIB_SELF__
+#include "CascLib.h"
+#include "CascCommon.h"
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+typedef int (*PARSEINFOFILE)(TCascStorage * hs, void * pvListFile);
+
+//-----------------------------------------------------------------------------
+// Local structures
+
+struct TBuildFileInfo
+{
+ const TCHAR * szFileName;
+ CBLD_TYPE BuildFileType;
+};
+
+struct TGameIdString
+{
+ const char * szGameInfo;
+ size_t cchGameInfo;
+ DWORD dwGameInfo;
+};
+
+static const TBuildFileInfo BuildTypes[] =
+{
+ {_T(".build.info"), CascBuildInfo}, // Since HOTS build 30027, the game uses .build.info file for storage info
+ {_T(".build.db"), CascBuildDb}, // Older CASC storages
+ {NULL, CascBuildNone}
+};
+
+static const TCHAR * DataDirs[] =
+{
+ _T("SC2Data"), // Starcraft II (Legacy of the Void) build 38749
+ _T("Data\\Casc"), // Overwatch
+ _T("Data"), // World of Warcraft, Diablo
+ _T("HeroesData"), // Heroes of the Storm
+ _T("BNTData"), // Heroes of the Storm, until build 30414
+ NULL,
+};
+
+static const TGameIdString GameIds[] =
+{
+ {"Hero", 0x04, CASC_GAME_HOTS}, // Alpha build of Heroes of the Storm
+ {"WoW", 0x03, CASC_GAME_WOW6}, // Alpha build of World of Warcraft - Warlords of Draenor
+ {"Diablo3", 0x07, CASC_GAME_DIABLO3}, // Diablo III BETA 2.2.0
+ {"Prometheus", 0x0A, CASC_GAME_OVERWATCH}, // Overwatch BETA since build 24919
+ {"SC2", 0x03, CASC_GAME_STARCRAFT2}, // Starcraft II - Legacy of the Void
+ {NULL, 0, 0},
+};
+
+//-----------------------------------------------------------------------------
+// Local functions
+
+static bool inline IsValueSeparator(const char * szVarValue)
+{
+ return ((0 <= szVarValue[0] && szVarValue[0] <= 0x20) || (szVarValue[0] == '|'));
+}
+
+static bool IsCharDigit(BYTE OneByte)
+{
+ return ('0' <= OneByte && OneByte <= '9');
+}
+
+static DWORD GetLocaleMask(const char * szTag)
+{
+ if(!strcmp(szTag, "enUS"))
+ return CASC_LOCALE_ENUS;
+
+ if(!strcmp(szTag, "koKR"))
+ return CASC_LOCALE_KOKR;
+
+ if(!strcmp(szTag, "frFR"))
+ return CASC_LOCALE_FRFR;
+
+ if(!strcmp(szTag, "deDE"))
+ return CASC_LOCALE_DEDE;
+
+ if(!strcmp(szTag, "zhCN"))
+ return CASC_LOCALE_ZHCN;
+
+ if(!strcmp(szTag, "esES"))
+ return CASC_LOCALE_ESES;
+
+ if(!strcmp(szTag, "zhTW"))
+ return CASC_LOCALE_ZHTW;
+
+ if(!strcmp(szTag, "enGB"))
+ return CASC_LOCALE_ENGB;
+
+ if(!strcmp(szTag, "enCN"))
+ return CASC_LOCALE_ENCN;
+
+ if(!strcmp(szTag, "enTW"))
+ return CASC_LOCALE_ENTW;
+
+ if(!strcmp(szTag, "esMX"))
+ return CASC_LOCALE_ESMX;
+
+ if(!strcmp(szTag, "ruRU"))
+ return CASC_LOCALE_RURU;
+
+ if(!strcmp(szTag, "ptBR"))
+ return CASC_LOCALE_PTBR;
+
+ if(!strcmp(szTag, "itIT"))
+ return CASC_LOCALE_ITIT;
+
+ if(!strcmp(szTag, "ptPT"))
+ return CASC_LOCALE_PTPT;
+
+ return 0;
+}
+
+static bool IsInfoVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName, const char * szVarType)
+{
+ size_t nLength;
+
+ // Check the variable name
+ nLength = strlen(szVarName);
+ if((size_t)(szLineEnd - szLineBegin) > nLength)
+ {
+ // Check the variable name
+ if(!_strnicmp(szLineBegin, szVarName, nLength))
+ {
+ // Skip variable name and the exclamation mark
+ szLineBegin += nLength;
+ if(szLineBegin < szLineEnd && szLineBegin[0] == '!')
+ {
+ // Skip the exclamation mark
+ szLineBegin++;
+
+ // Check the variable type
+ nLength = strlen(szVarType);
+ if((size_t)(szLineEnd - szLineBegin) > nLength)
+ {
+ // Check the variable name
+ if(!_strnicmp(szLineBegin, szVarType, nLength))
+ {
+ // Skip variable type and the doublecolon
+ szLineBegin += nLength;
+ return (szLineBegin < szLineEnd && szLineBegin[0] == ':');
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+static const char * SkipInfoVariable(const char * szLineBegin, const char * szLineEnd)
+{
+ while(szLineBegin < szLineEnd)
+ {
+ if(szLineBegin[0] == '|')
+ return szLineBegin + 1;
+
+ szLineBegin++;
+ }
+
+ return NULL;
+}
+
+static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
+{
+ TCHAR * szIndexPath;
+
+ // Cpmbine the index path
+ szIndexPath = CombinePath(hs->szDataPath, szSubDir);
+ if(DirectoryExists(szIndexPath))
+ {
+ hs->szIndexPath = szIndexPath;
+ return hs->szIndexPath;
+ }
+
+ CASC_FREE(szIndexPath);
+ return NULL;
+}
+
+TCHAR * AppendBlobText(TCHAR * szBuffer, LPBYTE pbData, DWORD cbData, TCHAR chSeparator)
+{
+ // Put the separator, if any
+ if(chSeparator != 0)
+ *szBuffer++ = chSeparator;
+
+ // Copy the blob data as text
+ for(DWORD i = 0; i < cbData; i++)
+ {
+ *szBuffer++ = IntToHexChar[pbData[0] >> 0x04];
+ *szBuffer++ = IntToHexChar[pbData[0] & 0x0F];
+ pbData++;
+ }
+
+ // Terminate the string
+ *szBuffer = 0;
+
+ // Return new buffer position
+ return szBuffer;
+}
+
+static const char * CheckLineVariable(const char * szLineBegin, const char * szLineEnd, const char * szVarName)
+{
+ size_t nLineLength = (size_t)(szLineEnd - szLineBegin);
+ size_t nNameLength = strlen(szVarName);
+
+ // If the line longer than the variable name?
+ if(nLineLength > nNameLength)
+ {
+ if(!_strnicmp((const char *)szLineBegin, szVarName, nNameLength))
+ {
+ // Skip the variable name
+ szLineBegin += nNameLength;
+
+ // Skip the separator(s)
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
+ szLineBegin++;
+
+ // Check if there is "="
+ if(szLineBegin >= szLineEnd || szLineBegin[0] != '=')
+ return NULL;
+ szLineBegin++;
+
+ // Skip the separator(s)
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
+ szLineBegin++;
+
+ // Check if there is "="
+ if(szLineBegin >= szLineEnd)
+ return NULL;
+
+ // Return the begin of the variable
+ return szLineBegin;
+ }
+ }
+
+ return NULL;
+}
+
+static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const char * szLineEnd, bool bHexaValue)
+{
+ const char * szLinePtr = szLineBegin;
+
+ // Sanity checks
+ assert(pVarBlob->pbData == NULL);
+ assert(pVarBlob->cbData == 0);
+
+ // Check length of the variable
+ while(szLinePtr < szLineEnd && szLinePtr[0] != '|')
+ szLinePtr++;
+
+ // Allocate space for the blob
+ if(bHexaValue)
+ {
+ // Initialize the blob
+ pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) / 2);
+ pVarBlob->cbData = (DWORD)((szLinePtr - szLineBegin) / 2);
+ return ConvertStringToBinary(szLineBegin, (size_t)(szLinePtr - szLineBegin), pVarBlob->pbData);
+ }
+
+ // Initialize the blob
+ pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1);
+ pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin);
+
+ // Check for success
+ if(pVarBlob->pbData == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Copy the string
+ memcpy(pVarBlob->pbData, szLineBegin, pVarBlob->cbData);
+ pVarBlob->pbData[pVarBlob->cbData] = 0;
+ return ERROR_SUCCESS;
+}
+
+static void AppendConfigFilePath(TCHAR * szFileName, PQUERY_KEY pFileKey)
+{
+ size_t nLength = _tcslen(szFileName);
+
+ // If there is no slash, append if
+ if(nLength > 0 && szFileName[nLength - 1] != '\\' && szFileName[nLength - 1] != '/')
+ szFileName[nLength++] = _T('/');
+
+ // Get to the end of the file name
+ szFileName = szFileName + nLength;
+
+ // Append the "config" directory
+ _tcscpy(szFileName, _T("config"));
+ szFileName += 6;
+
+ // Append the first level directory
+ szFileName = AppendBlobText(szFileName, pFileKey->pbData, 1, _T('/'));
+ szFileName = AppendBlobText(szFileName, pFileKey->pbData + 1, 1, _T('/'));
+ szFileName = AppendBlobText(szFileName, pFileKey->pbData, pFileKey->cbData, _T('/'));
+}
+
+static DWORD GetBlobCount(const char * szLineBegin, const char * szLineEnd)
+{
+ DWORD dwBlobCount = 0;
+
+ // Until we find an end of the line
+ while(szLineBegin < szLineEnd)
+ {
+ // Skip the blob
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin) == false)
+ szLineBegin++;
+
+ // Increment the number of blobs
+ dwBlobCount++;
+
+ // Skip the separator
+ while(szLineBegin < szLineEnd && IsValueSeparator(szLineBegin))
+ szLineBegin++;
+ }
+
+ return dwBlobCount;
+}
+
+static int LoadBlobArray(
+ PQUERY_KEY pBlob,
+ const char * szLineBegin,
+ const char * szLineEnd,
+ DWORD dwMaxBlobs)
+{
+ LPBYTE pbBufferEnd = pBlob->pbData + pBlob->cbData;
+ LPBYTE pbBuffer = pBlob->pbData;
+ int nError = ERROR_SUCCESS;
+
+ // Sanity check
+ assert(pBlob->pbData != NULL);
+ assert(pBlob->cbData != 0);
+
+ // Until we find an end of the line
+ while(szLineBegin < szLineEnd && dwMaxBlobs > 0)
+ {
+ const char * szBlobEnd = szLineBegin;
+
+ // Find the end of the text blob
+ while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd) == false)
+ szBlobEnd++;
+
+ // Verify the length of the found blob
+ if((szBlobEnd - szLineBegin) != MD5_STRING_SIZE)
+ return ERROR_BAD_FORMAT;
+
+ // Verify if there is enough space in the buffer
+ if((pbBufferEnd - pbBuffer) < MD5_HASH_SIZE)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Perform the conversion
+ nError = ConvertStringToBinary(szLineBegin, MD5_STRING_SIZE, pbBuffer);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Move pointers
+ pbBuffer += MD5_HASH_SIZE;
+ dwMaxBlobs--;
+
+ // Skip the separator
+ while(szBlobEnd < szLineEnd && IsValueSeparator(szBlobEnd))
+ szBlobEnd++;
+ szLineBegin = szBlobEnd;
+ }
+
+ return nError;
+}
+
+static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd, DWORD dwBlobCount)
+{
+ size_t nLength = (szLineEnd - szLineBegin);
+
+ // We expect each blob to have length of the encoding key and one space between
+ if(nLength > (dwBlobCount * MD5_STRING_SIZE) + ((dwBlobCount - 1) * sizeof(char)))
+ return ERROR_INVALID_PARAMETER;
+
+ // Allocate the blob buffer
+ pBlob->pbData = CASC_ALLOC(BYTE, dwBlobCount * MD5_HASH_SIZE);
+ if(pBlob->pbData == NULL)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ // Set the buffer size and load the blob array
+ pBlob->cbData = dwBlobCount * MD5_HASH_SIZE;
+ return LoadBlobArray(pBlob, szLineBegin, szLineEnd, dwBlobCount);
+}
+
+static int LoadMultipleBlobs(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd)
+{
+ return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, GetBlobCount(szLineBegin, szLineEnd));
+}
+
+static int LoadSingleBlob(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd)
+{
+ return LoadMultipleBlobs(pBlob, szLineBegin, szLineEnd, 1);
+}
+
+static int GetGameType(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd)
+{
+ // Go through all games that we support
+ for(size_t i = 0; GameIds[i].szGameInfo != NULL; i++)
+ {
+ // Check the length of the variable
+ if((size_t)(szLineEnd - szVarBegin) == GameIds[i].cchGameInfo)
+ {
+ // Check the string
+ if(!_strnicmp(szVarBegin, GameIds[i].szGameInfo, GameIds[i].cchGameInfo))
+ {
+ hs->dwGameInfo = GameIds[i].dwGameInfo;
+ return ERROR_SUCCESS;
+ }
+ }
+ }
+
+ // Unknown/unsupported game
+ assert(false);
+ return ERROR_BAD_FORMAT;
+}
+
+// "B29049"
+// "WOW-18125patch6.0.1"
+// "30013_Win32_2_2_0_Ptr_ptr"
+// "prometheus-0_8_0_0-24919"
+static int GetBuildNumber(TCascStorage * hs, const char * szVarBegin, const char * szLineEnd)
+{
+ DWORD dwBuildNumber = 0;
+
+ // Skip all non-digit characters
+ while(szVarBegin < szLineEnd)
+ {
+ // There must be at least three digits (build 99 anyone?)
+ if(IsCharDigit(szVarBegin[0]) && IsCharDigit(szVarBegin[1]) && IsCharDigit(szVarBegin[2]))
+ {
+ // Convert the build number string to value
+ while(szVarBegin < szLineEnd && IsCharDigit(szVarBegin[0]))
+ dwBuildNumber = (dwBuildNumber * 10) + (*szVarBegin++ - '0');
+ break;
+ }
+
+ // Move to the next
+ szVarBegin++;
+ }
+
+ assert(dwBuildNumber != 0);
+ hs->dwBuildNumber = dwBuildNumber;
+ return (dwBuildNumber != 0) ? ERROR_SUCCESS : ERROR_BAD_FORMAT;
+}
+
+static int GetDefaultLocaleMask(TCascStorage * hs, PQUERY_KEY pTagsString)
+{
+ char * szTagEnd = (char *)pTagsString->pbData + pTagsString->cbData;
+ char * szTagPtr = (char *)pTagsString->pbData;
+ char * szNext;
+ DWORD dwLocaleMask = 0;
+
+ while(szTagPtr < szTagEnd)
+ {
+ // Get the next part
+ szNext = strchr(szTagPtr, ' ');
+ if(szNext != NULL)
+ *szNext++ = 0;
+
+ // Check whether the current tag is a language identifier
+ dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr);
+
+ // Get the next part
+ if(szNext == NULL)
+ break;
+
+ // Skip spaces
+ while(szNext < szTagEnd && szNext[0] == ' ')
+ szNext++;
+ szTagPtr = szNext;
+ }
+
+ hs->dwDefaultLocale = dwLocaleMask;
+ return ERROR_SUCCESS;
+}
+
+static void * FetchAndVerifyConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey)
+{
+ TCHAR * szFileName;
+ void * pvListFile = NULL;
+
+ // Construct the local file name
+ szFileName = CascNewStr(hs->szDataPath, 8 + 3 + 3 + 32);
+ if(szFileName != NULL)
+ {
+ // Add the part where the config file path is
+ AppendConfigFilePath(szFileName, pFileKey);
+
+ // Load and verify the external listfile
+ pvListFile = ListFile_OpenExternal(szFileName);
+ if(pvListFile != NULL)
+ {
+ if(!ListFile_VerifyMD5(pvListFile, pFileKey->pbData))
+ {
+ ListFile_Free(pvListFile);
+ pvListFile = NULL;
+ }
+ }
+
+ // Free the file name
+ CASC_FREE(szFileName);
+ }
+
+ return pvListFile;
+}
+
+static int ParseFile_BuildInfo(TCascStorage * hs, void * pvListFile)
+{
+ QUERY_KEY Active = {NULL, 0};
+ QUERY_KEY TagString = {NULL, 0};
+ QUERY_KEY CdnHost = {NULL, 0};
+ QUERY_KEY CdnPath = {NULL, 0};
+ char szOneLine1[0x200];
+ char szOneLine2[0x200];
+ size_t nLength1;
+ size_t nLength2;
+ int nError = ERROR_BAD_FORMAT;
+
+ // Extract the first line, cotaining the headers
+ nLength1 = ListFile_GetNextLine(pvListFile, szOneLine1, _maxchars(szOneLine1));
+ if(nLength1 == 0)
+ return ERROR_BAD_FORMAT;
+
+ // Now parse the second and the next lines. We are looking for line
+ // with "Active" set to 1
+ for(;;)
+ {
+ const char * szLinePtr1 = szOneLine1;
+ const char * szLineEnd1 = szOneLine1 + nLength1;
+ const char * szLinePtr2 = szOneLine2;
+ const char * szLineEnd2;
+
+ // Read the next line
+ nLength2 = ListFile_GetNextLine(pvListFile, szOneLine2, _maxchars(szOneLine2));
+ if(nLength2 == 0)
+ break;
+ szLineEnd2 = szLinePtr2 + nLength2;
+
+ // Parse all variables
+ while(szLinePtr1 < szLineEnd1)
+ {
+ // Check for variables we need
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "Active", "DEC"))
+ LoadInfoVariable(&Active, szLinePtr2, szLineEnd2, false);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "Build Key", "HEX"))
+ LoadInfoVariable(&hs->CdnBuildKey, szLinePtr2, szLineEnd2, true);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Key", "HEX"))
+ LoadInfoVariable(&hs->CdnConfigKey, szLinePtr2, szLineEnd2, true);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Hosts", "STRING"))
+ LoadInfoVariable(&CdnHost, szLinePtr2, szLineEnd2, false);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "CDN Path", "STRING"))
+ LoadInfoVariable(&CdnPath, szLinePtr2, szLineEnd2, false);
+ if(IsInfoVariable(szLinePtr1, szLineEnd1, "Tags", "STRING"))
+ LoadInfoVariable(&TagString, szLinePtr2, szLineEnd2, false);
+
+ // Move both line pointers
+ szLinePtr1 = SkipInfoVariable(szLinePtr1, szLineEnd1);
+ if(szLinePtr1 == NULL)
+ break;
+
+ szLinePtr2 = SkipInfoVariable(szLinePtr2, szLineEnd2);
+ if(szLinePtr2 == NULL)
+ break;
+ }
+
+ // Stop parsing if found active config
+ if(Active.pbData != NULL && *Active.pbData == '1')
+ break;
+
+ // Free the blobs
+ FreeCascBlob(&Active);
+ FreeCascBlob(&hs->CdnBuildKey);
+ FreeCascBlob(&hs->CdnConfigKey);
+ FreeCascBlob(&CdnHost);
+ FreeCascBlob(&CdnPath);
+ FreeCascBlob(&TagString);
+ }
+
+ // All four must be present
+ if(hs->CdnBuildKey.pbData != NULL &&
+ hs->CdnConfigKey.pbData != NULL &&
+ CdnHost.pbData != NULL &&
+ CdnPath.pbData != NULL)
+ {
+ // Merge the CDN host and CDN path
+ hs->szUrlPath = CASC_ALLOC(TCHAR, CdnHost.cbData + CdnPath.cbData + 1);
+ if(hs->szUrlPath != NULL)
+ {
+ CopyString(hs->szUrlPath, (char *)CdnHost.pbData, CdnHost.cbData);
+ CopyString(hs->szUrlPath + CdnHost.cbData, (char *)CdnPath.pbData, CdnPath.cbData);
+ nError = ERROR_SUCCESS;
+ }
+ }
+
+ // If we found tags, we can extract language build from it
+ if(TagString.pbData != NULL)
+ GetDefaultLocaleMask(hs, &TagString);
+
+ FreeCascBlob(&CdnHost);
+ FreeCascBlob(&CdnPath);
+ FreeCascBlob(&TagString);
+ FreeCascBlob(&Active);
+ return nError;
+}
+
+static int ParseFile_BuildDb(TCascStorage * hs, void * pvListFile)
+{
+ const char * szLinePtr;
+ const char * szLineEnd;
+ char szOneLine[0x200];
+ size_t nLength;
+ int nError;
+
+ // Load the single line from the text file
+ nLength = ListFile_GetNextLine(pvListFile, szOneLine, _maxchars(szOneLine));
+ if(nLength == 0)
+ return ERROR_BAD_FORMAT;
+
+ // Set the line range
+ szLinePtr = szOneLine;
+ szLineEnd = szOneLine + nLength;
+
+ // Extract the CDN build key
+ nError = LoadInfoVariable(&hs->CdnBuildKey, szLinePtr, szLineEnd, true);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Skip the variable
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Load the CDN config hash
+ nError = LoadInfoVariable(&hs->CdnConfigKey, szLinePtr, szLineEnd, true);
+ if(nError == ERROR_SUCCESS)
+ {
+ // Skip the variable
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Skip the Locale/OS/code variable
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Load the URL
+ hs->szUrlPath = CascNewStrFromAnsi(szLinePtr, szLineEnd);
+ if(hs->szUrlPath == NULL)
+ nError = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Verify all variables
+ if(hs->CdnBuildKey.pbData == NULL || hs->CdnConfigKey.pbData == NULL || hs->szUrlPath == NULL)
+ nError = ERROR_BAD_FORMAT;
+ return nError;
+}
+
+static int LoadCdnConfigFile(TCascStorage * hs, void * pvListFile)
+{
+ const char * szLineBegin;
+ const char * szVarBegin;
+ const char * szLineEnd;
+ int nError = ERROR_SUCCESS;
+
+ // Keep parsing the listfile while there is something in there
+ for(;;)
+ {
+ // Get the next line
+ if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd))
+ break;
+
+ // Archive group
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archive-group");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadSingleBlob(&hs->ArchivesGroup, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Archives
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "archives");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadMultipleBlobs(&hs->ArchivesKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Patch archive group
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archive-group");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->PatchArchivesKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Patch archives
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch-archives");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadMultipleBlobs(&hs->PatchArchivesKey, szVarBegin, szLineEnd);
+ continue;
+ }
+ }
+
+ // Check if all required fields are present
+ if(hs->ArchivesKey.pbData == NULL || hs->ArchivesKey.cbData == 0)
+ return ERROR_BAD_FORMAT;
+
+ return nError;
+}
+
+static int LoadCdnBuildFile(TCascStorage * hs, void * pvListFile)
+{
+ const char * szLineBegin;
+ const char * szVarBegin;
+ const char * szLineEnd = NULL;
+ int nError = ERROR_SUCCESS;
+
+ for(;;)
+ {
+ // Get the next line
+ if(!ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd))
+ break;
+
+ // Game name
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-product");
+ if(szVarBegin != NULL)
+ {
+ GetGameType(hs, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Game build number
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "build-name");
+ if(szVarBegin != NULL)
+ {
+ GetBuildNumber(hs, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Root
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "root");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->RootKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Patch
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "patch");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->PatchKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Download
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "download");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->DownloadKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Install
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "install");
+ if(szVarBegin != NULL)
+ {
+ LoadSingleBlob(&hs->InstallKey, szVarBegin, szLineEnd);
+ continue;
+ }
+
+ // Encoding keys
+ szVarBegin = CheckLineVariable(szLineBegin, szLineEnd, "encoding");
+ if(szVarBegin != NULL)
+ {
+ nError = LoadMultipleBlobs(&hs->EncodingKey, szVarBegin, szLineEnd, 2);
+ continue;
+ }
+ }
+
+ // Check the encoding keys
+ if(hs->EncodingKey.pbData == NULL || hs->EncodingKey.cbData != MD5_HASH_SIZE * 2)
+ return ERROR_BAD_FORMAT;
+ return nError;
+}
+
+static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory)
+{
+ TCHAR * szDataPath;
+ int nError = ERROR_FILE_NOT_FOUND;
+
+ // Try all known subdirectories
+ for(size_t i = 0; DataDirs[i] != NULL; i++)
+ {
+ // Create the eventual data path
+ szDataPath = CombinePath(szDirectory, DataDirs[i]);
+ if(szDataPath != NULL)
+ {
+ // Does that directory exist?
+ if(DirectoryExists(szDataPath))
+ {
+ hs->szDataPath = szDataPath;
+ return ERROR_SUCCESS;
+ }
+
+ // Free the data path
+ CASC_FREE(szDataPath);
+ }
+ }
+
+ return nError;
+}
+
+
+//-----------------------------------------------------------------------------
+// Public functions
+
+int LoadBuildInfo(TCascStorage * hs)
+{
+ PARSEINFOFILE PfnParseProc = NULL;
+ void * pvListFile;
+ int nError = ERROR_SUCCESS;
+
+ switch(hs->BuildFileType)
+ {
+ case CascBuildInfo:
+ PfnParseProc = ParseFile_BuildInfo;
+ break;
+
+ case CascBuildDb:
+ PfnParseProc = ParseFile_BuildDb;
+ break;
+
+ default:
+ nError = ERROR_NOT_SUPPORTED;
+ break;
+ }
+
+ // Parse the appropriate build file
+ if(nError == ERROR_SUCCESS)
+ {
+ pvListFile = ListFile_OpenExternal(hs->szBuildFile);
+ if(pvListFile != NULL)
+ {
+ // Parse the info file
+ nError = PfnParseProc(hs, pvListFile);
+ ListFile_Free(pvListFile);
+ }
+ else
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // If the .build.info OR .build.db file has been loaded,
+ // proceed with loading the CDN config file and CDN build file
+ if(nError == ERROR_SUCCESS)
+ {
+ // Load the configuration file
+ pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnConfigKey);
+ if(pvListFile != NULL)
+ {
+ nError = LoadCdnConfigFile(hs, pvListFile);
+ ListFile_Free(pvListFile);
+ }
+ else
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // Load the build file
+ if(nError == ERROR_SUCCESS)
+ {
+ pvListFile = FetchAndVerifyConfigFile(hs, &hs->CdnBuildKey);
+ if(pvListFile != NULL)
+ {
+ nError = LoadCdnBuildFile(hs, pvListFile);
+ ListFile_Free(pvListFile);
+ }
+ else
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ // Fill the index directory
+ if(nError == ERROR_SUCCESS)
+ {
+ // First, check for more common "data" subdirectory
+ if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
+ return ERROR_SUCCESS;
+
+ // Second, try the "darch" subdirectory (older builds of HOTS - Alpha)
+ if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
+ return ERROR_SUCCESS;
+
+ nError = ERROR_FILE_NOT_FOUND;
+ }
+
+ return nError;
+}
+
+// Checks whether there is a ".agent.db". If yes, the function
+// sets "szRootPath" and "szDataPath" in the storage structure
+// and returns ERROR_SUCCESS
+int CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory)
+{
+ TFileStream * pStream;
+ TCHAR * szBuildFile;
+ int nError = ERROR_FILE_NOT_FOUND;
+
+ // Try to find any of the root files used in the history
+ for(size_t i = 0; BuildTypes[i].szFileName != NULL; i++)
+ {
+ // Create the full name of the .agent.db file
+ szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName);
+ if(szBuildFile != NULL)
+ {
+ // Attempt to open the file
+ pStream = FileStream_OpenFile(szBuildFile, 0);
+ if(pStream != NULL)
+ {
+ // Free the stream
+ FileStream_Close(pStream);
+
+ // Check for the data directory
+ nError = CheckDataDirectory(hs, szDirectory);
+ if(nError == ERROR_SUCCESS)
+ {
+ hs->szBuildFile = szBuildFile;
+ hs->BuildFileType = BuildTypes[i].BuildFileType;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ CASC_FREE(szBuildFile);
+ }
+ }
+
+ return nError;
+}
+
+// Parses single line from Overwatch.
+// The line structure is: "#MD5|CHUNK_ID|FILENAME|INSTALLPATH"
+// The line has all preceding spaces removed
+int ParseRootFileLine(const char * szLinePtr, const char * szLineEnd, PQUERY_KEY PtrEncodingKey, char * szFileName, size_t nMaxChars)
+{
+ size_t nLength;
+ int nError;
+
+ // Check the MD5 (aka encoding key)
+ if(szLinePtr[MD5_STRING_SIZE] != '|')
+ return ERROR_BAD_FORMAT;
+
+ // Convert the encoding key to binary
+ PtrEncodingKey->cbData = MD5_HASH_SIZE;
+ nError = ConvertStringToBinary(szLinePtr, MD5_STRING_SIZE, PtrEncodingKey->pbData);
+ if(nError != ERROR_SUCCESS)
+ return nError;
+
+ // Skip the MD5
+ szLinePtr += MD5_STRING_SIZE+1;
+
+ // Skip the chunk ID
+ szLinePtr = SkipInfoVariable(szLinePtr, szLineEnd);
+
+ // Get the archived file name
+ szLineEnd = SkipInfoVariable(szLinePtr, szLineEnd);
+ nLength = (size_t)(szLineEnd - szLinePtr - 1);
+
+ // Get the file name
+ if(nLength > nMaxChars)
+ return ERROR_INSUFFICIENT_BUFFER;
+
+ memcpy(szFileName, szLinePtr, nLength);
+ szFileName[nLength] = 0;
+ return ERROR_SUCCESS;
+}