aboutsummaryrefslogtreecommitdiff
path: root/dep
diff options
context:
space:
mode:
Diffstat (limited to 'dep')
-rw-r--r--dep/CascLib/src/CascCommon.h16
-rw-r--r--dep/CascLib/src/CascDumpData.cpp1
-rw-r--r--dep/CascLib/src/CascFiles.cpp299
-rw-r--r--dep/CascLib/src/CascLib.h46
-rw-r--r--dep/CascLib/src/CascOpenStorage.cpp294
-rw-r--r--dep/CascLib/src/CascPort.h13
-rw-r--r--dep/CascLib/src/CascReadFile.cpp39
-rw-r--r--dep/CascLib/src/CascRootFile_TVFS.cpp127
-rw-r--r--dep/CascLib/src/DllMain.def8
-rw-r--r--dep/CascLib/src/common/Array.h2
-rw-r--r--dep/CascLib/src/common/Common.h11
-rw-r--r--dep/CascLib/src/common/Csv.cpp6
-rw-r--r--dep/CascLib/src/common/FileStream.cpp101
-rw-r--r--dep/CascLib/src/common/FileTree.cpp2
-rw-r--r--dep/CascLib/src/common/Mime.cpp20
-rw-r--r--dep/CascLib/src/common/Mime.h15
-rw-r--r--dep/CascLib/src/common/Path.h16
-rw-r--r--dep/CascLib/src/common/RootHandler.cpp39
-rw-r--r--dep/CascLib/src/common/RootHandler.h24
-rw-r--r--dep/CascLib/src/common/Sockets.cpp67
-rw-r--r--dep/PackageList.txt2
21 files changed, 815 insertions, 333 deletions
diff --git a/dep/CascLib/src/CascCommon.h b/dep/CascLib/src/CascCommon.h
index cddd0a8ca0e..44f94b0b40d 100644
--- a/dep/CascLib/src/CascCommon.h
+++ b/dep/CascLib/src/CascCommon.h
@@ -15,10 +15,10 @@
// Compression support
// Include functions from zlib
-#ifndef __SYS_ZLIB
- #include "zlib/zlib.h"
+#ifndef CASC_USE_SYSTEM_ZLIB
+ #include "zlib/zlib.h"
#else
- #include <zlib.h>
+ #include <zlib.h>
#endif
#include "CascPort.h"
@@ -62,6 +62,9 @@
// For CASC_CDN_DOWNLOAD::Flags
#define CASC_CDN_FORCE_DOWNLOAD 0x0001 // Force downloading the file even if in the cache
+// The maximum size of an inline file
+#define CASC_MAX_ONLINE_FILE_SIZE 0x40000000
+
//-----------------------------------------------------------------------------
// In-memory structures
@@ -279,6 +282,7 @@ struct TCascStorage
CASC_LOCK StorageLock; // Lock for multi-threaded operations
LPCTSTR szIndexFormat; // Format of the index file name
+ LPTSTR szCdnHostUrl; // CDN host URL for online storage
LPTSTR szCodeName; // On local storage, this select a product in a multi-product storage. For online storage, this selects a product
LPTSTR szRootPath; // Path where the build file is
LPTSTR szDataPath; // This is the directory where data files are
@@ -382,8 +386,9 @@ struct TCascSearch
TCascSearch(TCascStorage * ahs, LPCTSTR aszListFile, const char * aszMask)
{
// Init the class
+ if(ahs != NULL)
+ hs = ahs->AddRef();
ClassName = CASC_MAGIC_FIND;
- hs = ahs->AddRef();
// Init provider-specific data
pCache = NULL;
@@ -399,7 +404,8 @@ struct TCascSearch
~TCascSearch()
{
// Dereference the CASC storage
- hs = hs->Release();
+ if(hs != NULL)
+ hs = hs->Release();
ClassName = 0;
// Free the rest of the members
diff --git a/dep/CascLib/src/CascDumpData.cpp b/dep/CascLib/src/CascDumpData.cpp
index 9437ba52db2..7a41d3da9f0 100644
--- a/dep/CascLib/src/CascDumpData.cpp
+++ b/dep/CascLib/src/CascDumpData.cpp
@@ -508,6 +508,7 @@ void CascDumpStorage(HANDLE hStorage, const char * szDumpFile)
fprintf(fp, "IndexPath: %s\n", StringFromLPTSTR(hs->szIndexPath, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "BuildFile: %s\n", StringFromLPTSTR(hs->szBuildFile, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "CDN Server: %s\n", StringFromLPTSTR(hs->szCdnServers, szStringBuff, sizeof(szStringBuff)));
+ fprintf(fp, "CDN Host Url: %s\n", StringFromLPTSTR(hs->szCdnHostUrl, szStringBuff, sizeof(szStringBuff)));
fprintf(fp, "CDN Path: %s\n", StringFromLPTSTR(hs->szCdnPath, szStringBuff, sizeof(szStringBuff)));
DumpKey(fp, "CDN Config Key: %s\n", hs->CdnConfigKey.pbData, hs->CdnConfigKey.cbData);
DumpKey(fp, "CDN Build Key: %s\n", hs->CdnBuildKey.pbData, hs->CdnBuildKey.cbData);
diff --git a/dep/CascLib/src/CascFiles.cpp b/dep/CascLib/src/CascFiles.cpp
index 695d7d4af6a..b18b8847a3d 100644
--- a/dep/CascLib/src/CascFiles.cpp
+++ b/dep/CascLib/src/CascFiles.cpp
@@ -60,7 +60,7 @@ static LPCTSTR DataDirs[] =
NULL,
};
-static LPCTSTR bnet_region = _T("us");
+static const LPCTSTR szDefaultCDN = _T("ribbit://us.version.battle.net/v1/products");
//-----------------------------------------------------------------------------
// Local functions
@@ -454,6 +454,7 @@ static DWORD LoadBuildProductId(TCascStorage * hs, const char * /* szVariableNam
// "B29049"
// "WOW-18125patch6.0.1"
+// "WOW-45779patch10.0.2_Beta"
// "30013_Win32_2_2_0_Ptr_ptr"
// "prometheus-0_8_0_0-24919"
static DWORD LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */)
@@ -914,6 +915,93 @@ static DWORD LoadCsvFile(TCascStorage * hs, LPCTSTR szFileName, PARSECSVFILE Pfn
return dwErrCode;
}
+static DWORD ForcePathExist(LPCTSTR szFileName, bool bIsFileName)
+{
+ LPTSTR szLocalPath;
+ size_t nIndex;
+ bool bFirstSeparator = false;
+ DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+
+ // Sanity checks
+ if(szFileName && szFileName[0])
+ {
+ szLocalPath = CascNewStr(szFileName);
+ if(szLocalPath != NULL)
+ {
+ // Get the end of search
+ if(bIsFileName)
+ CutLastPathPart(szLocalPath);
+
+ // Check the entire path
+ if(_taccess(szLocalPath, 0) != 0)
+ {
+ // Searth the entire path
+ for(nIndex = 0; szLocalPath[nIndex] != 0; nIndex++)
+ {
+ if(szLocalPath[nIndex] == '\\' || szLocalPath[nIndex] == '/')
+ {
+ // Cut the path and verify whether the folder/file exists
+ szLocalPath[nIndex] = 0;
+
+ // Skip the very first separator
+ if(bFirstSeparator == true)
+ {
+ // Is it there?
+ if(DirectoryExists(szLocalPath) == false && MakeDirectory(szLocalPath) == false)
+ {
+ dwErrCode = ERROR_PATH_NOT_FOUND;
+ break;
+ }
+ }
+
+ // Restore the character
+ szLocalPath[nIndex] = PATH_SEP_CHAR;
+ bFirstSeparator = true;
+ dwErrCode = ERROR_SUCCESS;
+ }
+ }
+
+ // Now check the final path
+ if(DirectoryExists(szLocalPath) || MakeDirectory(szLocalPath))
+ {
+ dwErrCode = ERROR_SUCCESS;
+ }
+ }
+ else
+ {
+ dwErrCode = ERROR_SUCCESS;
+ }
+
+ CASC_FREE(szLocalPath);
+ }
+ }
+
+ return dwErrCode;
+}
+
+static DWORD SaveLocalFile(LPCTSTR szLocalName, LPBYTE pbFileData, size_t cbFileData)
+{
+ TFileStream * pLocStream;
+ DWORD dwErrCode = ERROR_DISK_FULL;
+
+ // Make sure that the path exists
+ ForcePathExist(szLocalName, true);
+
+ // Create local file
+ pLocStream = FileStream_CreateFile(szLocalName, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
+ if(pLocStream != NULL)
+ {
+ if(FileStream_Write(pLocStream, NULL, pbFileData, (DWORD)(cbFileData)))
+ dwErrCode = ERROR_SUCCESS;
+
+ FileStream_Close(pLocStream);
+ }
+ else
+ dwErrCode = GetCascError();
+
+ return dwErrCode;
+}
+
static LPCTSTR ExtractCdnServerName(LPTSTR szServerName, size_t cchServerName, LPCTSTR szCdnServers)
{
LPCTSTR szSeparator;
@@ -997,70 +1085,6 @@ static void CreateRemoteAndLocalPath(TCascStorage * hs, CASC_CDN_DOWNLOAD & Cdns
LocalPath.AppendString(CdnsInfo.szExtension, false);
}
-static DWORD ForcePathExist(LPCTSTR szFileName, bool bIsFileName)
-{
- LPTSTR szLocalPath;
- size_t nIndex;
- bool bFirstSeparator = false;
- DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
-
- // Sanity checks
- if(szFileName && szFileName[0])
- {
- szLocalPath = CascNewStr(szFileName);
- if(szLocalPath != NULL)
- {
- // Get the end of search
- if(bIsFileName)
- CutLastPathPart(szLocalPath);
-
- // Check the entire path
- if(_taccess(szLocalPath, 0) != 0)
- {
- // Searth the entire path
- for(nIndex = 0; szLocalPath[nIndex] != 0; nIndex++)
- {
- if(szLocalPath[nIndex] == '\\' || szLocalPath[nIndex] == '/')
- {
- // Cut the path and verify whether the folder/file exists
- szLocalPath[nIndex] = 0;
-
- // Skip the very first separator
- if(bFirstSeparator == true)
- {
- // Is it there?
- if(DirectoryExists(szLocalPath) == false && MakeDirectory(szLocalPath) == false)
- {
- dwErrCode = ERROR_PATH_NOT_FOUND;
- break;
- }
- }
-
- // Restore the character
- szLocalPath[nIndex] = PATH_SEP_CHAR;
- bFirstSeparator = true;
- dwErrCode = ERROR_SUCCESS;
- }
- }
-
- // Now check the final path
- if(DirectoryExists(szLocalPath) || MakeDirectory(szLocalPath))
- {
- dwErrCode = ERROR_SUCCESS;
- }
- }
- else
- {
- dwErrCode = ERROR_SUCCESS;
- }
-
- CASC_FREE(szLocalPath);
- }
- }
-
- return dwErrCode;
-}
-
static bool FileAlreadyExists(LPCTSTR szFileName)
{
TFileStream * pStream;
@@ -1084,7 +1108,6 @@ static DWORD DownloadFile(
DWORD dwPortFlags)
{
TFileStream * pRemStream;
- TFileStream * pLocStream;
LPBYTE pbFileData;
DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
@@ -1098,28 +1121,31 @@ static DWORD DownloadFile(
ULONGLONG FileSize = 0;
// Retrieve the file size, but not longer than 1 GB
- if(FileStream_GetSize(pRemStream, &FileSize) && 0 < FileSize && FileSize < 0x40000000)
+ if(FileStream_GetSize(pRemStream, &FileSize))
{
- // Cut the file size down to 32 bits
- cbReadSize = (DWORD)FileSize;
+ // Verify valid size
+ if(0 < FileSize && FileSize < CASC_MAX_ONLINE_FILE_SIZE)
+ {
+ cbReadSize = (DWORD)FileSize;
+ dwErrCode = ERROR_SUCCESS;
+ }
+ else
+ {
+ dwErrCode = ERROR_BAD_FORMAT;
+ }
+ }
+ else
+ {
+ dwErrCode = GetCascError();
}
}
// Shall we read something?
- if((cbReadSize != 0) && (pbFileData = CASC_ALLOC<BYTE>(cbReadSize)) != NULL)
+ if((dwErrCode == ERROR_SUCCESS) && (cbReadSize != 0) && (pbFileData = CASC_ALLOC<BYTE>(cbReadSize)) != NULL)
{
// Read all required data from the remote file
if(FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize))
- {
- pLocStream = FileStream_CreateFile(szLocalName, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT);
- if(pLocStream != NULL)
- {
- if(FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize))
- dwErrCode = ERROR_SUCCESS;
-
- FileStream_Close(pLocStream);
- }
- }
+ dwErrCode = SaveLocalFile(szLocalName, pbFileData, cbReadSize);
// Free the data buffer
CASC_FREE(pbFileData);
@@ -1136,16 +1162,38 @@ static DWORD DownloadFile(
return dwErrCode;
}
-static DWORD RibbitDownloadFile(LPCTSTR szProduct, LPCTSTR szFileName, QUERY_KEY & FileData)
+static DWORD RibbitDownloadFile(LPCTSTR szCdnHostUrl, LPCTSTR szProduct, LPCTSTR szFileName, CASC_PATH<TCHAR> & LocalPath, QUERY_KEY & FileData)
{
+ CASC_PATH<TCHAR> LocalFile;
TFileStream * pStream;
ULONGLONG FileSize = 0;
TCHAR szRemoteUrl[256];
DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE;
+ // If required, try to load the local name first
+ if(LocalPath.Length() && LocalPath.LocalCaching())
+ {
+ LPBYTE pbFileData;
+ DWORD cbFileData = 0;
+
+ // Load the local file into memory
+ pbFileData = LoadFileToMemory(LocalPath, &cbFileData);
+ if(pbFileData && cbFileData)
+ {
+ // Pass the file data to the caller
+ FileData.pbData = pbFileData;
+ FileData.cbData = cbFileData;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ // Supply the default CDN URL, if not present
+ if(szCdnHostUrl == NULL || szCdnHostUrl[0] == 0)
+ szCdnHostUrl = szDefaultCDN;
+
// Construct the full URL (https://wowdev.wiki/Ribbit)
// Old (HTTP) download: wget http://us.patch.battle.net:1119/wow_classic/cdns
- CascStrPrintf(szRemoteUrl, _countof(szRemoteUrl), _T("ribbit://%s.version.battle.net/v1/products/%s/%s"), bnet_region, szProduct, szFileName);
+ CascStrPrintf(szRemoteUrl, _countof(szRemoteUrl), _T("%s/%s/%s"), szCdnHostUrl, szProduct, szFileName);
// Open the file stream
if((pStream = FileStream_OpenFile(szRemoteUrl, 0)) != NULL)
@@ -1186,6 +1234,9 @@ static DWORD RibbitDownloadFile(LPCTSTR szProduct, LPCTSTR szFileName, QUERY_KEY
dwErrCode = GetCascError();
}
+ // Save the file to the local cache
+ if(LocalPath.Length() && FileData.pbData && FileData.cbData && dwErrCode == ERROR_SUCCESS)
+ SaveLocalFile(LocalPath, FileData.pbData, FileData.cbData);
return dwErrCode;
}
@@ -1228,13 +1279,24 @@ DWORD DownloadFileFromCDN(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo)
// from the storage's configuration
if(CdnsInfo.szCdnsHost == NULL)
{
+ // Supply the CDN host
+ CdnsInfo.szCdnsHost = szCdnHost;
+
// Try all download servers
while((szCdnServers = ExtractCdnServerName(szCdnHost, _countof(szCdnHost), szCdnServers)) != NULL)
{
- CdnsInfo.szCdnsHost = szCdnHost;
- if((dwErrCode = DownloadFileFromCDN2(hs, CdnsInfo)) == ERROR_SUCCESS)
- return ERROR_SUCCESS;
+ // Attempt to download the file from the remote server
+ dwErrCode = DownloadFileFromCDN2(hs, CdnsInfo);
+
+ // If the download succeeded, exit immediately.
+ // Also exit when there is a low memory condition,
+ // as it will most likely end up with low memory on next download
+ if(dwErrCode == ERROR_SUCCESS || dwErrCode == ERROR_NOT_ENOUGH_MEMORY)
+ break;
}
+
+ // Don't give the local buffer pointer to the caller
+ CdnsInfo.szCdnsHost = NULL;
}
else
{
@@ -1411,14 +1473,21 @@ DWORD LoadBuildInfo(TCascStorage * hs)
// If the storage is online storage, we need to download "versions"
if(hs->dwFeatures & CASC_FEATURE_ONLINE)
{
+ CASC_PATH<TCHAR> LocalPath;
QUERY_KEY FileData;
+ LPCTSTR szVersionsName = _T("versions");
// Inform the user about loading the build.info/build.db/versions
if(InvokeProgressCallback(hs, "Downloading the \"versions\" file", NULL, 0, 0))
return ERROR_CANCELLED;
- // Download the file using Ribbit protocol
- dwErrCode = RibbitDownloadFile(hs->szCodeName, _T("versions"), FileData);
+ // Shall we prefer the locally cached file?
+ LocalPath.SetPathRoot(hs->szRootPath);
+ LocalPath.AppendString(szVersionsName, true);
+ LocalPath.SetLocalCaching(hs->dwFeatures & CASC_FEATURE_LOCAL_VERSIONS);
+
+ // Download the file using Ribbit/HTTP protocol
+ dwErrCode = RibbitDownloadFile(hs->szCdnHostUrl, hs->szCodeName, szVersionsName, LocalPath, FileData);
if(dwErrCode == ERROR_SUCCESS)
{
// Parse the downloaded file
@@ -1436,7 +1505,9 @@ DWORD LoadBuildInfo(TCascStorage * hs)
DWORD LoadCdnsFile(TCascStorage * hs)
{
+ CASC_PATH<TCHAR> LocalPath;
QUERY_KEY FileData;
+ LPCTSTR szCdnsName = _T("cdns");
DWORD dwErrCode = ERROR_SUCCESS;
// Sanity checks
@@ -1446,8 +1517,13 @@ DWORD LoadCdnsFile(TCascStorage * hs)
if(InvokeProgressCallback(hs, "Downloading the \"cdns\" file", NULL, 0, 0))
return ERROR_CANCELLED;
+ // Construct the local path of the file
+ LocalPath.SetPathRoot(hs->szRootPath);
+ LocalPath.AppendString(szCdnsName, true);
+ LocalPath.SetLocalCaching(hs->dwFeatures & CASC_FEATURE_LOCAL_CDNS);
+
// Download the file using Ribbit protocol
- dwErrCode = RibbitDownloadFile(hs->szCodeName, _T("cdns"), FileData);
+ dwErrCode = RibbitDownloadFile(hs->szCdnHostUrl, hs->szCodeName, szCdnsName, LocalPath, FileData);
if(dwErrCode == ERROR_SUCCESS)
{
// Parse the downloaded file
@@ -1603,7 +1679,6 @@ LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData)
{
SetCascError(ERROR_BAD_FORMAT);
cbFileData = 0;
- assert(false);
}
// Close the file stream
@@ -1616,3 +1691,49 @@ LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData)
return pbFileData;
}
+//-----------------------------------------------------------------------------
+// Public CDN functions
+
+LPCTSTR WINAPI CascCdnGetDefault()
+{
+ return szDefaultCDN;
+}
+
+LPBYTE WINAPI CascCdnDownload(LPCTSTR szCdnHostUrl, LPCTSTR szProduct, LPCTSTR szFileName, DWORD * PtrSize)
+{
+ CASC_PATH<TCHAR> LocalPath;
+ QUERY_KEY FileData;
+ LPBYTE pbFileData = NULL;
+ size_t cbFileData = 0;
+ DWORD dwErrCode;
+
+ // Download the file
+ dwErrCode = RibbitDownloadFile(szCdnHostUrl, szProduct, szFileName, LocalPath, FileData);
+ if(dwErrCode == ERROR_SUCCESS)
+ {
+ // Create copy of the buffer
+ if((pbFileData = CASC_ALLOC<BYTE>(FileData.cbData + 1)) != NULL)
+ {
+ memcpy(pbFileData, FileData.pbData, FileData.cbData);
+ pbFileData[FileData.cbData] = 0;
+ cbFileData = FileData.cbData;
+ }
+ else
+ {
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ // Give the results
+ if(dwErrCode != ERROR_SUCCESS)
+ SetCascError(dwErrCode);
+ if(PtrSize != NULL)
+ PtrSize[0] = (DWORD)cbFileData;
+ return pbFileData;
+}
+
+void WINAPI CascCdnFree(void * buffer)
+{
+ CASC_FREE(buffer);
+}
+
diff --git a/dep/CascLib/src/CascLib.h b/dep/CascLib/src/CascLib.h
index 815ca7e3376..3f4445e9351 100644
--- a/dep/CascLib/src/CascLib.h
+++ b/dep/CascLib/src/CascLib.h
@@ -140,6 +140,8 @@ extern "C" {
#define CASC_FEATURE_LOCALE_FLAGS 0x00000040 // Locale flags are supported
#define CASC_FEATURE_CONTENT_FLAGS 0x00000080 // Content flags are supported
#define CASC_FEATURE_ONLINE 0x00000100 // The storage is an online storage
+#define CASC_FEATURE_LOCAL_CDNS 0x00000200 // (Online) use cached "cdns" file, if available
+#define CASC_FEATURE_LOCAL_VERSIONS 0x00000400 // (Online) use cached "versions" file, if available
// Macro to convert FileDataId to the argument of CascOpenFile
#define CASC_FILE_DATA_ID(FileDataId) ((LPCSTR)(size_t)FileDataId)
@@ -329,7 +331,7 @@ typedef struct _CASC_OPEN_STORAGE_ARGS
void * PtrProductParam; // Pointer-sized parameter that will be passed to PfnProgressCallback
DWORD dwLocaleMask; // Locale mask to open
- DWORD dwFlags; // Reserved. Set to zero.
+ DWORD dwFlags; // Additional CASC_FEATURE_XXX can be set here
//
// Any additional member from here on must be checked for availability using the ExtractVersionedArgument function.
@@ -341,31 +343,34 @@ typedef struct _CASC_OPEN_STORAGE_ARGS
LPCTSTR szBuildKey; // If non-null, this will specify a build key (aka MD5 of build config that is different that current online version)
+ LPCTSTR szCdnHostUrl; // If non-null, specifies the custom CDN URL. Must contain protocol, can contain port number
+ // Example: http://eu.custom-wow-cdn.com:8000
+
} CASC_OPEN_STORAGE_ARGS, *PCASC_OPEN_STORAGE_ARGS;
//-----------------------------------------------------------------------------
// Functions for storage manipulation
-bool WINAPI CascOpenStorageEx(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs, bool bOnlineStorage, HANDLE * phStorage);
-bool WINAPI CascOpenStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage);
-bool WINAPI CascOpenOnlineStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage);
-bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded);
-bool WINAPI CascCloseStorage(HANDLE hStorage);
+bool WINAPI CascOpenStorageEx(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs, bool bOnlineStorage, HANDLE * phStorage);
+bool WINAPI CascOpenStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage);
+bool WINAPI CascOpenOnlineStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage);
+bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded);
+bool WINAPI CascCloseStorage(HANDLE hStorage);
-bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * PtrFileHandle);
-bool WINAPI CascOpenLocalFile(LPCTSTR szFileName, DWORD dwOpenFlags, HANDLE * PtrFileHandle);
-bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded);
-bool WINAPI CascGetFileSize64(HANDLE hFile, PULONGLONG PtrFileSize);
-bool WINAPI CascSetFilePointer64(HANDLE hFile, LONGLONG DistanceToMove, PULONGLONG PtrNewPos, DWORD dwMoveMethod);
-bool WINAPI CascReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, PDWORD pdwRead);
-bool WINAPI CascCloseFile(HANDLE hFile);
+bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * PtrFileHandle);
+bool WINAPI CascOpenLocalFile(LPCTSTR szFileName, DWORD dwOpenFlags, HANDLE * PtrFileHandle);
+bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded);
+bool WINAPI CascGetFileSize64(HANDLE hFile, PULONGLONG PtrFileSize);
+bool WINAPI CascSetFilePointer64(HANDLE hFile, LONGLONG DistanceToMove, PULONGLONG PtrNewPos, DWORD dwMoveMethod);
+bool WINAPI CascReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, PDWORD pdwRead);
+bool WINAPI CascCloseFile(HANDLE hFile);
-DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh);
-DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * PtrFilePosHigh, DWORD dwMoveMethod);
+DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh);
+DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * PtrFilePosHigh, DWORD dwMoveMethod);
HANDLE WINAPI CascFindFirstFile(HANDLE hStorage, LPCSTR szMask, PCASC_FIND_DATA pFindData, LPCTSTR szListFile);
-bool WINAPI CascFindNextFile(HANDLE hFind, PCASC_FIND_DATA pFindData);
-bool WINAPI CascFindClose(HANDLE hFind);
+bool WINAPI CascFindNextFile(HANDLE hFind, PCASC_FIND_DATA pFindData);
+bool WINAPI CascFindClose(HANDLE hFind);
bool WINAPI CascAddEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPBYTE Key);
bool WINAPI CascAddStringEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPCSTR szKey);
@@ -375,6 +380,13 @@ LPBYTE WINAPI CascFindEncryptionKey(HANDLE hStorage, ULONGLONG KeyName);
bool WINAPI CascGetNotFoundEncryptionKey(HANDLE hStorage, ULONGLONG * KeyName);
//-----------------------------------------------------------------------------
+// CDN Support
+
+LPCTSTR WINAPI CascCdnGetDefault();
+LPBYTE WINAPI CascCdnDownload(LPCTSTR szCdnHostUrl, LPCTSTR szProduct, LPCTSTR szFileName, DWORD * PtrSize);
+void WINAPI CascCdnFree(void * buffer);
+
+//-----------------------------------------------------------------------------
// Error code support
void SetCascError(DWORD dwErrCode);
diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp
index 584b79a38a1..1231ef58bfe 100644
--- a/dep/CascLib/src/CascOpenStorage.cpp
+++ b/dep/CascLib/src/CascOpenStorage.cpp
@@ -7,7 +7,7 @@
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
-/* 29.04.14 1.00 Lad The first version of CascOpenStorage.cpp */
+/* 29.04.14 1.00 Lad Created */
/*****************************************************************************/
#define __CASCLIB_SELF__
@@ -61,7 +61,7 @@ TCascStorage::TCascStorage()
pRootHandler = NULL;
dwRefCount = 1;
- szRootPath = szDataPath = szIndexPath = szBuildFile = szCdnServers = szCdnPath = szCodeName = NULL;
+ szRootPath = szDataPath = szIndexPath = szBuildFile = szCdnServers = szCdnPath = szCdnHostUrl = szCodeName = NULL;
szIndexFormat = NULL;
szRegion = NULL;
szBuildKey = NULL;
@@ -107,6 +107,7 @@ TCascStorage::~TCascStorage()
CASC_FREE(szCdnServers);
CASC_FREE(szCdnPath);
CASC_FREE(szCodeName);
+ CASC_FREE(szCdnHostUrl);
CASC_FREE(szRegion);
CASC_FREE(szBuildKey);
@@ -535,7 +536,7 @@ size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount)
size_t nBitmapLength;
nBitmapLength = (EntryCount / 8) + ((EntryCount & 0x07) ? 1 : 0);
- if ((pbFilePtr + nBitmapLength) > pbFileEnd)
+ if((pbFilePtr + nBitmapLength) > pbFileEnd)
nBitmapLength = (pbFileEnd - pbFilePtr);
return nBitmapLength;
@@ -565,14 +566,14 @@ int CaptureDownloadHeader(CASC_DOWNLOAD_HEADER & DlHeader, LPBYTE pbFileData, si
DlHeader.EntryLength = DlHeader.EKeyLength + 5 + 1 + (DlHeader.EntryHasChecksum ? 4 : 0);
// Capture header version 2
- if (pFileHeader->Version >= 2)
+ if(pFileHeader->Version >= 2)
{
DlHeader.FlagByteSize = pFileHeader->FlagByteSize;
DlHeader.HeaderLength = FIELD_OFFSET(FILE_DOWNLOAD_HEADER, BasePriority);
DlHeader.EntryLength += DlHeader.FlagByteSize;
// Capture header version 3
- if (pFileHeader->Version >= 3)
+ if(pFileHeader->Version >= 3)
{
DlHeader.BasePriority = pFileHeader->BasePriority;
DlHeader.HeaderLength = sizeof(FILE_DOWNLOAD_HEADER);
@@ -808,7 +809,7 @@ static int LoadInstallManifest(TCascStorage * hs)
// Load the entire DOWNLOAD file to memory
pbInstallFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbInstallFile);
- if (pbInstallFile != NULL && cbInstallFile != 0)
+ if(pbInstallFile != NULL && cbInstallFile != 0)
{
dwErrCode = RootHandler_CreateInstall(hs, pbInstallFile, cbInstallFile);
CASC_FREE(pbInstallFile);
@@ -861,7 +862,8 @@ static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC
static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask)
{
- PCASC_CKEY_ENTRY pCKeyEntry;
+ PCASC_CKEY_ENTRY pCKeyEntry = &hs->RootFile;
+ TRootHandler * pOldRootHandler = NULL;
PDWORD FileSignature;
LPBYTE pbRootFile = NULL;
DWORD cbRootFile = 0;
@@ -878,11 +880,14 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask)
// Locale: The default parameter is 0 - in that case, we load all locales
dwLocaleMask = (dwLocaleMask != 0) ? dwLocaleMask : 0xFFFFFFFF;
- // Prioritize the VFS root over legacy ROOT file
- pCKeyEntry = (hs->VfsRoot.ContentSize != CASC_INVALID_SIZE) ? &hs->VfsRoot : &hs->RootFile;
- pCKeyEntry = FindCKeyEntry_CKey(hs, pCKeyEntry->CKey);
+ // Prioritize the VFS root over legacy ROOT file, unless it's WoW
+ if(hs->VfsRoot.ContentSize != CASC_INVALID_SIZE)
+ pCKeyEntry = &hs->VfsRoot;
+
+__LoadRootFile:
// Load the entire ROOT file to memory
+ pCKeyEntry = FindCKeyEntry_CKey(hs, pCKeyEntry->CKey);
pbRootFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbRootFile);
if(pbRootFile != NULL)
{
@@ -937,6 +942,28 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask)
dwErrCode = GetCascError();
}
+ // Handle reparsing of the root file
+ if(dwErrCode == ERROR_REPARSE_ROOT && pCKeyEntry != &hs->RootFile)
+ {
+ if(InvokeProgressCallback(hs, "Loading ROOT manifest (reparsed)", NULL, 0, 0))
+ return ERROR_CANCELLED;
+
+ // Replace the root handler
+ pOldRootHandler = hs->pRootHandler;
+ hs->pRootHandler = NULL;
+
+ // Replace the CKey entry for the ROOT file
+ pCKeyEntry = &hs->RootFile;
+ goto __LoadRootFile;
+ }
+
+ // If we reparsed the ROOT file and we have the old one, we need to copy all items to the new one
+ if(hs->pRootHandler && pOldRootHandler)
+ {
+ hs->pRootHandler->Copy(pOldRootHandler);
+ delete pOldRootHandler;
+ }
+
return dwErrCode;
}
@@ -1112,14 +1139,14 @@ static DWORD InitializeLocalDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_AR
}
// Find the index directory
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
{
// First, check for more common "data" subdirectory
- if ((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
+ if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("data"))) != NULL)
dwErrCode = ERROR_SUCCESS;
// Second, try the "darch" subdirectory (older builds of HOTS - Alpha)
- else if ((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
+ else if((hs->szIndexPath = CheckForIndexDirectory(hs, _T("darch"))) != NULL)
dwErrCode = ERROR_SUCCESS;
else
@@ -1137,10 +1164,11 @@ static DWORD InitializeOnlineDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_A
{
// Create the root path
hs->szRootPath = CascNewStr(pArgs->szLocalPath);
- if (hs->szRootPath != NULL)
+ if(hs->szRootPath != NULL)
{
hs->BuildFileType = CascVersionsDb;
hs->dwFeatures |= CASC_FEATURE_ONLINE;
+ hs->dwFeatures |= (pArgs->dwFlags & (CASC_FEATURE_LOCAL_CDNS | CASC_FEATURE_LOCAL_VERSIONS));
return ERROR_SUCCESS;
}
@@ -1149,6 +1177,7 @@ static DWORD InitializeOnlineDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_A
static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
{
+ LPCTSTR szCdnHostUrl = NULL;
LPCTSTR szCodeName = NULL;
LPCTSTR szRegion = NULL;
LPCTSTR szBuildKey = NULL;
@@ -1160,7 +1189,11 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
// Extract optional arguments
ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, dwLocaleMask), &dwLocaleMask);
-
+
+ // Extract the CDN host URL
+ if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szCdnHostUrl), &szCdnHostUrl) && szCdnHostUrl != NULL)
+ hs->szCdnHostUrl = CascNewStr(szCdnHostUrl);
+
// Extract the product code name
if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szCodeName), &szCodeName) && szCodeName != NULL)
hs->szCodeName = CascNewStr(szCodeName);
@@ -1190,9 +1223,8 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
dwErrCode = LoadBuildInfo(hs);
}
- // If the .build.info OR .build.db file has been loaded,
- // proceed with loading the CDN config file
- if (dwErrCode == ERROR_SUCCESS)
+ // Proceed with loading the CDN config file
+ if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadCdnConfigFile(hs);
if(dwErrCode != ERROR_SUCCESS && (hs->dwFeatures & CASC_FEATURE_ONLINE) == 0)
@@ -1200,7 +1232,7 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
}
// Proceed with loading the CDN build file
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = LoadCdnBuildFile(hs);
}
@@ -1243,7 +1275,7 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
// Continue loading the manifest
dwErrCode = LoadBuildManifest(hs, dwLocaleMask);
- if (dwErrCode != ERROR_SUCCESS)
+ if(dwErrCode != ERROR_SUCCESS)
{
// If we fail to load the ROOT file, we take the file names from the INSTALL manifest
dwErrCode = LoadInstallManifest(hs);
@@ -1252,7 +1284,7 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
// Insert entries for files with well-known names. Their CKeys are in the BUILD file
// See https://wowdev.wiki/TACT#Encoding_table for their list
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
{
InsertWellKnownFile(hs, "ENCODING", hs->EncodingCKey);
InsertWellKnownFile(hs, "DOWNLOAD", hs->DownloadCKey);
@@ -1266,7 +1298,7 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
}
// Load the encryption keys
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
{
dwErrCode = CascLoadEncryptionKeys(hs);
}
@@ -1277,64 +1309,91 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs)
return dwErrCode;
}
-static LPTSTR ParseOpenParams(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs)
+// Check for URL pattern. Note that the string may be terminated by ':' instead of '\0'
+static bool IsUrl(LPCTSTR szString)
{
- LPTSTR szParamsCopy;
-
- // The 'szParams' must not be empty
- if(szParams == NULL || pArgs == NULL || szParams[0] == 0)
+ while(szString[0] != 0 && szString[0] != '*')
{
- SetCascError(ERROR_INVALID_PARAMETER);
- return NULL;
+ // Check for "://"
+ if(!_tcsncmp(szString, _T("://"), 3))
+ return true;
+
+ // Dot or slash both indicate an URL
+ if(szString[0] == '.' || szString[0] == '/')
+ return true;
+
+ szString++;
}
+ return false;
+}
+
+static LPTSTR GetNextParam(LPTSTR szParamsPtr, bool bMustBeUrl = false)
+{
+ LPTSTR szSeparator = NULL;
- // The 'pArgs' must be valid but must not contain 'szLocalPath', 'szCodeName' or 'szRegion'
- if(pArgs->szLocalPath != NULL || pArgs->szCodeName != NULL || pArgs->szRegion != NULL)
+ // The 'szParamsPtr' must be valid
+ if(szParamsPtr != NULL)
{
- SetCascError(ERROR_INVALID_PARAMETER);
- return NULL;
+ // Find the separator ("*") or end of string
+ if((szSeparator = _tcschr(szParamsPtr, _T('*'))) != NULL)
+ {
+ // Check for URL pattern, if needed
+ if(bMustBeUrl && IsUrl(szSeparator + 1) == false)
+ return NULL;
+
+ // Put the EOS there
+ *szSeparator++ = 0;
+ }
}
- // Make a copy of the parameters so we can temper with them
- if((szParamsCopy = CascNewStr(szParams)) != NULL)
- {
- LPTSTR szPlainName = (LPTSTR)GetPlainFileName(szParamsCopy);
- LPTSTR szSeparator;
+ return szSeparator;
+}
- // The local path is always set
- pArgs->szLocalPath = szParamsCopy;
- pArgs->szCodeName = NULL;
- pArgs->szRegion = NULL;
- pArgs->szBuildKey = NULL;
+static DWORD ParseOpenParams(LPTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs)
+{
+ LPTSTR szParamsPtr = szParams;
+ LPTSTR szParamsTmp;
- // Find the first ":". This will indicate the end of local path and also begin of product code
- if((szSeparator = _tcschr(szPlainName, _T(':'))) != NULL)
- {
- // The found string is a product code name
- pArgs->szCodeName = szSeparator + 1;
- szSeparator[0] = 0;
+ //
+ // Format of the params:
+ //
+ // Local: local_path*code_name ("C:\\Games\\World of Warcraft*wowt")
+ // Online: local_cache_path[*cdn_url]*code_name*region" ("C:\\Cache*wowt*us)
+ //
- // Try again. If found, it is a product region
- if((szSeparator = _tcschr(szSeparator + 1, _T(':'))) != NULL)
- {
- pArgs->szRegion = szSeparator + 1;
- szSeparator[0] = 0;
+ // If the caller supplied the local_path/local_cache_path
+ // both in szParams and pArgs, it's a conflict
+ if(pArgs->szLocalPath && pArgs->szLocalPath[0])
+ return ERROR_INVALID_PARAMETER;
+ pArgs->szLocalPath = szParams;
- // Try again. If found, it is a build key (MD5 of a build file)
- if((szSeparator = _tcschr(szSeparator + 1, _T(':'))) != NULL)
- {
- pArgs->szBuildKey = szSeparator + 1;
- szSeparator[0] = 0;
- }
- }
- }
+ // Extract the optional CDN path. If present, then we put it
+ // into CASC_OPEN_STORAGE_ARGS::szCdnHostUrl
+ if((szParamsTmp = GetNextParam(szParamsPtr, true)) != NULL)
+ {
+ if(pArgs->szCdnHostUrl && pArgs->szCdnHostUrl[0])
+ return ERROR_INVALID_PARAMETER;
+ pArgs->szCdnHostUrl = szParamsTmp;
+ szParamsPtr = szParamsTmp;
}
- else
+
+ // The next must be the code name of the product
+ if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL)
+ {
+ if(pArgs->szCodeName && pArgs->szCodeName[0])
+ return ERROR_INVALID_PARAMETER;
+ pArgs->szCodeName = szParamsPtr;
+ }
+
+ // There could be region appended at the end
+ if((szParamsPtr = GetNextParam(szParamsPtr)) != NULL)
{
- SetCascError(ERROR_NOT_ENOUGH_MEMORY);
+ if(pArgs->szRegion && pArgs->szRegion[0])
+ return ERROR_INVALID_PARAMETER;
+ pArgs->szRegion = szParamsPtr;
}
- return szParamsCopy;
+ return ERROR_SUCCESS;
}
//-----------------------------------------------------------------------------
@@ -1343,62 +1402,83 @@ static LPTSTR ParseOpenParams(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs)
bool WINAPI CascOpenStorageEx(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs, bool bOnlineStorage, HANDLE * phStorage)
{
CASC_OPEN_STORAGE_ARGS LocalArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)};
- TCascStorage * hs;
+ TCascStorage * hs = NULL;
LPTSTR szParamsCopy = NULL;
- DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+ DWORD dwErrCode = ERROR_SUCCESS;
+
+ // Supply the local args if not given by the caller
+ pArgs = (pArgs != NULL) ? pArgs : &LocalArgs;
+
+ //
+ // Parse the parameter string and put the parts into CASC_OPEN_STORAGE_ARGS
+ //
+ // Note that the parameter string is optional - is it possible
+ // to enter all params purely in CASC_OPEN_STORAGE_ARGS structure.
+ //
- // The storage path[+product[+region]] must either be passed in szParams or in pArgs. Not both.
- // It is allowed to pass NULL as pArgs if the szParams is not NULL
if(szParams != NULL)
{
- if(pArgs == NULL)
- pArgs = &LocalArgs;
-
- szParamsCopy = ParseOpenParams(szParams, pArgs);
- if(szParamsCopy == NULL)
+ // Make a copy of the parameters so we can tamper with them
+ if((szParamsCopy = CascNewStr(szParams)) == NULL)
+ {
+ SetCascError(ERROR_NOT_ENOUGH_MEMORY);
return false;
+ }
+
+ // Parse the parameter string and put the corresponding parts
+ // into the CASC_OPEN_STORAGE_ARGS structure.
+ dwErrCode = ParseOpenParams(szParamsCopy, pArgs);
}
- else
+
+ // Verify the minimum arguments
+ if(dwErrCode == ERROR_SUCCESS)
{
- // The arguments and the local path must be entered
- if(pArgs == NULL || pArgs->szLocalPath == NULL || pArgs->szLocalPath[0] == 0)
+ if(pArgs->szLocalPath == NULL || pArgs->szLocalPath[0] == 0)
{
- SetCascError(ERROR_INVALID_PARAMETER);
- return false;
+ dwErrCode = ERROR_INVALID_PARAMETER;
}
}
// Allocate the storage structure
- if((hs = new TCascStorage()) != NULL)
+ if(dwErrCode == ERROR_SUCCESS)
{
- // Setup the directories
- dwErrCode = (bOnlineStorage) ? InitializeOnlineDirectories(hs, pArgs) : InitializeLocalDirectories(hs, pArgs);
- if(dwErrCode == ERROR_SUCCESS)
+ if((hs = new TCascStorage()) != NULL)
{
- // Perform the entire storage loading
- dwErrCode = LoadCascStorage(hs, pArgs);
+ // Setup the directories
+ dwErrCode = (bOnlineStorage) ? InitializeOnlineDirectories(hs, pArgs) : InitializeLocalDirectories(hs, pArgs);
+ if(dwErrCode == ERROR_SUCCESS)
+ {
+ // Perform the entire storage loading
+ dwErrCode = LoadCascStorage(hs, pArgs);
+ }
}
+ }
- // Free the storage structure on fail
- if(dwErrCode != ERROR_SUCCESS)
- {
- hs = hs->Release();
- }
+ // Handle errors
+ if(dwErrCode != ERROR_SUCCESS)
+ {
+ SetCascError(dwErrCode);
+ hs = hs->Release();
}
- // Give the output parameter to the caller
+ // Free the copy of the parameters
CASC_FREE(szParamsCopy);
- *phStorage = (HANDLE)hs;
- // Return the result
- if(dwErrCode != ERROR_SUCCESS)
- SetCascError(dwErrCode);
+ // Give the output parameter to the caller
+ *phStorage = (HANDLE)hs;
return (dwErrCode == ERROR_SUCCESS);
}
-// szParams: "LocalPath:CodeName", e.g. "C:\\Games\\World of Warcraft:wowt"
-// * LocalPath: Local folder, where the online file will be cached.
-// * CodeName: Product code name, e.g. "agent" for Battle.net Agent. More info: https://wowdev.wiki/TACT#Products
+//
+// Opens a local CASC storage
+//
+// szParams: "local_path:code_name", like "C:\\Games\\World of Warcraft:wowt"
+//
+// local_path Local folder, where the online file will be cached.
+//
+// code_name: Product code name, e.g. "agent" for Battle.net Agent.
+// More info: https://wowdev.wiki/TACT#Products
+//
bool WINAPI CascOpenStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage)
{
CASC_OPEN_STORAGE_ARGS OpenArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)};
@@ -1407,11 +1487,19 @@ bool WINAPI CascOpenStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phSto
return CascOpenStorageEx(szParams, &OpenArgs, false, phStorage);
}
-// Allows to browse an online CDN storage
-// szParams: "CachePath:CodeName:Region", e.g. "C:\\Cache:wowt:us"
-// * CachePath: Local folder, where the online file will be cached.
-// * CodeName: Product code name, e.g. "agent" for Battle.net Agent. More info: https://wowdev.wiki/TACT#Products
-// * Region: The region (or subvariant) of the product. Corresponds to the first column of the "versions" file.
+//
+// Opens an online CDN storage
+//
+// szParams: "local_cache_path[:cdn_url]:code_name:region", e.g. "C:\\Cache:wowt:us"
+//
+// local_cache_path Local folder, where the online file will be cached.
+// cdn_url URL of the custom CDN server. Can also contain port.
+// This parameter is optional. Example: http://eu.custom-wow-cdn.com:8000
+// code_name Product code name, e.g. "agent" for Battle.net Agent.
+// More info: https://wowdev.wiki/TACT#Products
+// region The region (or subvariant) of the product.
+// Corresponds to the first column of the "versions" file.
+//
bool WINAPI CascOpenOnlineStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage)
{
CASC_OPEN_STORAGE_ARGS OpenArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)};
diff --git a/dep/CascLib/src/CascPort.h b/dep/CascLib/src/CascPort.h
index 419384045cc..9a136a3385c 100644
--- a/dep/CascLib/src/CascPort.h
+++ b/dep/CascLib/src/CascPort.h
@@ -13,9 +13,7 @@
#define __CASCPORT_H__
#ifndef __cplusplus
- #define bool char
- #define true 1
- #define false 0
+ #include <stdbool.h>
#endif
//-----------------------------------------------------------------------------
@@ -23,6 +21,11 @@
#if !defined(CASCLIB_PLATFORM_DEFINED) && (defined(_WIN32) || defined(_WIN64))
+ // Make sure that headers are only included once in newer SDKs
+ #if defined (_MSC_VER) && (_MSC_VER >= 1020)
+ #pragma once
+ #endif
+
// In MSVC 8.0, there are some functions declared as deprecated.
#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_NON_CONFORMING_SWPRINTFS
@@ -271,6 +274,10 @@
#define ERROR_INDEX_PARSING_DONE 1010
#endif
+#ifndef ERROR_REPARSE_ROOT
+#define ERROR_REPARSE_ROOT 1011
+#endif
+
#ifndef _countof
#define _countof(x) (sizeof(x) / sizeof(x[0]))
#endif
diff --git a/dep/CascLib/src/CascReadFile.cpp b/dep/CascLib/src/CascReadFile.cpp
index e71387b0466..c89445c7078 100644
--- a/dep/CascLib/src/CascReadFile.cpp
+++ b/dep/CascLib/src/CascReadFile.cpp
@@ -115,6 +115,7 @@ static DWORD OpenDataStream(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKE
return ERROR_SUCCESS;
}
}
+ return dwErrCode;
}
return ERROR_FILE_OFFLINE;
@@ -182,9 +183,9 @@ static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEn
if(ConvertBytesToInteger_4_LE(pBlteHeader->Signature) != BLTE_HEADER_SIGNATURE)
{
// There must be at least some bytes
- if (cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F))
+ if(cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F))
return ERROR_BAD_FORMAT;
- if (pEncodedHeader->EncodedSize != pCKeyEntry->EncodedSize)
+ if(pEncodedHeader->EncodedSize != pCKeyEntry->EncodedSize)
return ERROR_BAD_FORMAT;
#ifdef _DEBUG
@@ -204,15 +205,15 @@ static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEn
// Capture the header size. If this is non-zero, then array
// of chunk headers follow. Otherwise, the file is just one chunk
HeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSize);
- if (HeaderSize != 0)
+ if(HeaderSize != 0)
{
- if (pBlteHeader->MustBe0F != 0x0F)
+ if(pBlteHeader->MustBe0F != 0x0F)
return ERROR_BAD_FORMAT;
// Verify the header size
FrameCount = ConvertBytesToInteger_3(pBlteHeader->FrameCount);
ExpectedHeaderSize = 0x0C + FrameCount * sizeof(BLTE_FRAME);
- if (ExpectedHeaderSize != HeaderSize)
+ if(ExpectedHeaderSize != HeaderSize)
return ERROR_BAD_FORMAT;
// Give the values
@@ -233,12 +234,12 @@ static LPBYTE ReadMissingHeaderData(PCASC_FILE_SPAN pFileSpan, ULONGLONG DataFil
LPBYTE pbNewBuffer;
// Reallocate the buffer
- pbNewBuffer = CASC_REALLOC(BYTE, pbEncodedBuffer, cbTotalHeaderSize);
- if (pbNewBuffer != NULL)
+ pbNewBuffer = CASC_REALLOC(pbEncodedBuffer, cbTotalHeaderSize);
+ if(pbNewBuffer != NULL)
{
// Load the missing data
DataFileOffset += cbEncodedBuffer;
- if (FileStream_Read(pFileSpan->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
+ if(FileStream_Read(pFileSpan->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer)))
{
return pbNewBuffer;
}
@@ -273,14 +274,14 @@ static DWORD LoadSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEnt
assert(pFileSpan->pStream != NULL);
assert(pFileSpan->pFrames == NULL);
- if (pFileSpan->FrameCount != 0)
+ if(pFileSpan->FrameCount != 0)
{
// Move the raw archive offset
- DataFileOffset += (pFileSpan->FrameCount * sizeof(BLTE_FRAME));
+ DataFileOffset += ((ULONGLONG)pFileSpan->FrameCount * sizeof(BLTE_FRAME));
// Allocate array of file frames
pFrames = CASC_ALLOC<CASC_FILE_FRAME>(pFileSpan->FrameCount);
- if (pFrames != NULL)
+ if(pFrames != NULL)
{
// Copy the frames to the file structure
for (DWORD i = 0; i < pFileSpan->FrameCount; i++)
@@ -321,7 +322,7 @@ static DWORD LoadSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEnt
{
// Allocate single "dummy" frame
pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1);
- if (pFrames != NULL)
+ if(pFrames != NULL)
{
// Fill the single frame
memset(&pFrames->FrameHash, 0, sizeof(CONTENT_KEY));
@@ -357,7 +358,7 @@ static DWORD LoadSpanFramesForPlainFile(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_EN
// Allocate single "dummy" frame
pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1);
- if (pFrames != NULL)
+ if(pFrames != NULL)
{
// Setup the size
pFileSpan->EndOffset = pFileSpan->StartOffset + pCKeyEntry->ContentSize;
@@ -392,7 +393,7 @@ static DWORD LoadEncodedHeaderAndSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKE
// Allocate the initial buffer for the encoded headers
pbEncodedBuffer = CASC_ALLOC<BYTE>(MAX_ENCODED_HEADER);
- if (pbEncodedBuffer != NULL)
+ if(pbEncodedBuffer != NULL)
{
ULONGLONG ReadOffset = pFileSpan->ArchiveOffs;
size_t cbTotalHeaderSize;
@@ -407,24 +408,24 @@ static DWORD LoadEncodedHeaderAndSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKE
// Load the entire (eventual) header area. This is faster than doing
// two read operations in a row. Read as much as possible. If the file is cut,
// the FileStream will pad it with zeros
- if (FileStream_Read(pFileSpan->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
+ if(FileStream_Read(pFileSpan->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer))
{
// Parse the BLTE header
dwErrCode = ParseBlteHeader(pFileSpan, pCKeyEntry, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize);
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
{
// If the headers are larger than the initial read size, we read the missing data
pFileSpan->HeaderSize = (DWORD)(cbTotalHeaderSize = cbHeaderSize + (pFileSpan->FrameCount * sizeof(BLTE_FRAME)));
- if (cbTotalHeaderSize > cbEncodedBuffer)
+ if(cbTotalHeaderSize > cbEncodedBuffer)
{
pbEncodedBuffer = ReadMissingHeaderData(pFileSpan, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize);
- if (pbEncodedBuffer == NULL)
+ if(pbEncodedBuffer == NULL)
dwErrCode = GetCascError();
cbEncodedBuffer = cbTotalHeaderSize;
}
// Load the array of frame headers
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
{
assert((ReadOffset + cbHeaderSize) > ReadOffset);
dwErrCode = LoadSpanFrames(pFileSpan, pCKeyEntry, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize);
diff --git a/dep/CascLib/src/CascRootFile_TVFS.cpp b/dep/CascLib/src/CascRootFile_TVFS.cpp
index dbd681758ba..5c099ad3460 100644
--- a/dep/CascLib/src/CascRootFile_TVFS.cpp
+++ b/dep/CascLib/src/CascRootFile_TVFS.cpp
@@ -25,8 +25,12 @@
#define TVFS_PTE_PATH_SEPARATOR_POST 0x0002 // There is path separator after the name
#define TVFS_PTE_NODE_VALUE 0x0004 // The NodeValue in path table entry is valid
-#define TVFS_FOLDER_NODE 0x80000000 // Highest bit is set if a file node is a folder
-#define TVFS_FOLDER_SIZE_MASK 0x7FFFFFFF // Mask to get length of the folder
+#define TVFS_FOLDER_NODE 0x80000000 // Highest bit is set if a file node is a folder
+#define TVFS_FOLDER_SIZE_MASK 0x7FFFFFFF // Mask to get length of the folder
+
+// Uncomment this to parse TVFS root files for World of Warcraft
+// Note that this is signigicantly slower than using the legacy ROOT file
+//#define TVFS_PARSE_WOW_ROOT
//-----------------------------------------------------------------------------
// Local structures
@@ -98,6 +102,14 @@ typedef struct _TVFS_PATH_TABLE_ENTRY
DWORD NodeValue; // Node value
} TVFS_PATH_TABLE_ENTRY, *PTVFS_PATH_TABLE_ENTRY;
+typedef struct _TVFS_WOW_ENTRY
+{
+ DWORD LocaleFlags;
+ USHORT ContentFlags;
+ DWORD FileDataId;
+ BYTE ContentKey[MD5_HASH_SIZE];
+} TVFS_WOW_ENTRY, *PTVFS_WOW_ENTRY;
+
//-----------------------------------------------------------------------------
// Handler definition for TVFS root file
@@ -131,15 +143,15 @@ struct TRootHandler_TVFS : public TFileTreeRoot
bool PathBuffer_AppendNode(CASC_PATH<char> & PathBuffer, TVFS_PATH_TABLE_ENTRY & PathEntry)
{
// Append the prefix separator, if needed
- if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE)
+ if(PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE)
PathBuffer.AppendChar('/');
// Append the name fragment, if any
- if (PathEntry.pbNameEnd > PathEntry.pbNamePtr)
+ if(PathEntry.pbNameEnd > PathEntry.pbNamePtr)
PathBuffer.AppendStringN((const char *)PathEntry.pbNamePtr, (PathEntry.pbNameEnd - PathEntry.pbNamePtr), false);
// Append the postfix separator, if needed
- if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST)
+ if(PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST)
PathBuffer.AppendChar('/');
return true;
@@ -294,19 +306,19 @@ struct TRootHandler_TVFS : public TFileTreeRoot
PathEntry.NodeValue = 0;
// Zero before the name means prefix path separator
- if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
+ if(pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_PRE;
pbPathTablePtr++;
}
// Capture the length of the name fragment
- if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] != 0xFF)
+ if(pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] != 0xFF)
{
// Capture length of the name fragment
size_t nLength = *pbPathTablePtr++;
- if ((pbPathTablePtr + nLength) > pbPathTableEnd)
+ if((pbPathTablePtr + nLength) > pbPathTableEnd)
return NULL;
PathEntry.pbNamePtr = pbPathTablePtr;
PathEntry.pbNameEnd = pbPathTablePtr + nLength;
@@ -314,18 +326,18 @@ struct TRootHandler_TVFS : public TFileTreeRoot
}
// Zero after the name means postfix path separator
- if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
+ if(pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST;
pbPathTablePtr++;
}
- if (pbPathTablePtr < pbPathTableEnd)
+ if(pbPathTablePtr < pbPathTableEnd)
{
// Check for node value
- if (pbPathTablePtr[0] == 0xFF)
+ if(pbPathTablePtr[0] == 0xFF)
{
- if ((pbPathTablePtr + 1 + sizeof(DWORD)) > pbPathTableEnd)
+ if((pbPathTablePtr + 1 + sizeof(DWORD)) > pbPathTableEnd)
return NULL;
PathEntry.NodeValue = ConvertBytesToInteger_4(pbPathTablePtr + 1);
PathEntry.NodeFlags |= TVFS_PTE_NODE_VALUE;
@@ -352,9 +364,9 @@ struct TRootHandler_TVFS : public TFileTreeRoot
for (size_t i = 0; i < ItemCount; i++)
{
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i);
- if (pCKeyEntry != NULL)
+ if(pCKeyEntry != NULL)
{
- if (!memcmp(pCKeyEntry->EKey, EKey, EKeyLength))
+ if(!memcmp(pCKeyEntry->EKey, EKey, EKeyLength))
return true;
}
}
@@ -380,11 +392,11 @@ struct TRootHandler_TVFS : public TFileTreeRoot
{
// Load the entire file into memory
pbVfsData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbVfsData);
- if (pbVfsData && cbVfsData)
+ if(pbVfsData && cbVfsData)
{
// Capture the file folder. This also serves as test
dwErrCode = CaptureDirectoryHeader(SubHeader, pbVfsData, pbVfsData + cbVfsData);
- if (dwErrCode == ERROR_SUCCESS)
+ if(dwErrCode == ERROR_SUCCESS)
return dwErrCode;
// Clear the captured header
@@ -461,12 +473,12 @@ struct TRootHandler_TVFS : public TFileTreeRoot
PathBuffer_AppendNode(PathBuffer, PathEntry);
// Folder component
- if (PathEntry.NodeFlags & TVFS_PTE_NODE_VALUE)
+ if(PathEntry.NodeFlags & TVFS_PTE_NODE_VALUE)
{
// If the TVFS_FOLDER_NODE is set, then the path node is a directory,
// with its data immediately following the path node. Lower 31 bits of NodeValue
// contain the length of the directory (including the NodeValue!)
- if (PathEntry.NodeValue & TVFS_FOLDER_NODE)
+ if(PathEntry.NodeValue & TVFS_FOLDER_NODE)
{
LPBYTE pbDirectoryEnd = pbPathTablePtr + (PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) - sizeof(DWORD);
@@ -475,7 +487,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot
// Recursively call the folder parser on the same file
dwErrCode = ParsePathFileTable(hs, DirHeader, PathBuffer, pbPathTablePtr, pbDirectoryEnd);
- if (dwErrCode != ERROR_SUCCESS)
+ if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Skip the directory data
@@ -512,7 +524,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot
}
// We need to check whether this is another TVFS directory file
- if (IsVfsSubDirectory(hs, DirHeader, SubHeader, SpanEntry.EKey, SpanEntry.ContentSize) == ERROR_SUCCESS)
+ if(IsVfsSubDirectory(hs, DirHeader, SubHeader, SpanEntry.EKey, SpanEntry.ContentSize) == ERROR_SUCCESS)
{
// Add colon (':')
PathBuffer.AppendChar(':');
@@ -522,15 +534,39 @@ struct TRootHandler_TVFS : public TFileTreeRoot
FileTree.InsertByName(pCKeyEntry, PathBuffer);
// Parse the subdir
- ParseDirectoryData(hs, SubHeader, PathBuffer);
+ dwErrCode = ParseDirectoryData(hs, SubHeader, PathBuffer);
CASC_FREE(SubHeader.pbDirectoryData);
+
+ // On error, stop the parsing
+ if(dwErrCode != ERROR_SUCCESS)
+ return dwErrCode;
}
else
{
+ TVFS_WOW_ENTRY WowEntry;
+
// If the content content size is not there, supply it now
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
pCKeyEntry->ContentSize = SpanEntry.ContentSize;
- FileTree.InsertByName(pCKeyEntry, PathBuffer);
+
+ // Detect generic file names from World of Warcraft (since build 45779)
+ switch(dwErrCode = CheckWoWGenericName(PathBuffer, WowEntry))
+ {
+ case ERROR_SUCCESS: // The entry was recognized and has the right format
+ FileTree.InsertByName(pCKeyEntry, PathBuffer, WowEntry.FileDataId, WowEntry.LocaleFlags, WowEntry.ContentFlags);
+ break;
+
+ case ERROR_BAD_FORMAT: // The entry was not recognized as TVFS WoW name
+ FileTree.InsertByName(pCKeyEntry, PathBuffer);
+ break;
+
+ default: // The entry has a bad format - use classic ROOT file
+ assert(dwErrCode == ERROR_REPARSE_ROOT);
+ return dwErrCode;
+ }
+
+ // If not a generic name, insert to the tree
+ //printf("%s\n", (const char *)PathBuffer);
}
}
else
@@ -679,6 +715,51 @@ struct TRootHandler_TVFS : public TFileTreeRoot
return ParseDirectoryData(hs, RootHeader, PathBuffer);
}
+ DWORD CheckWoWGenericName(const CASC_PATH<char> & PathBuffer, TVFS_WOW_ENTRY & WowEntry)
+ {
+ size_t nPathLength = PathBuffer.Length();
+ BYTE BinaryBuffer[4+2+4+16];
+
+ //
+ // WoW Build 45779: 000000020000:000C472F02BA924C604A670B253AA02DBCD9441 (Bug: Missing last digit of the CKey)
+ // WoW Build 46144: 000000020000:000C472F02BA924C604A670B253AA02DBCD9441C
+ // LLLLLLLLCCCC IIIIIIIIKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
+ //
+ // L = Locale flags, C = Content flags, I = File Data ID, K = CKey
+ //
+
+ if(nPathLength == 52 || nPathLength == 53)
+ {
+ if(PathBuffer[12] == ':')
+ {
+ // Check the first part of the TVFS name
+ if(BinaryFromString(&PathBuffer[00], 12, (LPBYTE)(&BinaryBuffer[0])) != ERROR_SUCCESS)
+ return ERROR_REPARSE_ROOT;
+
+ // Check the second part of the file name
+ if(BinaryFromString(&PathBuffer[13], 40, (LPBYTE)(&BinaryBuffer[6])) != ERROR_SUCCESS)
+ return ERROR_REPARSE_ROOT;
+
+#ifdef TVFS_PARSE_WOW_ROOT
+ // We accept strings with length 53 chars
+ if(nPathLength == 53)
+ {
+ WowEntry.LocaleFlags = ConvertBytesToInteger_4(BinaryBuffer + 0x00);
+ WowEntry.ContentFlags = ConvertBytesToInteger_2(BinaryBuffer + 0x04);
+ WowEntry.FileDataId = ConvertBytesToInteger_4(BinaryBuffer + 0x06);
+ memcpy(WowEntry.ContentKey, BinaryBuffer + 0x0A, MD5_HASH_SIZE);
+ return ERROR_SUCCESS;
+ }
+#endif // TVFS_PARSE_WOW_ROOT
+
+ // An invalid entry - reparse tot he normal root
+ CASCLIB_UNUSED(WowEntry);
+ return ERROR_REPARSE_ROOT;
+ }
+ }
+ return ERROR_BAD_FORMAT;
+ }
+
CASC_ARRAY SpanArray; // Array of CASC_SPAN_ENTRY for all multi-span files
};
@@ -701,7 +782,7 @@ DWORD RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootF
{
// Load the root directory. If load failed, we free the object
dwErrCode = pRootHandler->Load(hs, RootHeader);
- if(dwErrCode != ERROR_SUCCESS)
+ if(dwErrCode != ERROR_SUCCESS && dwErrCode != ERROR_REPARSE_ROOT)
{
delete pRootHandler;
pRootHandler = NULL;
diff --git a/dep/CascLib/src/DllMain.def b/dep/CascLib/src/DllMain.def
index 9442184946f..ddda8af921b 100644
--- a/dep/CascLib/src/DllMain.def
+++ b/dep/CascLib/src/DllMain.def
@@ -33,5 +33,9 @@ EXPORTS
CascFindEncryptionKey
CascGetNotFoundEncryptionKey
- GetLastError=Kernel32.GetLastError
- SetLastError=Kernel32.SetLastError
+ CascCdnGetDefault
+ CascCdnDownload
+ CascCdnFree
+
+ GetCascError
+ SetCascError
diff --git a/dep/CascLib/src/common/Array.h b/dep/CascLib/src/common/Array.h
index ea99d10fe22..832a230a8ed 100644
--- a/dep/CascLib/src/common/Array.h
+++ b/dep/CascLib/src/common/Array.h
@@ -191,7 +191,7 @@ class CASC_ARRAY
ItemCountMax = ItemCountMax << 1;
// Allocate new table
- NewItemArray = CASC_REALLOC(BYTE, m_pItemArray, (ItemCountMax * m_ItemSize));
+ NewItemArray = CASC_REALLOC(m_pItemArray, (ItemCountMax * m_ItemSize));
if (NewItemArray == NULL)
return false;
diff --git a/dep/CascLib/src/common/Common.h b/dep/CascLib/src/common/Common.h
index 63996e5503c..3fbbf0d842c 100644
--- a/dep/CascLib/src/common/Common.h
+++ b/dep/CascLib/src/common/Common.h
@@ -140,7 +140,16 @@ extern unsigned char IntToHexChar[];
// - Memory freeing function must check for NULL pointer and do nothing if so
//
-#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type))
+template <typename T>
+T * CASC_REALLOC(T * old_ptr, size_t count)
+{
+ T * new_ptr = (T *)realloc(old_ptr, count * sizeof(T));
+
+ // If realloc fails, then the old buffer remains unfreed
+ if(new_ptr == NULL)
+ free(old_ptr);
+ return new_ptr;
+}
template <typename T>
T * CASC_ALLOC(size_t nCount)
diff --git a/dep/CascLib/src/common/Csv.cpp b/dep/CascLib/src/common/Csv.cpp
index 4afaae10a0a..589c711571a 100644
--- a/dep/CascLib/src/common/Csv.cpp
+++ b/dep/CascLib/src/common/Csv.cpp
@@ -186,9 +186,7 @@ CASC_CSV::~CASC_CSV()
{
if(m_pLines != NULL)
delete[] m_pLines;
- if(m_szCsvFile != NULL)
- delete [] m_szCsvFile;
- m_szCsvFile = NULL;
+ CASC_FREE(m_szCsvFile);
}
DWORD CASC_CSV::SetNextLineProc(CASC_CSV_NEXTPROC PfnNextLineProc, CASC_CSV_NEXTPROC PfnNextColProc, void * pvUserData)
@@ -233,7 +231,7 @@ DWORD CASC_CSV::Load(LPBYTE pbData, size_t cbData)
{
DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
- m_szCsvFile = new char[cbData + 1];
+ m_szCsvFile = CASC_ALLOC<char>(cbData + 1);
if (m_szCsvFile != NULL)
{
// Copy the entire data and terminate them with zero
diff --git a/dep/CascLib/src/common/FileStream.cpp b/dep/CascLib/src/common/FileStream.cpp
index 498729bab6f..622af421a82 100644
--- a/dep/CascLib/src/common/FileStream.cpp
+++ b/dep/CascLib/src/common/FileStream.cpp
@@ -182,7 +182,7 @@ static bool BaseFile_Read(
pStream->Base.File.FilePos = ByteOffset;
// Read the data
- if (dwBytesToRead != 0)
+ if(dwBytesToRead != 0)
{
OVERLAPPED Overlapped;
@@ -204,9 +204,9 @@ static bool BaseFile_Read(
// If the byte offset is different from the current file position,
// we have to update the file position
- if (ByteOffset != pStream->Base.File.FilePos)
+ if(ByteOffset != pStream->Base.File.FilePos)
{
- if (lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET) == (off64_t)-1)
+ if(lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET) == (off64_t)-1)
{
CascUnlock(pStream->Lock);
SetCascError(errno);
@@ -216,10 +216,10 @@ static bool BaseFile_Read(
}
// Perform the read operation
- if (dwBytesToRead != 0)
+ if(dwBytesToRead != 0)
{
bytes_read = read((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToRead);
- if (bytes_read == -1)
+ if(bytes_read == -1)
{
CascUnlock(pStream->Lock);
SetCascError(errno);
@@ -282,7 +282,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
pStream->Base.File.FilePos = ByteOffset;
// Read the data
- if (dwBytesToWrite != 0)
+ if(dwBytesToWrite != 0)
{
OVERLAPPED Overlapped;
@@ -304,9 +304,9 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
// If the byte offset is different from the current file position,
// we have to update the file position
- if (ByteOffset != pStream->Base.File.FilePos)
+ if(ByteOffset != pStream->Base.File.FilePos)
{
- if (lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET) == (off64_t)-1)
+ if(lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET) == (off64_t)-1)
{
CascUnlock(pStream->Lock);
SetCascError(errno);
@@ -317,7 +317,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const
// Perform the read operation
bytes_written = write((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToWrite);
- if (bytes_written == -1)
+ if(bytes_written == -1)
{
CascUnlock(pStream->Lock);
SetCascError(errno);
@@ -611,22 +611,48 @@ static void BaseMap_Init(TFileStream * pStream)
//-----------------------------------------------------------------------------
// Local functions - base HTTP file support
-static DWORD BaseHttp_ParseURL(TFileStream * pStream, LPCTSTR szFileName)
+static DWORD BaseHttp_ParseURL(TFileStream * pStream, LPCTSTR szFileName, int * pPortNum)
{
- LPCTSTR szFilePtr = szFileName;
- char * hostName;
- char * fileName;
+ LPCTSTR szHostNamePtr = szFileName;
+ LPCTSTR szHostNameEnd = szFileName;
+ LPCTSTR szPortPtr = NULL;
+ LPCTSTR szPortEnd = NULL;
+ LPCTSTR szFilePtr;
+ size_t nLength;
+ LPSTR hostName = NULL;
+ LPSTR fileName = NULL;
+ char szPort[20];
+
+ // Find the end of the host name
+ while(szHostNameEnd[0] != 0 && szHostNameEnd[0] != ':' && szHostNameEnd[0] != '/')
+ szHostNameEnd++;
+ szFilePtr = szHostNameEnd;
- // Find the end od the host name
- if((szFilePtr = _tcschr(szFileName, '/')) == NULL)
- return ERROR_INVALID_PARAMETER;
+ // Is there port number?
+ if(szHostNameEnd[0] == ':')
+ {
+ // Set the range of the port
+ szPortPtr = szPortEnd = szHostNameEnd + 1;
+ while(szPortEnd[0] != 0 && szPortEnd[0] != '/')
+ szPortEnd++;
+ szFilePtr = szPortEnd;
+ }
- // Allocate and copy the host name
- if((hostName = CASC_ALLOC<char>(szFilePtr - szFileName + 1)) != NULL)
+ // Allocate the host name
+ nLength = szHostNameEnd - szHostNamePtr + 1;
+ if((hostName = CASC_ALLOC<char>(nLength)) != NULL)
{
- CascStrCopy(hostName, 256, szFileName, (szFilePtr - szFileName));
+ // Copy the host name
+ CascStrCopy(hostName, nLength, szHostNamePtr, (szHostNameEnd - szHostNamePtr));
- // Allocate and copy the resource name
+ // Parse port, if present
+ if(szPortPtr != NULL && szPortEnd > szPortPtr)
+ {
+ CascStrCopy(szPort, _countof(szPort), szPortPtr, (szPortEnd - szPortPtr));
+ pPortNum[0] = atoi(szPort);
+ }
+
+ // Allocate file name
if((fileName = CascNewStrT2A(szFilePtr)) != NULL)
{
pStream->Base.Socket.hostName = hostName;
@@ -634,12 +660,16 @@ static DWORD BaseHttp_ParseURL(TFileStream * pStream, LPCTSTR szFileName)
return ERROR_SUCCESS;
}
+ // Free the host name
CASC_FREE(hostName);
}
return ERROR_NOT_ENOUGH_MEMORY;
}
+//-----------------------------------------------------------------------------
+// Local functions - base HTTP file support
+
static bool BaseHttp_Download(TFileStream * pStream)
{
CASC_MIME Mime;
@@ -654,6 +684,9 @@ static bool BaseHttp_Download(TFileStream * pStream)
// If we already have the data, it's success
if(pStream->Base.Socket.fileData == NULL)
{
+ // Reset the file data length as well
+ pStream->Base.Socket.fileDataLength = 0;
+
// Construct the request, either HTTP or Ribbit (https://wowdev.wiki/Ribbit).
// Note that Ribbit requests don't start with slash
if((pStream->dwFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_RIBBIT)
@@ -668,13 +701,22 @@ static bool BaseHttp_Download(TFileStream * pStream)
server_response = pStream->Base.Socket.pSocket->ReadResponse(request, request_length, &response_length);
if(server_response != NULL)
{
- // Decode the MIME document
- if((dwErrCode = Mime.Load(server_response, response_length)) == ERROR_SUCCESS)
+ // Check for non-zero data
+ if(response_length != 0)
{
- // Move the data from MIME to HTTP stream
- pStream->Base.Socket.fileData = Mime.GiveAway(&pStream->Base.Socket.fileDataLength);
+ // Decode the MIME document
+ if((dwErrCode = Mime.Load(server_response, response_length)) == ERROR_SUCCESS)
+ {
+ // Move the data from MIME to HTTP stream
+ pStream->Base.Socket.fileData = Mime.GiveAway(&pStream->Base.Socket.fileDataLength);
+ }
+ }
+ else
+ {
+ SetCascError(ERROR_BAD_FORMAT);
}
+ // Free the buffer
CASC_FREE(server_response);
}
}
@@ -685,15 +727,13 @@ static bool BaseHttp_Download(TFileStream * pStream)
static bool BaseHttp_Open(TFileStream * pStream, LPCTSTR szFileName, DWORD dwStreamFlags)
{
+ PCASC_SOCKET pSocket;
DWORD dwErrCode;
+ int portNum = ((dwStreamFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_RIBBIT) ? CASC_PORT_RIBBIT : CASC_PORT_HTTP;
// Extract the server part
- if((dwErrCode = BaseHttp_ParseURL(pStream, szFileName)) == ERROR_SUCCESS)
+ if((dwErrCode = BaseHttp_ParseURL(pStream, szFileName, &portNum)) == ERROR_SUCCESS)
{
- // Determine the proper port
- PCASC_SOCKET pSocket;
- int portNum = ((dwStreamFlags & BASE_PROVIDER_MASK) == BASE_PROVIDER_RIBBIT) ? CASC_PORT_RIBBIT : CASC_PORT_HTTP;
-
// Initiate the remote connection
if((pSocket = sockets_connect(pStream->Base.Socket.hostName, portNum)) != NULL)
{
@@ -771,8 +811,7 @@ static bool BaseHttp_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
CascLock(pStream->Lock);
{
// Make sure that we have the file data
- bResult = BaseHttp_Download(pStream);
- if(bResult)
+ if((bResult = BaseHttp_Download(pStream)) != false)
{
*pFileSize = pStream->Base.Socket.fileDataLength;
}
diff --git a/dep/CascLib/src/common/FileTree.cpp b/dep/CascLib/src/common/FileTree.cpp
index 6a44940bd8a..490698122e5 100644
--- a/dep/CascLib/src/common/FileTree.cpp
+++ b/dep/CascLib/src/common/FileTree.cpp
@@ -114,7 +114,7 @@ bool CASC_FILE_TREE::InsertToIdTable(PCASC_FILE_NODE pFileNode)
if(FileDataId != CASC_INVALID_ID)
{
// Sanity check
- assert(FileDataId < 0x10000000);
+ assert(FileDataId < CASC_INVALID_ID);
// Insert the element to the array
RefElement = (PCASC_FILE_NODE *)FileDataIds.InsertAt(FileDataId);
diff --git a/dep/CascLib/src/common/Mime.cpp b/dep/CascLib/src/common/Mime.cpp
index 52a3a5f645f..faa3402d832 100644
--- a/dep/CascLib/src/common/Mime.cpp
+++ b/dep/CascLib/src/common/Mime.cpp
@@ -37,13 +37,13 @@ static size_t DecodeValueInt32(const char * string, const char * string_end)
return result;
}
-bool CASC_MIME_HTTP::IsDataComplete(const char * response, size_t response_length)
+size_t CASC_MIME_HTTP::IsDataComplete(const char * response, size_t response_length, size_t * ptr_content_length)
{
const char * content_length_ptr;
const char * content_begin_ptr;
// Do not parse the HTTP response multiple times
- if(response_valid == 0 && response_length > 8)
+ if((http_flags & HTTP_HEADER_COMPLETE) == 0 && response_length > 8)
{
// Check the begin of the response
if(!strncmp(response, "HTTP/1.1", 8))
@@ -52,24 +52,30 @@ bool CASC_MIME_HTTP::IsDataComplete(const char * response, size_t response_lengt
if((content_begin_ptr = strstr(response, "\r\n\r\n")) != NULL)
{
// HTTP responses contain "Content-Length: %u\n\r"
- if((content_length_ptr = strstr(response, "Content-Length: ")) != NULL)
+ if((content_length_ptr = strstr(response, "Content-Length: ")) == NULL)
+ content_length_ptr = strstr(response, "content-length: ");
+ if(content_length_ptr != NULL)
{
- // The content length info muse be before the actual content
+ // The content length info must be before the actual content
if(content_length_ptr < content_begin_ptr)
{
// Fill the HTTP info cache
- response_valid = 0x48545450; // 'HTTP'
content_offset = (content_begin_ptr + 4) - response;
content_length = DecodeValueInt32(content_length_ptr + 16, content_begin_ptr);
total_length = content_offset + content_length;
+ http_flags = HTTP_HEADER_COMPLETE;
}
}
}
}
}
- // If we know the expected total length, we can tell whether it's complete or not
- return ((response_valid != 0) && (total_length == response_length));
+ // Update flags
+ if((http_flags & HTTP_HEADER_COMPLETE) && (ptr_content_length != NULL))
+ ptr_content_length[0] = content_length;
+ if(total_length == response_length)
+ http_flags |= HTTP_DATA_COMPLETE;
+ return http_flags;
}
//-----------------------------------------------------------------------------
diff --git a/dep/CascLib/src/common/Mime.h b/dep/CascLib/src/common/Mime.h
index 01dc35fdac9..dc7db517179 100644
--- a/dep/CascLib/src/common/Mime.h
+++ b/dep/CascLib/src/common/Mime.h
@@ -16,6 +16,10 @@
#define MAX_LENGTH_BOUNDARY 128
+// Flags returned by CASC_MIME_HTTP::IsDataComplete()
+#define HTTP_HEADER_COMPLETE 0x01 // HTML header is complete
+#define HTTP_DATA_COMPLETE 0x02 // HTML data is complete
+
enum CASC_MIME_ENCODING
{
MimeEncodingTextPlain,
@@ -31,15 +35,15 @@ struct CASC_MIME_HTTP
{
CASC_MIME_HTTP()
{
- response_valid = content_length = content_offset = total_length = 0;
+ total_length = content_offset = content_length = http_flags = 0;
}
- bool IsDataComplete(const char * response, size_t response_length);
+ size_t IsDataComplete(const char * response, size_t response_length, size_t * ptr_content_length = NULL);
- size_t response_valid; // Nonzero if this is an already parsed HTTP response
size_t content_length; // Parsed value of "Content-Length"
size_t content_offset; // Offset of the HTTP data, relative to the begin of the response
size_t total_length; // Expected total length of the HTTP response (content_offset + content_size)
+ size_t http_flags; // Nonzero if this is an already parsed HTTP response
};
//-----------------------------------------------------------------------------
@@ -122,9 +126,4 @@ class CASC_MIME
CASC_MIME_ELEMENT root;
};
-//-----------------------------------------------------------------------------
-// HTTP helpers
-
-bool IsHttpResponseComplete(CASC_MIME_HTTP & HttpInfo, const char * response, size_t response_length);
-
#endif // __MIME_H__
diff --git a/dep/CascLib/src/common/Path.h b/dep/CascLib/src/common/Path.h
index 98ba601d33b..56489c5f4d9 100644
--- a/dep/CascLib/src/common/Path.h
+++ b/dep/CascLib/src/common/Path.h
@@ -22,6 +22,7 @@ struct CASC_PATH
m_szBufferBegin = m_szBufferPtr = m_Buffer;
m_szBufferEnd = m_szBufferBegin + _countof(m_Buffer);
m_chSeparator = (xchar)chSeparator;
+ m_bLocalCache = 0;
m_Buffer[0] = 0;
}
@@ -34,11 +35,21 @@ struct CASC_PATH
}
// LPCTSTR szPath = Path;
- operator const xchar *()
+ operator const xchar *() const
{
return m_szBufferBegin;
}
+ void SetLocalCaching(int bLocalCache)
+ {
+ m_bLocalCache = (xchar)(bLocalCache != 0);
+ }
+
+ bool LocalCaching()
+ {
+ return (m_bLocalCache != 0);
+ }
+
// LPTSTR szPath = Path.New();
xchar * New()
{
@@ -81,7 +92,7 @@ struct CASC_PATH
return true;
}
- size_t Length()
+ size_t Length() const
{
return m_szBufferPtr - m_szBufferBegin;
}
@@ -177,6 +188,7 @@ struct CASC_PATH
xchar * m_szBufferEnd;
xchar m_Buffer[MAX_PATH];
xchar m_chSeparator;
+ xchar m_bLocalCache:1;
};
#endif // __CASC_PATH_H__
diff --git a/dep/CascLib/src/common/RootHandler.cpp b/dep/CascLib/src/common/RootHandler.cpp
index b33f96209d9..fa34346b9ce 100644
--- a/dep/CascLib/src/common/RootHandler.cpp
+++ b/dep/CascLib/src/common/RootHandler.cpp
@@ -58,10 +58,21 @@ PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, DWORD FileDataI
return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL;
}
+PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(size_t nFileIndex, char * szFileName, size_t ccFileName)
+{
+ PCASC_CKEY_ENTRY pCKeyEntry = NULL;
+ PCASC_FILE_NODE pFileNode;
+
+ // Perform the search in the underlying file tree
+ if((pFileNode = FileTree.PathAt(szFileName, ccFileName, nFileIndex)) != NULL)
+ pCKeyEntry = pFileNode->pCKeyEntry;
+ return pCKeyEntry;
+}
+
PCASC_CKEY_ENTRY TFileTreeRoot::Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
{
PCASC_FILE_NODE pFileNode;
- size_t nMaxFileIndex = FileTree.GetMaxFileIndex();
+ size_t nMaxFileIndex = GetMaxFileIndex();
// Are we still inside the root directory range?
while(pSearch->nFileIndex < nMaxFileIndex)
@@ -111,3 +122,29 @@ bool TFileTreeRoot::GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, PCASC_FILE_FULL_INFO pF
return false;
}
+size_t TFileTreeRoot::Copy(TRootHandler * pRoot)
+{
+ PCASC_CKEY_ENTRY pCKeyEntry;
+ size_t nMaxFileIndex = GetMaxFileIndex();
+ size_t nItemsCopied = 0;
+ char szFileName[0x200];
+
+ for(size_t nFileIndex = 0; nFileIndex < nMaxFileIndex; nFileIndex++)
+ {
+ if((pCKeyEntry = pRoot->GetFile(nFileIndex, szFileName, _countof(szFileName))) != NULL)
+ {
+ if(szFileName[0] != 0)
+ {
+ Insert(szFileName, pCKeyEntry);
+ nItemsCopied++;
+ }
+ }
+ }
+
+ return nItemsCopied;
+}
+
+size_t TFileTreeRoot::GetMaxFileIndex()
+{
+ return FileTree.GetMaxFileIndex();
+}
diff --git a/dep/CascLib/src/common/RootHandler.h b/dep/CascLib/src/common/RootHandler.h
index 4e56650b034..f9ea76b5f3a 100644
--- a/dep/CascLib/src/common/RootHandler.h
+++ b/dep/CascLib/src/common/RootHandler.h
@@ -62,6 +62,14 @@ struct TRootHandler
return NULL;
}
+ // Searches the file by file name
+ // nFileIndex - Index to the table
+ // szFileName - Pointer to the file name
+ virtual PCASC_CKEY_ENTRY GetFile(size_t /* nFileIndex */, char * /* szFileName */, size_t /* ccFileName */)
+ {
+ return NULL;
+ }
+
// Performs find-next-file operation
// pSearch - Pointer to the initialized search structure
// pFindData - Pointer to output structure that will contain the information
@@ -78,6 +86,19 @@ struct TRootHandler
return false;
}
+ // Copies all items from the given root handler to the new one
+ virtual size_t Copy(TRootHandler * /* pRoot */)
+ {
+ return 0;
+ }
+
+ // Returns the maximum file index
+ virtual size_t GetMaxFileIndex()
+ {
+ return 0;
+ }
+
+ // Returns the list of features
DWORD GetFeatures()
{
return dwFeatures;
@@ -100,8 +121,11 @@ struct TFileTreeRoot : public TRootHandler
PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, const char * szFileName);
PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, DWORD FileDataId);
+ PCASC_CKEY_ENTRY GetFile(size_t nFileIndex, char * /* szFileName */, size_t /* ccFileName */);
PCASC_CKEY_ENTRY Search(struct TCascSearch * pSearch, struct _CASC_FIND_DATA * pFindData);
bool GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, struct _CASC_FILE_FULL_INFO * pFileInfo);
+ size_t Copy(TRootHandler * pRoot);
+ size_t GetMaxFileIndex();
protected:
diff --git a/dep/CascLib/src/common/Sockets.cpp b/dep/CascLib/src/common/Sockets.cpp
index dee5643d42e..cf3ffe9d0b1 100644
--- a/dep/CascLib/src/common/Sockets.cpp
+++ b/dep/CascLib/src/common/Sockets.cpp
@@ -16,6 +16,8 @@
//-----------------------------------------------------------------------------
// Local variables
+#define BUFFER_INITIAL_SIZE 0x8000
+
CASC_SOCKET_CACHE SocketCache;
//-----------------------------------------------------------------------------
@@ -26,9 +28,12 @@ char * CASC_SOCKET::ReadResponse(const char * request, size_t request_length, si
{
CASC_MIME_HTTP HttpInfo;
char * server_response = NULL;
+ size_t content_length = 0;
size_t total_received = 0;
- size_t block_increment = 0x8000;
- size_t buffer_size = block_increment;
+ size_t buffer_length = BUFFER_INITIAL_SIZE;
+ size_t buffer_delta = BUFFER_INITIAL_SIZE;
+ size_t http_flags = 0;
+ DWORD dwErrCode = ERROR_SUCCESS;
int bytes_received = 0;
// Pre-set the result length
@@ -54,41 +59,73 @@ char * CASC_SOCKET::ReadResponse(const char * request, size_t request_length, si
}
// Allocate buffer for server response. Allocate one extra byte for zero terminator
- if((server_response = CASC_ALLOC<char>(buffer_size + 1)) != NULL)
+ if((server_response = CASC_ALLOC<char>(buffer_length + 1)) != NULL)
{
- for(;;)
+ while((http_flags & HTTP_DATA_COMPLETE) == 0)
{
// Reallocate the buffer size, if needed
- if(total_received == buffer_size)
+ if(total_received == buffer_length)
{
- if((server_response = CASC_REALLOC(char, server_response, buffer_size + block_increment + 1)) == NULL)
+ // Reallocate the buffer
+ if((server_response = CASC_REALLOC(server_response, buffer_length + buffer_delta + 1)) == NULL)
{
- SetCascError(ERROR_NOT_ENOUGH_MEMORY);
- CascUnlock(Lock);
- return NULL;
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+ break;
}
- buffer_size += block_increment;
+ buffer_length += buffer_delta;
+ buffer_delta = BUFFER_INITIAL_SIZE;
}
// Receive the next part of the response, up to buffer size
// Return value 0 means "connection closed", -1 means an error
- bytes_received = recv(sock, server_response + total_received, (int)(buffer_size - total_received), 0);
+ bytes_received = recv(sock, server_response + total_received, (int)(buffer_length - total_received), 0);
if(bytes_received <= 0)
break;
+ // Verify buffer overflow
+ if((total_received + bytes_received) < total_received)
+ {
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+ break;
+ }
+
// Append the number of bytes received. Also terminate response with zero
total_received += bytes_received;
server_response[total_received] = 0;
// On a HTTP protocol, we need to check whether we received all data
- if(HttpInfo.IsDataComplete(server_response, total_received))
- break;
+ http_flags = HttpInfo.IsDataComplete(server_response, total_received, &content_length);
+ if(http_flags & HTTP_HEADER_COMPLETE)
+ {
+ // Check for maximum file size
+ if(content_length > CASC_MAX_ONLINE_FILE_SIZE)
+ {
+ dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
+ break;
+ }
+
+ // If we just retrieved the content length, we temporarily increment
+ // the buffer delta. This will make next reallocation to make buffer
+ // large enough to prevent abundant reallocations and memory memcpy's
+ if(content_length > buffer_length)
+ {
+ buffer_delta = (HttpInfo.content_offset + content_length) - buffer_length;
+ }
+ }
}
}
// Unlock the socket
CascUnlock(Lock);
+ // Low memory condition: Delete the server response
+ if(dwErrCode != ERROR_SUCCESS)
+ {
+ CASC_FREE(server_response);
+ SetCascError(dwErrCode);
+ total_received = 0;
+ }
+
// Give the result to the caller
if(PtrLength != NULL)
PtrLength[0] = total_received;
@@ -143,7 +180,7 @@ DWORD CASC_SOCKET::GetAddrInfoWrapper(const char * hostName, unsigned portNum, P
continue;
}
#endif
- case EAI_AGAIN: // Temporary error, try again
+ case (DWORD)EAI_AGAIN: // Temporary error, try again
continue;
default: // Any other result, incl. ERROR_SUCCESS
@@ -428,7 +465,7 @@ PCASC_SOCKET sockets_connect(const char * hostName, unsigned portNum)
pSocket = CASC_SOCKET::Connect(hostName, portNum);
// Insert it to the cache, if it's a HTTP connection
- if(pSocket->portNum == CASC_PORT_HTTP)
+ if(pSocket != NULL && pSocket->portNum == CASC_PORT_HTTP)
pSocket = SocketCache.InsertSocket(pSocket);
}
diff --git a/dep/PackageList.txt b/dep/PackageList.txt
index 06bb61e130f..bdcf4e72d0e 100644
--- a/dep/PackageList.txt
+++ b/dep/PackageList.txt
@@ -63,7 +63,7 @@ catch2
CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014)
https://github.com/ladislav-zezula/CascLib
- Version: 4fc4c18bd5a49208337199a7f4256271675cae44
+ Version: 136c6e05537bd7123620ddb28671d1f2cf060e0b
rapidjson (A fast JSON parser/generator for C++ with both SAX/DOM style API http://rapidjson.org/)
https://github.com/Tencent/rapidjson