diff options
-rw-r--r-- | dep/CascLib/src/CascCommon.h | 16 | ||||
-rw-r--r-- | dep/CascLib/src/CascDumpData.cpp | 1 | ||||
-rw-r--r-- | dep/CascLib/src/CascFiles.cpp | 299 | ||||
-rw-r--r-- | dep/CascLib/src/CascLib.h | 46 | ||||
-rw-r--r-- | dep/CascLib/src/CascOpenStorage.cpp | 294 | ||||
-rw-r--r-- | dep/CascLib/src/CascPort.h | 13 | ||||
-rw-r--r-- | dep/CascLib/src/CascReadFile.cpp | 39 | ||||
-rw-r--r-- | dep/CascLib/src/CascRootFile_TVFS.cpp | 127 | ||||
-rw-r--r-- | dep/CascLib/src/DllMain.def | 8 | ||||
-rw-r--r-- | dep/CascLib/src/common/Array.h | 2 | ||||
-rw-r--r-- | dep/CascLib/src/common/Common.h | 11 | ||||
-rw-r--r-- | dep/CascLib/src/common/Csv.cpp | 6 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileStream.cpp | 101 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileTree.cpp | 2 | ||||
-rw-r--r-- | dep/CascLib/src/common/Mime.cpp | 20 | ||||
-rw-r--r-- | dep/CascLib/src/common/Mime.h | 15 | ||||
-rw-r--r-- | dep/CascLib/src/common/Path.h | 16 | ||||
-rw-r--r-- | dep/CascLib/src/common/RootHandler.cpp | 39 | ||||
-rw-r--r-- | dep/CascLib/src/common/RootHandler.h | 24 | ||||
-rw-r--r-- | dep/CascLib/src/common/Sockets.cpp | 67 | ||||
-rw-r--r-- | dep/PackageList.txt | 2 |
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 |