mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-18 16:38:42 +01:00
1033 lines
30 KiB
C++
1033 lines
30 KiB
C++
/*****************************************************************************/
|
|
/* 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;
|
|
}
|