diff --git a/.gitignore b/.gitignore index 3180547e..921c2aa3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ /*.cmake /Makefile /CMakeScripts/ +/build/ # Visual Studio related files *.opensdf diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dd1e50c..e053aac0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 3.2) project(CascLib) set(HEADER_FILES @@ -13,8 +13,10 @@ set(HEADER_FILES src/common/FileTree.h src/common/ListFile.h src/common/Map.h + src/common/Mime.h src/common/Path.h src/common/RootHandler.h + src/common/Sockets.h src/jenkins/lookup.h ) @@ -25,7 +27,9 @@ set(SRC_FILES src/common/FileStream.cpp src/common/FileTree.cpp src/common/ListFile.cpp + src/common/Mime.cpp src/common/RootHandler.cpp + src/common/Sockets.cpp src/jenkins/lookup3.c src/md5/md5.cpp src/CascDecompress.cpp @@ -73,10 +77,10 @@ if(WIN32) set(SRC_ADDITIONAL_FILES ${ZLIB_FILES}) set(LINK_LIBS wininet) if(CASC_UNICODE) - message(STATUS "Build UNICODE version") + message(STATUS "Build UNICODE version") add_definitions(-DUNICODE -D_UNICODE) else() - message(STATUS "Build ANSI version") + message(STATUS "Build ANSI version") endif() endif() @@ -93,57 +97,58 @@ endif() option(CASC_BUILD_SHARED_LIB "Compile dynamically linked library" ON) if(CASC_BUILD_SHARED_LIB) - message(STATUS "Build dynamically linked library") - add_library(casc SHARED ${SRC_FILES} ${HEADER_FILES} ${SRC_ADDITIONAL_FILES}) - target_link_libraries(casc ${LINK_LIBS}) - install(TARGETS casc RUNTIME DESTINATION bin LIBRARY DESTINATION lib${LIB_SUFFIX} ARCHIVE DESTINATION lib${LIB_SUFFIX} FRAMEWORK DESTINATION /Library/Frameworks) - target_include_directories(casc - PUBLIC - $ - $ - ) - # On Win32, build CascLib.dll - if(WIN32) - set_target_properties(casc PROPERTIES OUTPUT_NAME CascLib) - endif() + message(STATUS "Build dynamically linked library") + add_library(casc SHARED ${SRC_FILES} ${HEADER_FILES} ${SRC_ADDITIONAL_FILES}) + if(APPLE) + set_target_properties(casc PROPERTIES FRAMEWORK true) + set_target_properties(casc PROPERTIES PUBLIC_HEADER "src/CascLib.h src/CascPort.h") + set_target_properties(casc PROPERTIES LINK_FLAGS "-framework Carbon") + endif() -if(APPLE) - set_target_properties(casc PROPERTIES FRAMEWORK true) - set_target_properties(casc PROPERTIES PUBLIC_HEADER "src/CascLib.h src/CascPort.h") - set_target_properties(casc PROPERTIES LINK_FLAGS "-framework Carbon") -endif() - -if(UNIX) - set_target_properties(casc PROPERTIES VERSION 1.0.0) - set_target_properties(casc PROPERTIES SOVERSION 1) -endif() + if(UNIX) + set_target_properties(casc PROPERTIES VERSION 1.0.0) + set_target_properties(casc PROPERTIES SOVERSION 1) + endif() + + target_link_libraries(casc ${LINK_LIBS}) + install(TARGETS casc RUNTIME DESTINATION bin LIBRARY DESTINATION lib${LIB_SUFFIX} ARCHIVE DESTINATION lib${LIB_SUFFIX} FRAMEWORK DESTINATION /Library/Frameworks) + target_include_directories(casc + PUBLIC + $ + $ + ) + # On Win32, build CascLib.dll + if(WIN32) + set_target_properties(casc PROPERTIES OUTPUT_NAME CascLib) + endif() endif() option(CASC_BUILD_TESTS "Build Test application" OFF) if(CASC_BUILD_TESTS) - set(CASC_BUILD_STATIC_LIB ON CACHE BOOL "Force Static library building to link test app") - message(STATUS "Build Test application") + set(CASC_BUILD_STATIC_LIB ON CACHE BOOL "Force Static library building to link test app" FORCE) + message(STATUS "Build Test application") add_executable(casc_test ${TEST_SRC_FILES}) + set_target_properties(casc_test PROPERTIES LINK_FLAGS "-pthread") target_link_libraries(casc_test casc_static) - install(TARGETS casc_test RUNTIME DESTINATION bin) + install(TARGETS casc_test RUNTIME DESTINATION bin) endif() option(CASC_BUILD_STATIC_LIB "Build static linked library" OFF) if(CASC_BUILD_STATIC_LIB) - message(STATUS "Build static linked library") + message(STATUS "Build static linked library") add_library(casc_static STATIC ${SRC_FILES} ${HEADER_FILES} ${SRC_ADDITIONAL_FILES}) target_link_libraries(casc_static ${LINK_LIBS}) set_target_properties(casc_static PROPERTIES OUTPUT_NAME casc) - target_include_directories(casc_static - PUBLIC - $ - $ - ) - install(TARGETS casc_static RUNTIME DESTINATION bin LIBRARY DESTINATION lib${LIB_SUFFIX} ARCHIVE DESTINATION lib${LIB_SUFFIX} FRAMEWORK DESTINATION /Library/Frameworks) - - if(APPLE) + target_include_directories(casc_static + PUBLIC + $ + $ + ) + install(TARGETS casc_static RUNTIME DESTINATION bin LIBRARY DESTINATION lib${LIB_SUFFIX} ARCHIVE DESTINATION lib${LIB_SUFFIX} FRAMEWORK DESTINATION /Library/Frameworks) + + if(APPLE) set_target_properties(casc_static PROPERTIES FRAMEWORK false) set_target_properties(casc_static PROPERTIES PUBLIC_HEADER "src/CascLib.h src/CascPort.h") set_target_properties(casc_static PROPERTIES LINK_FLAGS "-framework Carbon") diff --git a/CascLib_vs08.vcproj b/CascLib_vs08.vcproj index 700be812..d99a6b00 100644 --- a/CascLib_vs08.vcproj +++ b/CascLib_vs08.vcproj @@ -1213,6 +1213,22 @@ RelativePath=".\src\common\RootHandler.h" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -606,6 +608,8 @@ + + diff --git a/CascLib_vs19.vcxproj.filters b/CascLib_vs19.vcxproj.filters index fbfb7c92..570f2cf2 100644 --- a/CascLib_vs19.vcxproj.filters +++ b/CascLib_vs19.vcxproj.filters @@ -71,6 +71,12 @@ Source Files\md5 + + Source Files\common + + + Source Files\common + @@ -172,5 +178,11 @@ Source Files\zlib + + Source Files\common + + + Source Files\common + \ No newline at end of file diff --git a/CascLib_vs19_dll.vcxproj b/CascLib_vs19_dll.vcxproj index c9b16483..9ae0d6c9 100644 --- a/CascLib_vs19_dll.vcxproj +++ b/CascLib_vs19_dll.vcxproj @@ -215,6 +215,8 @@ + + @@ -239,6 +241,8 @@ + + diff --git a/CascLib_vs19_dll.vcxproj.filters b/CascLib_vs19_dll.vcxproj.filters index da148349..1e714182 100644 --- a/CascLib_vs19_dll.vcxproj.filters +++ b/CascLib_vs19_dll.vcxproj.filters @@ -120,6 +120,12 @@ Source Files\zlib + + Source Files\common + + + Source Files\common + @@ -167,6 +173,12 @@ Source Files\zlib + + Source Files\common + + + Source Files\common + diff --git a/CascLib_vs19_test.vcxproj b/CascLib_vs19_test.vcxproj index 62dc9f39..e07874af 100644 --- a/CascLib_vs19_test.vcxproj +++ b/CascLib_vs19_test.vcxproj @@ -308,6 +308,8 @@ + + Level1 Level1 @@ -368,6 +370,8 @@ + + diff --git a/CascLib_vs19_test.vcxproj.filters b/CascLib_vs19_test.vcxproj.filters index 5837e889..ec6526cc 100644 --- a/CascLib_vs19_test.vcxproj.filters +++ b/CascLib_vs19_test.vcxproj.filters @@ -127,6 +127,12 @@ Source Files\zlib + + Source Files\common + + + Source Files\common + @@ -171,6 +177,12 @@ Header Files + + Source Files\common + + + Source Files\common + diff --git a/doc/history.txt b/doc/history.txt deleted file mode 100644 index bc520215..00000000 --- a/doc/history.txt +++ /dev/null @@ -1,6 +0,0 @@ -CascLib history -=============== - -Version 1.00 - -- Created diff --git a/sources-cpp.cpp b/sources-cpp.cpp index 0760180e..64351f25 100644 --- a/sources-cpp.cpp +++ b/sources-cpp.cpp @@ -4,7 +4,9 @@ #include "src\common\FileStream.cpp" #include "src\common\FileTree.cpp" #include "src\common\ListFile.cpp" +#include "src\common\Mime.cpp" #include "src\common\RootHandler.cpp" +#include "src\common\Sockets.cpp" #include "src\md5\md5.cpp" #include "src\CascDecompress.cpp" #include "src\CascDecrypt.cpp" diff --git a/src/CascCommon.h b/src/CascCommon.h index aa0aff87..cddd0a8c 100644 --- a/src/CascCommon.h +++ b/src/CascCommon.h @@ -30,8 +30,10 @@ #include "common/Directory.h" #include "common/ListFile.h" #include "common/Csv.h" +#include "common/Mime.h" #include "common/Path.h" #include "common/RootHandler.h" +#include "common/Sockets.h" // Headers from Alexander Peslyak's MD5 implementation #include "md5/md5.h" @@ -58,8 +60,7 @@ #define CASC_MAGIC_FIND 0x444E494643534143 // 'CASCFIND' // For CASC_CDN_DOWNLOAD::Flags -#define CASC_CDN_FLAG_PORT1119 0x0001 // Use port 1119 -#define CASC_CDN_FORCE_DOWNLOAD 0x0002 // Force downloading the file even if in the cache +#define CASC_CDN_FORCE_DOWNLOAD 0x0001 // Force downloading the file even if in the cache //----------------------------------------------------------------------------- // In-memory structures @@ -101,7 +102,7 @@ typedef struct _CASC_INDEX typedef struct _CASC_INDEX_HEADER { USHORT IndexVersion; // 5 for index v 1.0, 7 for index version 2.0 - BYTE BucketIndex; // Should be the same as the first byte of the hex filename. + BYTE BucketIndex; // Should be the same as the first byte of the hex filename. BYTE StorageOffsetLength; // Length, in bytes, of the StorageOffset field in the EKey entry BYTE EncodedSizeLength; // Length, in bytes, of the EncodedSize in the EKey entry BYTE EKeyLength; // Length, in bytes, of the (trimmed) EKey in the EKey entry @@ -367,7 +368,7 @@ struct TCascFile DWORD bVerifyIntegrity:1; // If true, then the data are validated more strictly when read DWORD bDownloadFileIf:1; // If true, then the data will be downloaded from the online storage if missing DWORD bCloseFileStream:1; // If true, file stream needs to be closed during CascCloseFile - DWORD bOvercomeEncrypted:1; // If true, then CascReadFile will fill the part that is encrypted (and key was not found) with zeros + DWORD bOvercomeEncrypted:1; // If true, then CascReadFile will fill the part that is encrypted (and key was not found) with zeros DWORD bFreeCKeyEntries:1; // If true, dectructor will free the array of CKey entries ULONGLONG FileCacheStart; // Starting offset of the file cached area diff --git a/src/CascDumpData.cpp b/src/CascDumpData.cpp index e29ad66b..9437ba52 100644 --- a/src/CascDumpData.cpp +++ b/src/CascDumpData.cpp @@ -530,4 +530,9 @@ void CascDumpStorage(HANDLE hStorage, const char * szDumpFile) } } +#else // _DEBUG + +// so linker won't mind this .cpp file is empty in non-DEBUG builds +void unused_symbol() { } + #endif // _DEBUG diff --git a/src/CascFiles.cpp b/src/CascFiles.cpp index 596a07d3..695d7d4a 100644 --- a/src/CascFiles.cpp +++ b/src/CascFiles.cpp @@ -13,6 +13,10 @@ #include "CascLib.h" #include "CascCommon.h" +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") // Internet functions for HTTP stream +#endif + //----------------------------------------------------------------------------- // Local defines @@ -56,6 +60,8 @@ static LPCTSTR DataDirs[] = NULL, }; +static LPCTSTR bnet_region = _T("us"); + //----------------------------------------------------------------------------- // Local functions @@ -93,12 +99,12 @@ static const char * CaptureDecimalInteger(const char * szDataPtr, const char * s while (szDataPtr < szDataEnd && szDataPtr[0] != ' ') { // Must only contain decimal digits ('0' - '9') - if (!IsCharDigit(szDataPtr[0])) + if(!IsCharDigit(szDataPtr[0])) break; // Get the next value and verify overflow AddValue = szDataPtr[0] - '0'; - if ((TotalValue + AddValue) < TotalValue) + if((TotalValue + AddValue) < TotalValue) return NULL; TotalValue = (TotalValue * 10) + AddValue; @@ -143,13 +149,13 @@ static const char * CaptureSingleHash(const char * szDataPtr, const char * szDat // Count all hash characters for (size_t i = 0; i < HashStringLength; i++) { - if (szDataPtr >= szDataEnd || isxdigit(szDataPtr[0]) == 0) + if(szDataPtr >= szDataEnd || isxdigit(szDataPtr[0]) == 0) return NULL; szDataPtr++; } // There must be a separator or end-of-string - if (szDataPtr > szDataEnd || IsWhiteSpace(szDataPtr) == false) + if(szDataPtr > szDataEnd || IsWhiteSpace(szDataPtr) == false) return NULL; // Give the values @@ -167,7 +173,7 @@ static const char * CaptureHashCount(const char * szDataPtr, const char * szData { // Check one hash szDataPtr = CaptureSingleHash(szDataPtr, szDataEnd, HashValue, MD5_HASH_SIZE); - if (szDataPtr == NULL) + if(szDataPtr == NULL) return NULL; // Skip all whitespaces @@ -227,11 +233,11 @@ static bool CheckConfigFileVariable( // Capture the variable from the line szLinePtr = CaptureSingleString(szLinePtr, szLineEnd, szVariableName, sizeof(szVariableName)); - if (szLinePtr == NULL) + if(szLinePtr == NULL) return false; // Verify whether this is the variable - if (!CascCheckWildCard(szVariableName, szVarName)) + if(!CascCheckWildCard(szVariableName, szVarName)) return false; // Skip the spaces and '=' @@ -239,7 +245,7 @@ static bool CheckConfigFileVariable( szLinePtr++; // Call the parsing function only if there is some data - if (szLinePtr >= szLineEnd) + if(szLinePtr >= szLineEnd) return false; return (PfnParseProc(hs, szVariableName, szLinePtr, szLineEnd, pvParseParam) == ERROR_SUCCESS); @@ -256,7 +262,7 @@ static DWORD LoadHashArray( // Allocate the blob buffer pBlob->cbData = (DWORD)(HashCount * MD5_HASH_SIZE); pBlob->pbData = CASC_ALLOC(pBlob->cbData); - if (pBlob->pbData != NULL) + if(pBlob->pbData != NULL) { LPBYTE pbBuffer = pBlob->pbData; @@ -264,7 +270,7 @@ static DWORD LoadHashArray( { // Capture the hash value szLinePtr = CaptureSingleHash(szLinePtr, szLineEnd, pbBuffer, MD5_HASH_SIZE); - if (szLinePtr == NULL) + if(szLinePtr == NULL) return ERROR_BAD_FORMAT; // Move buffer @@ -283,7 +289,7 @@ static DWORD LoadMultipleHashes(PQUERY_KEY pBlob, const char * szLineBegin, cons DWORD dwErrCode = ERROR_SUCCESS; // Retrieve the hash count - if (CaptureHashCount(szLineBegin, szLineEnd, &HashCount) == NULL) + if(CaptureHashCount(szLineBegin, szLineEnd, &HashCount) == NULL) return ERROR_BAD_FORMAT; // Do nothing if there is no data @@ -398,10 +404,10 @@ static DWORD LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, co DWORD VfsRootIndex = CASC_INVALID_INDEX; // Skip the "vfs-" part - if (!strncmp(szVariableName, "vfs-", 4)) + if(!strncmp(szVariableName, "vfs-", 4)) { // Then, there must be a decimal number as index - if ((szVarPtr = CaptureDecimalInteger(szVarPtr + 4, szVarEnd, &VfsRootIndex)) != NULL) + if((szVarPtr = CaptureDecimalInteger(szVarPtr + 4, szVarEnd, &VfsRootIndex)) != NULL) { // We expect the array to be initialized assert(pArray->IsInitialized()); @@ -484,9 +490,9 @@ static DWORD LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName * static int LoadQueryKey(const CASC_CSV_COLUMN & Column, QUERY_KEY & Key) { // Check the input data - if (Column.szValue == NULL) + if(Column.szValue == NULL) return ERROR_BUFFER_OVERFLOW; - if (Column.nLength != MD5_STRING_SIZE) + if(Column.nLength != MD5_STRING_SIZE) return ERROR_BAD_FORMAT; return LoadHashArray(&Key, Column.szValue, Column.szValue + Column.nLength, 1); @@ -538,6 +544,7 @@ static DWORD GetDefaultLocaleMask(TCascStorage * hs, const CASC_CSV_COLUMN & Col static DWORD ParseFile_CDNS(TCascStorage * hs, CASC_CSV & Csv) { const char * szWantedRegion = hs->szRegion; + const char * szRegion; size_t nLineCount; // Fix the region @@ -548,19 +555,23 @@ static DWORD ParseFile_CDNS(TCascStorage * hs, CASC_CSV & Csv) nLineCount = Csv.GetLineCount(); // Find the active config - for (size_t i = 0; i < nLineCount; i++) + for(size_t i = 0; i < nLineCount; i++) { - // Is it the version we are looking for? - if(!strcmp(Csv[i]["Name!STRING:0"].szValue, szWantedRegion)) + // Retrieve the region + if((szRegion = Csv[i]["Name!STRING:0"].szValue) != NULL) { - // Save the list of CDN servers - hs->szCdnServers = CascNewStrA2T(Csv[i]["Hosts!STRING:0"].szValue); + // Is it the version we are looking for? + if(!strcmp(Csv[i]["Name!STRING:0"].szValue, szWantedRegion)) + { + // Save the list of CDN servers + hs->szCdnServers = CascNewStrA2T(Csv[i]["Hosts!STRING:0"].szValue); - // Save the CDN subpath - hs->szCdnPath = CascNewStrA2T(Csv[i]["Path!STRING:0"].szValue); + // Save the CDN subpath + hs->szCdnPath = CascNewStrA2T(Csv[i]["Path!STRING:0"].szValue); - // Check and return result - return (hs->szCdnServers && hs->szCdnPath) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + // Check and return result + return (hs->szCdnServers && hs->szCdnPath) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } } } @@ -657,12 +668,12 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv) { // Extract the CDN build key dwErrCode = LoadQueryKey(Csv[nSelected]["Build Key!HEX:16"], hs->CdnBuildKey); - if (dwErrCode != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Extract the CDN config key dwErrCode = LoadQueryKey(Csv[nSelected]["CDN Key!HEX:16"], hs->CdnConfigKey); - if (dwErrCode != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Get the CDN path @@ -694,20 +705,20 @@ static DWORD ParseFile_VersionsDb(TCascStorage * hs, CASC_CSV & Csv) for (size_t i = 0; i < nLineCount; i++) { // Either take the version required or take the first one - if (hs->szRegion == NULL || !strcmp(Csv[i]["Region!STRING:0"].szValue, hs->szRegion)) + if(hs->szRegion == NULL || !strcmp(Csv[i]["Region!STRING:0"].szValue, hs->szRegion)) { // Extract the CDN build key dwErrCode = LoadQueryKey(Csv[i]["BuildConfig!HEX:16"], hs->CdnBuildKey); - if (dwErrCode != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Extract the CDN config key dwErrCode = LoadQueryKey(Csv[i]["CDNConfig!HEX:16"], hs->CdnConfigKey); - if (dwErrCode != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) return dwErrCode; const CASC_CSV_COLUMN & VerColumn = Csv[i]["VersionsName!String:0"]; - if (VerColumn.szValue && VerColumn.nLength) + if(VerColumn.szValue && VerColumn.nLength) { LoadBuildNumber(hs, NULL, VerColumn.szValue, VerColumn.szValue + VerColumn.nLength, NULL); } @@ -739,7 +750,7 @@ static DWORD ParseFile_BuildDb(TCascStorage * hs, CASC_CSV & Csv) // Extract the CDN config key dwErrCode = LoadQueryKey(Csv[CSV_ZERO][1], hs->CdnConfigKey); - if (dwErrCode != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Verify all variables @@ -798,7 +809,7 @@ static DWORD ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) // Initialize the empty VFS array dwErrCode = hs->VfsRootList.Create(0x10); - if (dwErrCode != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) return dwErrCode; // Parse all variables @@ -860,39 +871,45 @@ static DWORD ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) static DWORD CheckDataDirectory(TCascStorage * hs, LPTSTR szDirectory) { - LPTSTR szDataPath; + TCHAR szDataPath[MAX_PATH]; DWORD dwErrCode = ERROR_FILE_NOT_FOUND; // Try all known subdirectories for(size_t i = 0; DataDirs[i] != NULL; i++) { // Create the eventual data path - szDataPath = CombinePath(szDirectory, DataDirs[i]); - if(szDataPath != NULL) - { - // Does that directory exist? - if(DirectoryExists(szDataPath)) - { - hs->szRootPath = CascNewStr(szDirectory); - hs->szDataPath = szDataPath; - return ERROR_SUCCESS; - } + CombinePath(szDataPath, _countof(szDataPath), szDirectory, DataDirs[i], NULL); - // Free the data path - CASC_FREE(szDataPath); + // Does that directory exist? + if(DirectoryExists(szDataPath)) + { + hs->szRootPath = CascNewStr(szDirectory); + hs->szDataPath = CascNewStr(szDataPath); + return ERROR_SUCCESS; } } return dwErrCode; } +static DWORD LoadCsvFile(TCascStorage * hs, LPBYTE pbFileData, size_t cbFileData, PARSECSVFILE PfnParseProc, bool bHasHeader) +{ + CASC_CSV Csv(0x40, bHasHeader); + DWORD dwErrCode; + + // Load the external file to memory + if((dwErrCode = Csv.Load(pbFileData, cbFileData)) == ERROR_SUCCESS) + dwErrCode = PfnParseProc(hs, Csv); + return dwErrCode; +} + static DWORD LoadCsvFile(TCascStorage * hs, LPCTSTR szFileName, PARSECSVFILE PfnParseProc, bool bHasHeader) { CASC_CSV Csv(0x40, bHasHeader); DWORD dwErrCode; // Load the external file to memory - if ((dwErrCode = Csv.Load(szFileName)) == ERROR_SUCCESS) + if((dwErrCode = Csv.Load(szFileName)) == ERROR_SUCCESS) dwErrCode = PfnParseProc(hs, Csv); return dwErrCode; } @@ -940,7 +957,7 @@ static void CreateRemoteAndLocalPath(TCascStorage * hs, CASC_CDN_DOWNLOAD & Cdns { // The file is given by EKey. It's either a loose file, or it's stored in an archive. // We check that using the EKey map - if ((pEKeyEntry = (PCASC_EKEY_ENTRY)hs->IndexMap.FindObject(CdnsInfo.pbEKey)) != NULL) + if((pEKeyEntry = (PCASC_EKEY_ENTRY)hs->IndexMap.FindObject(CdnsInfo.pbEKey)) != NULL) { // Change the path type to "data" RemotePath.AppendString(_T("data"), true); @@ -1073,15 +1090,15 @@ static DWORD DownloadFile( // Open the remote stream pRemStream = FileStream_OpenFile(szRemoteName, BASE_PROVIDER_HTTP | STREAM_PROVIDER_FLAT | dwPortFlags); - if (pRemStream != NULL) + if(pRemStream != NULL) { // Will we download the entire file or just a part of it? - if (PtrByteOffset == NULL) + if(PtrByteOffset == NULL) { 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) && 0 < FileSize && FileSize < 0x40000000) { // Cut the file size down to 32 bits cbReadSize = (DWORD)FileSize; @@ -1089,15 +1106,15 @@ static DWORD DownloadFile( } // Shall we read something? - if ((cbReadSize != 0) && (pbFileData = CASC_ALLOC(cbReadSize)) != NULL) + if((cbReadSize != 0) && (pbFileData = CASC_ALLOC(cbReadSize)) != NULL) { // Read all required data from the remote file - if (FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize)) + if(FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize)) { pLocStream = FileStream_CreateFile(szLocalName, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT); - if (pLocStream != NULL) + if(pLocStream != NULL) { - if (FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize)) + if(FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize)) dwErrCode = ERROR_SUCCESS; FileStream_Close(pLocStream); @@ -1119,11 +1136,63 @@ static DWORD DownloadFile( return dwErrCode; } +static DWORD RibbitDownloadFile(LPCTSTR szProduct, LPCTSTR szFileName, QUERY_KEY & FileData) +{ + TFileStream * pStream; + ULONGLONG FileSize = 0; + TCHAR szRemoteUrl[256]; + DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE; + + // 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); + + // Open the file stream + if((pStream = FileStream_OpenFile(szRemoteUrl, 0)) != NULL) + { + if(FileStream_GetSize(pStream, &FileSize) && FileSize <= 0x04000000) + { + // Fill-in the file pointer and size + FileData.pbData = CASC_ALLOC((size_t)FileSize); + if(FileData.pbData != NULL) + { + if(FileStream_Read(pStream, NULL, FileData.pbData, (DWORD)FileSize)) + { + FileData.cbData = (size_t)FileSize; + dwErrCode = ERROR_SUCCESS; + } + else + { + dwErrCode = GetCascError(); + CASC_FREE(FileData.pbData); + FileData.pbData = NULL; + } + } + else + { + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + } + } + else + { + dwErrCode = GetCascError(); + } + + // Close the remote stream + FileStream_Close(pStream); + } + else + { + dwErrCode = GetCascError(); + } + + return dwErrCode; +} + static DWORD DownloadFileFromCDN2(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo) { CASC_PATH RemotePath(URL_SEP_CHAR); CASC_PATH LocalPath(PATH_SEP_CHAR); - DWORD dwPortFlags = (CdnsInfo.Flags & CASC_CDN_FLAG_PORT1119) ? STREAM_FLAG_USE_PORT_1119 : 0; DWORD dwErrCode; // Assemble both the remote and local path @@ -1139,7 +1208,7 @@ static DWORD DownloadFileFromCDN2(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInf return dwErrCode; // Attempt to download the file - dwErrCode = DownloadFile(RemotePath, LocalPath, NULL, 0, dwPortFlags); + dwErrCode = DownloadFile(RemotePath, LocalPath, NULL, 0, 0); if(dwErrCode != ERROR_SUCCESS) return dwErrCode; } @@ -1213,9 +1282,9 @@ static DWORD FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARS // Load and verify the external listfile pvListFile = ListFile_OpenExternal(szLocalPath); - if (pvListFile != NULL) + if(pvListFile != NULL) { - if (ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) + if(ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) { dwErrCode = PfnParseProc(hs, pvListFile); } @@ -1283,34 +1352,30 @@ DWORD GetFileSpanInfo(PCASC_CKEY_ENTRY pCKeyEntry, PULONGLONG PtrContentSize, PU DWORD CheckGameDirectory(TCascStorage * hs, LPTSTR szDirectory) { TFileStream * pStream; - LPTSTR szBuildFile; + TCHAR szBuildFile[MAX_PATH]; DWORD dwErrCode = ERROR_FILE_NOT_FOUND; // Try to find any of the root files used in the history for (size_t i = 0; BuildTypes[i].szFileName != NULL; i++) { // Create the full name of the .agent.db file - szBuildFile = CombinePath(szDirectory, BuildTypes[i].szFileName); - if (szBuildFile != NULL) + CombinePath(szBuildFile, _countof(szBuildFile), szDirectory, BuildTypes[i].szFileName, NULL); + + // Attempt to open the file + pStream = FileStream_OpenFile(szBuildFile, STREAM_FLAG_READ_ONLY); + if(pStream != NULL) { - // Attempt to open the file - pStream = FileStream_OpenFile(szBuildFile, STREAM_FLAG_READ_ONLY); - if (pStream != NULL) - { - // Free the stream - FileStream_Close(pStream); + // Free the stream + FileStream_Close(pStream); - // Check for the data directory - dwErrCode = CheckDataDirectory(hs, szDirectory); - if (dwErrCode == ERROR_SUCCESS) - { - hs->szBuildFile = szBuildFile; - hs->BuildFileType = BuildTypes[i].BuildFileType; - return ERROR_SUCCESS; - } + // Check for the data directory + dwErrCode = CheckDataDirectory(hs, szDirectory); + if(dwErrCode == ERROR_SUCCESS) + { + hs->szBuildFile = CascNewStr(szBuildFile); + hs->BuildFileType = BuildTypes[i].BuildFileType; + return ERROR_SUCCESS; } - - CASC_FREE(szBuildFile); } } @@ -1323,35 +1388,6 @@ DWORD LoadBuildInfo(TCascStorage * hs) DWORD dwErrCode; bool bHasHeader = true; - // If the storage is online storage, we need to download "versions" - if(hs->dwFeatures & CASC_FEATURE_ONLINE) - { - CASC_CDN_DOWNLOAD CdnsInfo = {0}; - TCHAR szLocalFile[MAX_PATH]; - - // Inform the user about loading the build.info/build.db/versions - if(InvokeProgressCallback(hs, "Downloading the \"versions\" file", NULL, 0, 0)) - return ERROR_CANCELLED; - - // Prepare the download structure for "us.patch.battle.net/%CODENAME%/versions" file - CdnsInfo.szCdnsHost = _T("us.patch.battle.net"); - CdnsInfo.szCdnsPath = hs->szCodeName; - CdnsInfo.szPathType = _T(""); - CdnsInfo.szFileName = _T("versions"); - CdnsInfo.szLocalPath = szLocalFile; - CdnsInfo.ccLocalPath = _countof(szLocalFile); - CdnsInfo.Flags = CASC_CDN_FLAG_PORT1119 | CASC_CDN_FORCE_DOWNLOAD; - - // Attempt to download the "versions" file - dwErrCode = DownloadFileFromCDN(hs, CdnsInfo); - if(dwErrCode != ERROR_SUCCESS) - return dwErrCode; - - // Retrieve the name of the "versions" file - if((hs->szBuildFile = CascNewStr(szLocalFile)) == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - } - // We support ".build.info", ".build.db" or "versions" switch (hs->BuildFileType) { @@ -1372,14 +1408,35 @@ DWORD LoadBuildInfo(TCascStorage * hs) return ERROR_NOT_SUPPORTED; } - assert(hs->szBuildFile != NULL); - return LoadCsvFile(hs, hs->szBuildFile, PfnParseProc, bHasHeader); + // If the storage is online storage, we need to download "versions" + if(hs->dwFeatures & CASC_FEATURE_ONLINE) + { + QUERY_KEY FileData; + + // 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); + if(dwErrCode == ERROR_SUCCESS) + { + // Parse the downloaded file + dwErrCode = LoadCsvFile(hs, FileData.pbData, FileData.cbData, ParseFile_VersionsDb, true); + } + } + else + { + assert(hs->szBuildFile != NULL); + dwErrCode = LoadCsvFile(hs, hs->szBuildFile, PfnParseProc, bHasHeader); + } + + return dwErrCode; } DWORD LoadCdnsFile(TCascStorage * hs) { - CASC_CDN_DOWNLOAD CdnsInfo = {0}; - TCHAR szLocalPath[MAX_PATH]; + QUERY_KEY FileData; DWORD dwErrCode = ERROR_SUCCESS; // Sanity checks @@ -1389,19 +1446,12 @@ DWORD LoadCdnsFile(TCascStorage * hs) if(InvokeProgressCallback(hs, "Downloading the \"cdns\" file", NULL, 0, 0)) return ERROR_CANCELLED; - // Prepare the download structure - CdnsInfo.szCdnsHost = _T("us.patch.battle.net"); - CdnsInfo.szCdnsPath = hs->szCodeName; - CdnsInfo.szPathType = _T(""); - CdnsInfo.szFileName = _T("cdns"); - CdnsInfo.szLocalPath = szLocalPath; - CdnsInfo.ccLocalPath = _countof(szLocalPath); - CdnsInfo.Flags = CASC_CDN_FLAG_PORT1119 | CASC_CDN_FORCE_DOWNLOAD; - - // Download file and parse it - if((dwErrCode = DownloadFileFromCDN(hs, CdnsInfo)) == ERROR_SUCCESS) + // Download the file using Ribbit protocol + dwErrCode = RibbitDownloadFile(hs->szCodeName, _T("cdns"), FileData); + if(dwErrCode == ERROR_SUCCESS) { - dwErrCode = LoadCsvFile(hs, szLocalPath, ParseFile_CDNS, true); + // Parse the downloaded file + dwErrCode = LoadCsvFile(hs, FileData.pbData, FileData.cbData, ParseFile_CDNS, true); } return dwErrCode; @@ -1524,7 +1574,7 @@ LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData) FileStream_GetSize(pStream, &FileSize); cbFileData = (DWORD)FileSize; - // Do not load zero files or too larget files + // Do not load zero files or too large files if(0 < FileSize && FileSize <= 0x2000000) { // Allocate file data buffer. Make it 1 byte longer @@ -1532,7 +1582,12 @@ LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData) pbFileData = CASC_ALLOC(cbFileData + 1); if(pbFileData != NULL) { - if(!FileStream_Read(pStream, NULL, pbFileData, cbFileData)) + if(FileStream_Read(pStream, NULL, pbFileData, cbFileData)) + { + // Terminate the data with zero so various string-based functions can process it + pbFileData[cbFileData] = 0; + } + else { CASC_FREE(pbFileData); cbFileData = 0; diff --git a/src/CascIndexFiles.cpp b/src/CascIndexFiles.cpp index bc50f9f2..98416c2b 100644 --- a/src/CascIndexFiles.cpp +++ b/src/CascIndexFiles.cpp @@ -118,6 +118,7 @@ static bool IndexDirectory_OnFileFound( static LPTSTR CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD IndexVersion) { + TCHAR szFullName[MAX_PATH]; TCHAR szPlainName[0x40]; // Sanity checks @@ -127,7 +128,10 @@ static LPTSTR CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD Ind // Create the full path CascStrPrintf(szPlainName, _countof(szPlainName), hs->szIndexFormat, IndexValue, IndexVersion); - return CombinePath(hs->szIndexPath, szPlainName); + CombinePath(szFullName, _countof(szFullName), hs->szIndexPath, szPlainName, NULL); + + // Return allocated path + return CascNewStr(szFullName); } static void SaveFileOffsetBitsAndEKeyLength(TCascStorage * hs, BYTE FileOffsetBits, BYTE EKeyLength) diff --git a/src/CascLib.h b/src/CascLib.h index 75f8729e..815ca7e3 100644 --- a/src/CascLib.h +++ b/src/CascLib.h @@ -74,8 +74,8 @@ extern "C" { //----------------------------------------------------------------------------- // Defines -#define CASCLIB_VERSION 0x0200 // CascLib version - integral (1.50) -#define CASCLIB_VERSION_STRING "2.0" // CascLib version - string +#define CASCLIB_VERSION 0x0210 // CascLib version - integral (2.1) +#define CASCLIB_VERSION_STRING "2.1" // CascLib version - string // Values for CascOpenFile #define CASC_OPEN_BY_NAME 0x00000000 // Open the file by name. This is the default value diff --git a/src/CascOpenStorage.cpp b/src/CascOpenStorage.cpp index 00b3797e..584b79a3 100644 --- a/src/CascOpenStorage.cpp +++ b/src/CascOpenStorage.cpp @@ -135,6 +135,11 @@ TCascStorage * TCascStorage::Release() // Need this to be atomic to make multi-threaded file opens work if(CascInterlockedDecrement(&dwRefCount) == 0) { + // Release all references in the socket cache + if(dwFeatures & CASC_FEATURE_ONLINE) + sockets_set_caching(false); + + // Delete the object and return NULL delete this; return NULL; } @@ -162,16 +167,16 @@ void * ProbeOutputBuffer(void * pvBuffer, size_t cbLength, size_t cbMinLength, s static LPTSTR CheckForIndexDirectory(TCascStorage * hs, LPCTSTR szSubDir) { - LPTSTR szIndexPath; + TCHAR szIndexPath[MAX_PATH]; // Combine the index path - szIndexPath = CombinePath(hs->szDataPath, szSubDir); - if (!DirectoryExists(szIndexPath)) - { - CASC_FREE(szIndexPath); - } + CombinePath(szIndexPath, _countof(szIndexPath), hs->szDataPath, szSubDir, NULL); - return szIndexPath; + // Check whether the path exists + if(!DirectoryExists(szIndexPath)) + return NULL; + + return CascNewStr(szIndexPath); } // Inserts an entry from the text build file @@ -1168,9 +1173,14 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) if(ExtractVersionedArgument(pArgs, FIELD_OFFSET(CASC_OPEN_STORAGE_ARGS, szBuildKey), &szBuildKey) && szBuildKey != NULL) hs->szBuildKey = CascNewStrT2A(szBuildKey); - // For online storages, we need to load CDN servers - if ((dwErrCode == ERROR_SUCCESS) && (hs->dwFeatures & CASC_FEATURE_ONLINE)) + // Special handling to online storages + if(hs->dwFeatures & CASC_FEATURE_ONLINE) { + // Enable caching of the sockets. This will add references + // to all existing and all future sockets + sockets_set_caching(true); + + // For online storages, we need to load CDN servers dwErrCode = LoadCdnsFile(hs); } diff --git a/src/CascPort.h b/src/CascPort.h index 030d1271..da11fccd 100644 --- a/src/CascPort.h +++ b/src/CascPort.h @@ -13,8 +13,8 @@ #define __CASCPORT_H__ #ifndef __cplusplus - #define bool char - #define true 1 + #define bool char + #define true 1 #define false 0 #endif @@ -43,9 +43,9 @@ #include #include #include - #include + #include #include - #include + #define PLATFORM_LITTLE_ENDIAN #pragma intrinsic(memset, memcmp, memcpy) // Make these functions intrinsic (inline) @@ -56,6 +56,7 @@ #define PLATFORM_WINDOWS #define PLATFORM_DEFINED // The platform is known now + #endif #ifndef FIELD_OFFSET @@ -70,6 +71,7 @@ // Macintosh #include #include + #include #include #include #include @@ -85,6 +87,7 @@ #include #include #include + #include // Support for PowerPC on Max OS X #if (__ppc__ == 1) || (__POWERPC__ == 1) || (_ARCH_PPC == 1) @@ -104,6 +107,8 @@ #define PATH_SEP_CHAR '/' #define PATH_SEP_STRING "/" + typedef int SOCKET; + #define PLATFORM_MAC #define PLATFORM_DEFINED // The platform is known now @@ -115,6 +120,7 @@ #if !defined(PLATFORM_DEFINED) #include #include + #include #include #include #include @@ -130,11 +136,14 @@ #include #include #include + #include #define URL_SEP_CHAR '/' #define PATH_SEP_CHAR '/' #define PATH_SEP_STRING "/" + typedef int SOCKET; + #define PLATFORM_LITTLE_ENDIAN #define PLATFORM_LINUX #define PLATFORM_DEFINED @@ -202,6 +211,8 @@ #define _tcsicmp strcasecmp #define _tcsnicmp strncasecmp + #define closesocket close + #endif // !PLATFORM_WINDOWS // 64-bit calls are supplied by "normal" calls on Mac @@ -235,6 +246,7 @@ #define ERROR_FILE_ENCRYPTED 1005 // Returned by encrypted stream when can't find file key #define ERROR_FILE_TOO_LARGE 1006 // No such error code under Linux #define ERROR_ARITHMETIC_OVERFLOW 1007 // The string value is too large to fit in the given type + #define ERROR_NETWORK_NOT_AVAILABLE 1008 // Cannot connect to the internet #endif #ifndef ERROR_FILE_INCOMPLETE diff --git a/src/CascReadFile.cpp b/src/CascReadFile.cpp index 6078c32f..e71387b0 100644 --- a/src/CascReadFile.cpp +++ b/src/CascReadFile.cpp @@ -48,7 +48,7 @@ static DWORD OpenDataStream(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKE { // Prepare the name of the data file CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), dwArchiveIndex); - CombinePath(szDataFile, _countof(szDataFile), PATH_SEP_CHAR, hs->szIndexPath, szPlainName, NULL); + CombinePath(szDataFile, _countof(szDataFile), hs->szIndexPath, szPlainName, NULL); // Open the data stream with read+write sharing to prevent Battle.net agent // detecting a corruption and redownloading the entire package diff --git a/src/DllMain.rc b/src/DllMain.rc index 4241c268..befa7c43 100644 --- a/src/DllMain.rc +++ b/src/DllMain.rc @@ -27,8 +27,8 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,50,0,204 - PRODUCTVERSION 1,50,0,204 + FILEVERSION 1,50,0,205 + PRODUCTVERSION 1,50,0,205 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -45,12 +45,12 @@ BEGIN BEGIN VALUE "Comments", "http://www.zezula.net/casc.html" VALUE "FileDescription", "CascLib library for reading Blizzard CASC storages" - VALUE "FileVersion", "1, 50, 0, 204\0" + VALUE "FileVersion", "1, 50, 0, 205\0" VALUE "InternalName", "CascLib" VALUE "LegalCopyright", "Copyright (c) 2014 - 2019 Ladislav Zezula" VALUE "OriginalFilename", "CascLib.dll" VALUE "ProductName", "CascLib" - VALUE "ProductVersion", "1, 50, 0, 204\0" + VALUE "ProductVersion", "1, 50, 0, 205\0" END END BLOCK "VarFileInfo" diff --git a/src/common/Common.cpp b/src/common/Common.cpp index 7bec2b14..953a4f0b 100644 --- a/src/common/Common.cpp +++ b/src/common/Common.cpp @@ -454,9 +454,9 @@ bool CutLastPathPart(LPTSTR szWorkPath) return true; } -size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list argList) +size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, va_list argList) { - CASC_PATH Path(chSeparator); + CASC_PATH Path(PATH_SEP_CHAR); LPCTSTR szFragment; bool bWithSeparator = false; @@ -470,28 +470,18 @@ size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list return Path.Copy(szBuffer, nMaxChars); } -size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, ...) +size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, ...) { va_list argList; size_t nLength; - va_start(argList, chSeparator); - nLength = CombinePath(szBuffer, nMaxChars, chSeparator, argList); + va_start(argList, nMaxChars); + nLength = CombinePath(szBuffer, nMaxChars, argList); va_end(argList); return nLength; } -LPTSTR CombinePath(LPCTSTR szDirectory, LPCTSTR szSubDir) -{ - CASC_PATH Path(PATH_SEP_CHAR); - - // Merge the path - Path.AppendString(szDirectory, false); - Path.AppendString(szSubDir, true); - return Path.New(); -} - size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars) { char * szNormNameEnd = szNormName + cchMaxChars; diff --git a/src/common/Common.h b/src/common/Common.h index 46b23adb..63996e55 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -328,9 +328,8 @@ wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve = 0); LPSTR CascNewStrT2A(LPCTSTR szString, size_t nCharsToReserve = 0); LPTSTR CascNewStrA2T(LPCSTR szString, size_t nCharsToReserve = 0); -size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list argList); -size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, ...); -LPTSTR CombinePath(LPCTSTR szPath, LPCTSTR szSubDir); +size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, va_list argList); +size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, ...); LPTSTR GetLastPathPart(LPTSTR szWorkPath); bool CutLastPathPart(LPTSTR szWorkPath); @@ -438,7 +437,7 @@ xchar * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, xchar * szBuffer) } //----------------------------------------------------------------------------- -// Structure query key +// Structures for data blobs struct QUERY_KEY { @@ -454,6 +453,16 @@ struct QUERY_KEY cbData = 0; } + DWORD SetData(const void * pv, size_t cb) + { + if((pbData = CASC_ALLOC(cb)) == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + memcpy(pbData, pv, cb); + cbData = cb; + return ERROR_SUCCESS; + } + LPBYTE pbData; size_t cbData; }; diff --git a/src/common/Csv.cpp b/src/common/Csv.cpp index 40a50ec7..4afaae10 100644 --- a/src/common/Csv.cpp +++ b/src/common/Csv.cpp @@ -21,7 +21,7 @@ static const CASC_CSV_LINE NullLine; //----------------------------------------------------------------------------- // Local functions -static char * NextLine(char * szLine) +static char * NextLine_Default(void * /* pvUserData */, char * szLine) { // Find the end of the line while(szLine[0] != 0 && szLine[0] != 0x0A && szLine[0] != 0x0D) @@ -35,7 +35,7 @@ static char * NextLine(char * szLine) return (szLine[0] != 0) ? szLine : NULL; } -static char * NextColumn(char * szColumn) +static char * NextColumn_Default(void * /* pvUserData */, char * szColumn) { // Find the end of the column while(szColumn[0] != 0 && szColumn[0] != '|') @@ -86,6 +86,7 @@ CASC_CSV_LINE::~CASC_CSV_LINE() bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine) { + CASC_CSV_NEXTPROC PfnNextColumn = pParent->GetNextColumnProc(); char * szCsvColumn; size_t nHdrColumns = 0; size_t nColumns = 0; @@ -99,7 +100,7 @@ bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine) { // Get current line and the next one szCsvColumn = szCsvLine; - szCsvLine = NextColumn(szCsvLine); + szCsvLine = PfnNextColumn(pParent->GetUserData(), szCsvLine); // Save the current line Columns[nColumns].szValue = szCsvColumn; @@ -154,12 +155,17 @@ CASC_CSV::CASC_CSV(size_t nLinesMax, bool bHasHeader) { // Initialize the class variables memset(HashTable, 0xFF, sizeof(HashTable)); + m_pvUserData = NULL; m_szCsvFile = NULL; m_szCsvPtr = NULL; m_nCsvFile = 0; m_nLines = 0; m_bHasHeader = bHasHeader; + // Initialize the "NextLine" function that will serve for finding next line in the text + PfnNextLine = NextLine_Default; + PfnNextColumn = NextColumn_Default; + // Nonzero number of lines means a finite CSV handler. The CSV will be loaded at once. // Zero means that the user needs to call LoadNextLine() and then the line data if(nLinesMax != 0) @@ -185,6 +191,21 @@ CASC_CSV::~CASC_CSV() m_szCsvFile = NULL; } +DWORD CASC_CSV::SetNextLineProc(CASC_CSV_NEXTPROC PfnNextLineProc, CASC_CSV_NEXTPROC PfnNextColProc, void * pvUserData) +{ + // Set the procedure for next line parsing + if(PfnNextLineProc != NULL) + PfnNextLine = PfnNextLineProc; + + // Set procedure for next columne parsing + if(PfnNextColProc != NULL) + PfnNextColumn = PfnNextColProc; + + // Save the user data + m_pvUserData = pvUserData; + return ERROR_SUCCESS; +} + DWORD CASC_CSV::Load(LPCTSTR szFileName) { DWORD cbFileData = 0; @@ -193,8 +214,7 @@ DWORD CASC_CSV::Load(LPCTSTR szFileName) m_szCsvFile = (char *)LoadFileToMemory(szFileName, &cbFileData); if (m_szCsvFile != NULL) { - // There is one extra byte reserved by LoadFileToMemory, so we can put zero there - m_szCsvFile[cbFileData] = 0; + // Assign the data to the CSV object m_szCsvPtr = m_szCsvFile; m_nCsvFile = cbFileData; @@ -282,7 +302,7 @@ bool CASC_CSV::LoadNextLine(CASC_CSV_LINE & Line) char * szCsvLine = m_szCsvPtr; // Get the next line - m_szCsvPtr = NextLine(m_szCsvPtr); + m_szCsvPtr = PfnNextLine(m_pvUserData, m_szCsvPtr); // Initialize the line if (Line.SetLine(this, szCsvLine)) diff --git a/src/common/Csv.h b/src/common/Csv.h index 2fcee847..8df99c63 100644 --- a/src/common/Csv.h +++ b/src/common/Csv.h @@ -19,6 +19,13 @@ #define CSV_MAX_COLUMNS 0x20 #define CSV_HASH_TABLE_SIZE 0x80 +//----------------------------------------------------------------------------- +// Interface for finding of next text element (line, column) + +// The function must find the next (line|column), put zero there and return begin +// of the next (line|column). In case there is no next (line|column), the function returns NULL +typedef char * (*CASC_CSV_NEXTPROC)(void * pvUserData, char * szLine); + //----------------------------------------------------------------------------- // Class for CSV line @@ -69,6 +76,12 @@ class CASC_CSV CASC_CSV(size_t nLinesMax, bool bHasHeader); ~CASC_CSV(); + DWORD SetNextLineProc(CASC_CSV_NEXTPROC PfnNextLineProc, CASC_CSV_NEXTPROC PfnNextColProc = NULL, void * pvUserData = NULL); + CASC_CSV_NEXTPROC GetNextColumnProc() + { + return PfnNextColumn; + } + DWORD Load(LPBYTE pbData, size_t cbData); DWORD Load(LPCTSTR szFileName); bool LoadNextLine(); @@ -79,6 +92,11 @@ class CASC_CSV size_t GetHeaderColumns() const; size_t GetColumnIndex(const char * szColumnName) const; + void * GetUserData() const + { + return m_pvUserData; + } + size_t GetLineCount() const { return m_nLines; @@ -90,9 +108,13 @@ class CASC_CSV bool LoadNextLine(CASC_CSV_LINE & Line); bool ParseCsvData(); + CASC_CSV_NEXTPROC PfnNextLine; + CASC_CSV_NEXTPROC PfnNextColumn; + CASC_CSV_LINE * m_pLines; CASC_CSV_LINE Header; BYTE HashTable[CSV_HASH_TABLE_SIZE]; + void * m_pvUserData; char * m_szCsvFile; char * m_szCsvPtr; size_t m_nCsvFile; diff --git a/src/common/Directory.cpp b/src/common/Directory.cpp index 57c7d552..a306e54a 100644 --- a/src/common/Directory.cpp +++ b/src/common/Directory.cpp @@ -60,13 +60,11 @@ int ScanIndexDirectory( #ifdef PLATFORM_WINDOWS WIN32_FIND_DATA wf; - LPTSTR szSearchMask; HANDLE hFind; + TCHAR szSearchMask[MAX_PATH]; // Prepare the search mask - szSearchMask = CombinePath(szIndexPath, _T("*")); - if(szSearchMask == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + CombinePath(szSearchMask, _countof(szSearchMask), szIndexPath, _T("*"), NULL); // Prepare directory search hFind = FindFirstFile(szSearchMask, &wf); @@ -87,8 +85,6 @@ int ScanIndexDirectory( FindClose(hFind); } - CASC_FREE(szSearchMask); - #else // PLATFORM_WINDOWS struct dirent * dir_entry; diff --git a/src/common/FileStream.cpp b/src/common/FileStream.cpp index cb0090f8..e84c7cc3 100644 --- a/src/common/FileStream.cpp +++ b/src/common/FileStream.cpp @@ -18,13 +18,17 @@ #include "../CascCommon.h" #ifdef _MSC_VER -#pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream #pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning) #endif //----------------------------------------------------------------------------- // Local functions - platform-specific functions +static ULONGLONG GetByteOffset(ULONGLONG * ByteOffset1, ULONGLONG ByteOffset2) +{ + return (ByteOffset1 != NULL) ? ByteOffset1[0] : ByteOffset2; +} + static DWORD StringToInt(const char * szString) { DWORD dwValue = 0; @@ -165,7 +169,7 @@ static bool BaseFile_Read( // Synchronize the access to the TFileStream structure CascLock(pStream->Lock); { - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.File.FilePos); #ifdef PLATFORM_WINDOWS { @@ -265,7 +269,7 @@ static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const // Synchronize the access to the TFileStream structure CascLock(pStream->Lock); { - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos; + ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.File.FilePos); #ifdef PLATFORM_WINDOWS { @@ -557,7 +561,7 @@ static bool BaseMap_Read( void * pvBuffer, // Pointer to data to be read DWORD dwBytesToRead) // Number of bytes to read from the file { - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos; + ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.Map.FilePos); // Do we have to read anything at all? if(dwBytesToRead != 0) @@ -607,150 +611,100 @@ static void BaseMap_Init(TFileStream * pStream) //----------------------------------------------------------------------------- // Local functions - base HTTP file support -static LPCTSTR BaseHttp_ExtractServerName(LPCTSTR szFileName, LPTSTR szServerName) +static DWORD BaseHttp_ParseURL(TFileStream * pStream, LPCTSTR szFileName) { - // Check for HTTP - if(!_tcsnicmp(szFileName, _T("http://"), 7)) - szFileName += 7; + LPCTSTR szFilePtr = szFileName; + char * hostName; + char * fileName; - // Cut off the server name - if(szServerName != NULL) - { - while(szFileName[0] != 0 && szFileName[0] != _T('/')) - *szServerName++ = *szFileName++; - *szServerName = 0; - } - else + // Find the end od the host name + if((szFilePtr = _tcschr(szFileName, '/')) == NULL) + return ERROR_INVALID_PARAMETER; + + // Allocate and copy the host name + if((hostName = CASC_ALLOC(szFilePtr - szFileName + 1)) != NULL) { - while(szFileName[0] != 0 && szFileName[0] != _T('/')) - szFileName++; + CascStrCopy(hostName, 256, szFileName, (szFilePtr - szFileName)); + + // Allocate and copy the resource name + if((fileName = CascNewStrT2A(szFilePtr)) != NULL) + { + pStream->Base.Socket.hostName = hostName; + pStream->Base.Socket.fileName = fileName; + return ERROR_SUCCESS; + } + + CASC_FREE(hostName); } - // Return the remainder - return szFileName; + return ERROR_NOT_ENOUGH_MEMORY; } -static bool BaseHttp_Open(TFileStream * pStream, LPCTSTR szFileName, DWORD dwStreamFlags) +static bool BaseHttp_Download(TFileStream * pStream) { -#ifdef PLATFORM_WINDOWS + CASC_MIME Mime; + const char * request_mask = "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: Keep-Alive\r\n\r\n"; + char * server_response; + char * fileName = pStream->Base.Socket.fileName; + char request[0x100]; + size_t response_length = 0; + size_t request_length = 0; + DWORD dwErrCode; - INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT; - HINTERNET hRequest; - DWORD dwTemp = 0; - bool bFileAvailable = false; - DWORD dwErrCode = ERROR_SUCCESS; - - // Check alternate ports - if(dwStreamFlags & STREAM_FLAG_USE_PORT_1119) + // If we already have the data, it's success + if(pStream->Base.Socket.fileData == NULL) { - ServerPort = 1119; - } + // 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) + { + if(fileName[0] == '/') + fileName++; + request_mask = "%s\r\n"; + } - // Don't download if we are not connected to the internet - if(!InternetGetConnectedState(&dwTemp, 0)) - dwErrCode = GetCascError(); + // Send the request and receive decoded response + request_length = CascStrPrintf(request, _countof(request), request_mask, fileName, pStream->Base.Socket.hostName); + 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) + { + // Move the data from MIME to HTTP stream + pStream->Base.Socket.fileData = Mime.GiveAway(&pStream->Base.Socket.fileDataLength); + } - // Initiate the connection to the internet - if(dwErrCode == ERROR_SUCCESS) - { - pStream->Base.Http.hInternet = InternetOpen(_T("agent/2.17.2.6700"), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0); - if(pStream->Base.Http.hInternet == NULL) - dwErrCode = GetCascError(); + CASC_FREE(server_response); + } } - // Connect to the server - if(dwErrCode == ERROR_SUCCESS) - { - TCHAR szServerName[MAX_PATH]; - DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; + // If we have data loaded, return true + return (pStream->Base.Socket.fileData != NULL); +} - // Initiate connection with the server - szFileName = BaseHttp_ExtractServerName(szFileName, szServerName); - pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, - szServerName, - ServerPort, - NULL, - NULL, - INTERNET_SERVICE_HTTP, - dwFlags, - 0); - if(pStream->Base.Http.hConnect == NULL) - dwErrCode = GetCascError(); - } +static bool BaseHttp_Open(TFileStream * pStream, LPCTSTR szFileName, DWORD dwStreamFlags) +{ + DWORD dwErrCode; - // Now try to query the file size - if(dwErrCode == ERROR_SUCCESS) + // Extract the server part + if((dwErrCode = BaseHttp_ParseURL(pStream, szFileName)) == ERROR_SUCCESS) { - // Open HTTP request to the file - hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); - if(hRequest != NULL) + // 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) { - if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) - { - ULONGLONG FileTime = 0; - LPTSTR szEndPtr; - TCHAR szStatusCode[0x10]; - DWORD dwStatusCode = 404; - DWORD dwFileSize = 0; - DWORD dwDataSize = sizeof(szStatusCode); - DWORD dwIndex = 0; - - // Check whether the file exists - if (HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwDataSize, &dwIndex)) - dwStatusCode = _tcstoul(szStatusCode, &szEndPtr, 10); - - // Compare the status code - if (dwStatusCode == 200) - { - // Check if the archive has Last Modified field - dwDataSize = sizeof(ULONGLONG); - if (HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) - pStream->Base.Http.FileTime = FileTime; - - // Verify if the server supports random access - dwDataSize = sizeof(DWORD); - if (HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) - { - if (dwFileSize != 0) - { - pStream->Base.Http.FileSize = dwFileSize; - pStream->Base.Http.FilePos = 0; - bFileAvailable = true; - } - } - } - else - { - dwErrCode = ERROR_FILE_NOT_FOUND; - } - } - InternetCloseHandle(hRequest); + pStream->Base.Socket.pSocket = pSocket; + return true; } } - // If the file is not there and is not available for random access, - // report error - if(bFileAvailable == false) - { - pStream->BaseClose(pStream); - SetCascError(dwErrCode); - return false; - } - - return true; - -#else - - // Not supported - SetCascError(ERROR_NOT_SUPPORTED); - pStream = pStream; + // Failure: set the last error and return false + SetCascError(dwErrCode); return false; - -#endif } static bool BaseHttp_Read( @@ -759,90 +713,94 @@ static bool BaseHttp_Read( void * pvBuffer, // Pointer to data to be read DWORD dwBytesToRead) // Number of bytes to read from the file { -#ifdef PLATFORM_WINDOWS - ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos; - DWORD dwTotalBytesRead = 0; + ULONGLONG ByteOffset = GetByteOffset(pByteOffset, pStream->Base.Socket.fileDataPos); + bool bCanReadTheWholeRange = true; - // Do we have to read anything at all? - if(dwBytesToRead != 0) + // Synchronize the access to the TFileStream structure + CascLock(pStream->Lock); { - HINTERNET hRequest; - LPCTSTR szFileName; - LPBYTE pbBuffer = (LPBYTE)pvBuffer; - TCHAR szRangeRequest[0x80]; - DWORD dwStartOffset = (DWORD)ByteOffset; - DWORD dwEndOffset = dwStartOffset + dwBytesToRead; - - // Open HTTP request to the file - szFileName = BaseHttp_ExtractServerName(pStream->szFileName, NULL); - hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0); - if(hRequest != NULL) + // Do we have to read anything at all? + if(dwBytesToRead != 0) { - // Add range request to the HTTP headers - // http://www.clevercomponents.com/articles/article015/resuming.asp - CascStrPrintf(szRangeRequest, _countof(szRangeRequest), _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset); - HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW); + // Make sure that we have the file downloaded + if(!BaseHttp_Download(pStream)) + { + CascUnlock(pStream->Lock); + return false; + } - // Send the request to the server - if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) + // Are we trying to read more than available? + if(ByteOffset <= pStream->Base.Socket.fileDataLength) { - while(dwTotalBytesRead < dwBytesToRead) + if((ByteOffset + dwBytesToRead) > pStream->Base.Socket.fileDataLength) { - DWORD dwBlockBytesToRead = dwBytesToRead - dwTotalBytesRead; - DWORD dwBlockBytesRead = 0; - - // Read the block from the file - if(dwBlockBytesToRead > 0x200) - dwBlockBytesToRead = 0x200; - InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead); - - // Check for end - if(dwBlockBytesRead == 0) - break; - - // Move buffers - dwTotalBytesRead += dwBlockBytesRead; - pbBuffer += dwBlockBytesRead; + bCanReadTheWholeRange = false; + dwBytesToRead = (DWORD)(pStream->Base.Socket.fileDataLength - ByteOffset); } } - InternetCloseHandle(hRequest); + else + { + bCanReadTheWholeRange = false; + dwBytesToRead = 0; + } + + // Copy the data + if(dwBytesToRead != 0) + { + memcpy(pvBuffer, pStream->Base.Socket.fileData + ByteOffset, dwBytesToRead); + } } - } - // Increment the current file position by number of bytes read - pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead; + // Increment the current file position by number of bytes read + pStream->Base.Socket.fileDataPos = (size_t)(ByteOffset + dwBytesToRead); + } + CascUnlock(pStream->Lock); // If the number of bytes read doesn't match the required amount, return false - if(dwTotalBytesRead != dwBytesToRead) + if(bCanReadTheWholeRange == false) SetCascError(ERROR_HANDLE_EOF); - return (dwTotalBytesRead == dwBytesToRead); + return bCanReadTheWholeRange; +} + +// Gives the current file size +static bool BaseHttp_GetSize(TFileStream * pStream, ULONGLONG * pFileSize) +{ + bool bResult; -#else + // Synchronize the access to the TFileStream structure + CascLock(pStream->Lock); + { + // Make sure that we have the file data + bResult = BaseHttp_Download(pStream); + if(bResult) + { + *pFileSize = pStream->Base.Socket.fileDataLength; + } + } + CascUnlock(pStream->Lock); - // Not supported - pStream = pStream; - pByteOffset = pByteOffset; - pvBuffer = pvBuffer; - dwBytesToRead = dwBytesToRead; - SetCascError(ERROR_NOT_SUPPORTED); - return false; + // Give the result + return bResult; +} -#endif +static bool BaseHttp_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) +{ + // Give the current position + *pByteOffset = pStream->Base.Socket.fileDataPos; + return true; } static void BaseHttp_Close(TFileStream * pStream) { -#ifdef PLATFORM_WINDOWS - if(pStream->Base.Http.hConnect != NULL) - InternetCloseHandle(pStream->Base.Http.hConnect); - pStream->Base.Http.hConnect = NULL; - - if(pStream->Base.Http.hInternet != NULL) - InternetCloseHandle(pStream->Base.Http.hInternet); - pStream->Base.Http.hInternet = NULL; -#else - pStream = pStream; -#endif + if(pStream->Base.Socket.fileData != NULL) + CASC_FREE(pStream->Base.Socket.fileData); + if(pStream->Base.Socket.hostName != NULL) + CASC_FREE(pStream->Base.Socket.hostName); + if(pStream->Base.Socket.fileName != NULL) + CASC_FREE(pStream->Base.Socket.fileName); + if(pStream->Base.Socket.pSocket != NULL) + pStream->Base.Socket.pSocket->Release(); + memset(&pStream->Base.Socket, 0, sizeof(pStream->Base.Socket)); } // Initializes base functions for the mapped file @@ -851,8 +809,8 @@ static void BaseHttp_Init(TFileStream * pStream) // Supply the stream functions pStream->BaseOpen = BaseHttp_Open; pStream->BaseRead = BaseHttp_Read; - pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function - pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function + pStream->BaseGetSize = BaseHttp_GetSize; + pStream->BaseGetPos = BaseHttp_GetPos; pStream->BaseClose = BaseHttp_Close; // HTTP files are read-only @@ -894,7 +852,7 @@ static bool BlockStream_Read( return true; // Get the current position in the stream - ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos; + ByteOffset = GetByteOffset(pByteOffset, pStream->StreamPos); EndOffset = ByteOffset + dwBytesToRead; if(EndOffset > pStream->StreamSize) { @@ -1029,11 +987,12 @@ static void BlockStream_Close(TBlockStream * pStream) //----------------------------------------------------------------------------- // File stream allocation function -static STREAM_INIT StreamBaseInit[4] = +static STREAM_INIT StreamBaseInit[5] = { BaseFile_Init, BaseMap_Init, BaseHttp_Init, + BaseHttp_Init, // Ribbit provider shares code with HTTP provider BaseNone_Init }; @@ -2526,42 +2485,50 @@ size_t FileStream_Prefix(LPCTSTR szFileName, DWORD * pdwProvider) nPrefixLength1 = 5; } + // Cut out the stream provider + szFileName += nPrefixLength1; + // // Determine the base provider // - if(!_tcsnicmp(szFileName+nPrefixLength1, _T("file:"), 5)) + if(!_tcsnicmp(szFileName, _T("file:"), 5)) { dwProvider |= BASE_PROVIDER_FILE; nPrefixLength2 = 5; } - else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4)) + else if(!_tcsnicmp(szFileName, _T("map:"), 4)) { dwProvider |= BASE_PROVIDER_MAP; nPrefixLength2 = 4; } - else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5)) + else if(!_tcsnicmp(szFileName, _T("http:"), 5)) { dwProvider |= BASE_PROVIDER_HTTP; nPrefixLength2 = 5; } + else if(!_tcsnicmp(szFileName, _T("ribbit:"), 7)) + { + dwProvider |= BASE_PROVIDER_RIBBIT; + nPrefixLength2 = 7; + } + // Only accept stream provider if we recognized the base provider if(nPrefixLength2 != 0) { // It is also allowed to put "//" after the base provider, e.g. "file://", "http://" - if(szFileName[nPrefixLength1+nPrefixLength2] == '/' && szFileName[nPrefixLength1+nPrefixLength2+1] == '/') + if(szFileName[nPrefixLength2] == '/' && szFileName[nPrefixLength2+1] == '/') nPrefixLength2 += 2; if(pdwProvider != NULL) *pdwProvider = dwProvider; - return nPrefixLength1 + nPrefixLength2; } } - return 0; + return nPrefixLength1 + nPrefixLength2; } /** diff --git a/src/common/FileStream.h b/src/common/FileStream.h index acd0683a..df782200 100644 --- a/src/common/FileStream.h +++ b/src/common/FileStream.h @@ -16,7 +16,8 @@ #define BASE_PROVIDER_FILE 0x00000000 // Base data source is a file #define BASE_PROVIDER_MAP 0x00000001 // Base data source is memory-mapped file -#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server +#define BASE_PROVIDER_HTTP 0x00000002 // Base data source is a file on web server via the HTTP protocol +#define BASE_PROVIDER_RIBBIT 0x00000003 // Base data source is a file on web server via the Ribbit protocol #define BASE_PROVIDER_MASK 0x0000000F // Mask for base provider value #define STREAM_PROVIDER_FLAT 0x00000000 // Stream is linear with no offset mapping @@ -29,7 +30,6 @@ #define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write #define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it #define STREAM_FLAG_FILL_MISSING 0x00000800 // If less than expected was read from the file, fill the missing part with zeros -#define STREAM_FLAG_USE_PORT_1119 0x00001000 // For HTTP streams, use port 1119 #define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options #define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers @@ -172,12 +172,13 @@ union TBaseProviderData struct { - ULONGLONG FileSize; // Size of the file - ULONGLONG FilePos; // Current file position - ULONGLONG FileTime; // Last write time - HANDLE hInternet; // Internet handle - HANDLE hConnect; // Connection to the internet server - } Http; + class CASC_SOCKET * pSocket; // An open socket + unsigned char * fileData; // Raw response converted to file data + char * hostName; // Name of the remote host + char * fileName; // Name of the remote resource + size_t fileDataLength; // Length of the file data, in bytes + size_t fileDataPos; // Current position in the data + } Socket; }; struct TFileStream @@ -249,7 +250,7 @@ struct TEncryptedStream : public TBlockStream // Public functions for file stream TFileStream * FileStream_CreateFile(LPCTSTR szFileName, DWORD dwStreamFlags); -TFileStream * FileStream_OpenFile(LPCTSTR szFileName, DWORD dwStreamFlags); +TFileStream * FileStream_OpenFile(LPCTSTR szFileName, DWORD dwStreamFlags = 0); LPCTSTR FileStream_GetFileName(TFileStream * pStream); size_t FileStream_Prefix(LPCTSTR szFileName, DWORD * pdwProvider); diff --git a/src/common/Mime.cpp b/src/common/Mime.cpp new file mode 100644 index 00000000..52a3a5f6 --- /dev/null +++ b/src/common/Mime.cpp @@ -0,0 +1,674 @@ +/*****************************************************************************/ +/* Mime.cpp Copyright (c) Ladislav Zezula 2021 */ +/*---------------------------------------------------------------------------*/ +/* Mime functions for CascLib */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 21.01.21 1.00 Lad Created */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "../CascLib.h" +#include "../CascCommon.h" + +//----------------------------------------------------------------------------- +// Local variables + +#define BASE64_INVALID_CHAR 0xFF +#define BASE64_WHITESPACE_CHAR 0xFE + +static const char * CascBase64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static unsigned char CascBase64ToBits[0x80] = {0}; + +//----------------------------------------------------------------------------- +// CASC_MIME_HTTP implementation + +static size_t DecodeValueInt32(const char * string, const char * string_end) +{ + size_t result = 0; + + while(string < string_end && isdigit(string[0])) + { + result = (result * 10) + (string[0] - '0'); + string++; + } + + return result; +} + +bool CASC_MIME_HTTP::IsDataComplete(const char * response, size_t response_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) + { + // Check the begin of the response + if(!strncmp(response, "HTTP/1.1", 8)) + { + // Check if there's begin of the content + 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) + { + // The content length info muse 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; + } + } + } + } + } + + // If we know the expected total length, we can tell whether it's complete or not + return ((response_valid != 0) && (total_length == response_length)); +} + +//----------------------------------------------------------------------------- +// The MIME blob class + +CASC_MIME_BLOB::CASC_MIME_BLOB(char * mime_ptr, char * mime_end) +{ + ptr = mime_ptr; + end = mime_end; +} + +CASC_MIME_BLOB::~CASC_MIME_BLOB() +{ + ptr = end = NULL; +} + +char * CASC_MIME_BLOB::GetNextLine() +{ + char * mime_line = ptr; + + while(ptr < end) + { + // Every line, even the last one, must be terminated with 0D 0A + if(ptr[0] == 0x0D && ptr[1] == 0x0A) + { + // If space or tabulator follows, then this is continuation of the line + if(ptr[2] == 0x09 || ptr[2] == 0x20) + { + ptr = ptr + 2; + continue; + } + + // Terminate the line and return its begin + ptr[0] = 0; + ptr[1] = 0; + ptr = ptr + 2; + return mime_line; + } + + // Move to tne next character + ptr++; + } + + // No EOL terminated line found, break the search + return NULL; +} + +//----------------------------------------------------------------------------- +// The MIME element class + +CASC_MIME_ELEMENT::CASC_MIME_ELEMENT() +{ + memset(this, 0, sizeof(CASC_MIME_ELEMENT)); +} + +CASC_MIME_ELEMENT::~CASC_MIME_ELEMENT() +{ + // Free the children and next elements + if(folder.pChild != NULL) + delete folder.pChild; + folder.pChild = NULL; + + if(folder.pNext != NULL) + delete folder.pNext; + folder.pNext = NULL; + + // Free the data + if(data.begin != NULL) + CASC_FREE(data.begin); + data.begin = NULL; +} + +unsigned char * CASC_MIME_ELEMENT::GiveAway(size_t * ptr_data_length) +{ + unsigned char * give_away_data = data.begin; + size_t give_away_length = data.length; + + // Clear the data (DO NOT FREE) + data.begin = NULL; + data.length = 0; + + // Copy the data to local buffer + if(ptr_data_length != NULL) + ptr_data_length[0] = give_away_length; + return give_away_data; +} + +DWORD CASC_MIME_ELEMENT::Load(char * mime_data_begin, char * mime_data_end, const char * boundary_ptr) +{ + CASC_MIME_ENCODING Encoding = MimeEncodingTextPlain; + CASC_MIME_BLOB mime_data(mime_data_begin, mime_data_end); + CASC_MIME_HTTP HttpInfo; + size_t length_begin; + size_t length_end; + char * mime_line; + char boundary_begin[MAX_LENGTH_BOUNDARY + 2]; + char boundary_end[MAX_LENGTH_BOUNDARY + 4]; + DWORD dwErrCode = ERROR_SUCCESS; + bool mime_version = false; + + // Diversion for HTTP: No need to parse the entire headers and stuff. + // Just give the data right away + if(HttpInfo.IsDataComplete(mime_data_begin, (mime_data_end - mime_data_begin))) + { + if((data.begin = CASC_ALLOC(HttpInfo.content_length)) == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + memcpy(data.begin, mime_data_begin + HttpInfo.content_offset, HttpInfo.content_length); + data.length = HttpInfo.content_length; + return ERROR_SUCCESS; + } + + // Reset the boundary + boundary[0] = 0; + + // Parse line-by-line untile we find the end of data + while((mime_line = mime_data.GetNextLine()) != NULL) + { + // If the line is empty, this means that it's the end of the MIME header and begin of the MIME data + if(mime_line[0] == 0) + { + mime_data.ptr = mime_line + 2; + break; + } + + // Root nodes are required to have MIME version + if(boundary_ptr == NULL && mime_version == false) + { + if(!strcmp(mime_line, "MIME-Version: 1.0")) + { + mime_version = true; + continue; + } + if(!strcmp(mime_line, "HTTP/1.1 200 OK")) + { + mime_version = true; + continue; + } + } + + // Get the encoding + if(!strncmp(mime_line, "Content-Transfer-Encoding: ", 27)) + { + ExtractEncoding(mime_line + 27, Encoding); + continue; + } + + // Is there content type? + if(!strncmp(mime_line, "Content-Type: ", 14)) + { + ExtractBoundary(mime_line + 14); + continue; + } + } + + // Keep going only if we have MIME version + if(boundary_ptr != NULL || mime_version == true) + { + // If we have boundary, it means that the element begin + // and end is marked by the boundary + if(boundary[0]) + { + CASC_MIME_ELEMENT * pLast = NULL; + CASC_MIME_ELEMENT * pChild; + CASC_MIME_BLOB sub_mime(mime_data.ptr, NULL); + + // Construct the begin of the boundary. Don't include newline there, + // as it must also match end of the boundary + length_begin = CascStrPrintf(boundary_begin, _countof(boundary_begin), "--%s", boundary); + dwErrCode = ERROR_SUCCESS; + + // The current line must point to the begin of the boundary + // Find the end of the boundary + if(memcmp(sub_mime.ptr, boundary_begin, length_begin)) + return ERROR_BAD_FORMAT; + sub_mime.ptr += length_begin; + + // Find the end of the boundary + length_end = CascStrPrintf(boundary_end, _countof(boundary_end), "--%s--\r\n", boundary); + sub_mime.end = strstr(sub_mime.ptr, boundary_end); + if(sub_mime.end == NULL) + return ERROR_BAD_FORMAT; + + // This is the end of the MIME section. Cut it to blocks + // and put each into the separate CASC_MIME_ELEMENT + while(sub_mime.ptr < sub_mime.end) + { + char * sub_mime_next; + + // At this point, there must be newline in the current data pointer + if(sub_mime.ptr[0] != 0x0D || sub_mime.ptr[1] != 0x0A) + return ERROR_BAD_FORMAT; + sub_mime.ptr += 2; + + // Find the next MIME element. This must succeed, as the last element also matches the first element + sub_mime_next = strstr(sub_mime.ptr, boundary_begin); + if(sub_mime_next == NULL) + return ERROR_BAD_FORMAT; + + // Allocate the element + pChild = AllocateAndLoadElement(sub_mime.ptr, sub_mime_next - 2, boundary_begin); + if(pChild == NULL) + { + dwErrCode = GetCascError(); + break; + } + + // Link the element + if(folder.pChild == NULL) + folder.pChild = pChild; + if(pLast != NULL) + pLast->folder.pNext = pChild; + pLast = pChild; + + // Move to the next MIME element. Note that if we are at the ending boundary, + // this moves past the end, but it's OK, because the while loop will catch that + sub_mime.ptr = sub_mime_next + length_begin; + } + } + else + { + CASC_MIME_BLOB content(mime_data.ptr, NULL); + unsigned char * data_buffer; + size_t data_length = 0; + size_t raw_length; + + // If we have boundary pointer, we need to cut the data up to the boundary end. + // Otherwise, we decode the data to the end of the document + if(boundary_ptr != NULL) + { + // Find the end of the current data by the boundary. It is 2 characters before the next boundary + content.end = strstr(content.ptr, boundary_ptr); + if(content.end == NULL) + return ERROR_BAD_FORMAT; + if((content.ptr + 2) >= content.end) + return ERROR_BAD_FORMAT; + content.end -= 2; + } + else + { + content.end = mime_data_end - 2; + if(content.end[0] != 0x0D || content.end[1] != 0x0A) + return ERROR_BAD_FORMAT; + if((content.ptr + 2) >= content.end) + return ERROR_BAD_FORMAT; + } + + // Allocate buffer for decoded data. + // Make it the same size like source data plus zero at the end + raw_length = (content.end - content.ptr); + data_buffer = CASC_ALLOC(raw_length); + if(data_buffer != NULL) + { + // Decode the data + switch(Encoding) + { + case MimeEncodingTextPlain: + dwErrCode = DecodeTextPlain(content.ptr, content.end, data_buffer, &data_length); + break; + + case MimeEncodingQuotedPrintable: + dwErrCode = DecodeQuotedPrintable(content.ptr, content.end, data_buffer, &data_length); + break; + + case MimeEncodingBase64: + dwErrCode = DecodeBase64(content.ptr, content.end, data_buffer, &data_length); + break; + + default:; + // to remove warning + } + + // If failed, free the buffer back + if(dwErrCode != ERROR_SUCCESS) + { + CASC_FREE(data_buffer); + data_buffer = NULL; + data_length = 0; + } + } + else + { + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + } + + // Put the data there, even if they are invalid + data.begin = data_buffer; + data.length = data_length; + } + } + else + { + dwErrCode = ERROR_NOT_SUPPORTED; + } + + // Return the result of the decoding + return dwErrCode; +} + +#ifdef _DEBUG +#define MAX_LEVEL 0x10 +void CASC_MIME_ELEMENT::Print(size_t nLevel, size_t nIndex) +{ + char Prefix[(MAX_LEVEL * 4) + 0x20 + 1] = {0}; + size_t nSpaces; + + // Fill-in the spaces + nSpaces = (nLevel < MAX_LEVEL) ? (nLevel * 4) : (MAX_LEVEL * 4); + memset(Prefix, ' ', nSpaces); + + // Print the spaces and index + nSpaces = printf("%s* [%u]: ", Prefix, (int)nIndex); + memset(Prefix, ' ', nSpaces); + + // Is this a folder item? + if(folder.pChild != NULL) + { + printf("Folder item (boundary: %s)\n", boundary); + folder.pChild->Print(nLevel + 1, 0); + } + else + { + char data_printable[0x20] = {0}; + + for(size_t i = 0; (i < data.length && i < _countof(data_printable) - 1); i++) + { + if(0x20 <= data.begin[i] && data.begin[i] <= 0x7F) + data_printable[i] = data.begin[i]; + else + data_printable[i] = '.'; + } + + printf("Data item (%u bytes): \"%s\"\n", (int)data.length, data_printable); + } + + // Do we have a next element? + if(folder.pNext != NULL) + { + folder.pNext->Print(nLevel, nIndex + 1); + } +} +#endif + +CASC_MIME_ELEMENT * CASC_MIME_ELEMENT::AllocateAndLoadElement(char * a_mime_data, char * a_mime_data_end, const char * boundary_begin) +{ + CASC_MIME_ELEMENT * pElement; + DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + + // Allocate the element + if((pElement = new CASC_MIME_ELEMENT()) != NULL) + { + // Load the element + dwErrCode = pElement->Load(a_mime_data, a_mime_data_end, boundary_begin); + if(dwErrCode == ERROR_SUCCESS) + return pElement; + + // Free the element on failure + delete pElement; + } + + SetCascError(dwErrCode); + return NULL; +} + +bool CASC_MIME_ELEMENT::ExtractEncoding(const char * line, CASC_MIME_ENCODING & Encoding) +{ + if(!_stricmp(line, "base64")) + { + Encoding = MimeEncodingBase64; + return true; + } + + if(!_stricmp(line, "quoted-printable")) + { + Encoding = MimeEncodingQuotedPrintable; + return true; + } + + // Unknown encoding + return false; +} + + +bool CASC_MIME_ELEMENT::ExtractBoundary(const char * line) +{ + const char * begin; + const char * end; + + // Find the begin of the boundary + if((begin = strstr(line, "boundary=\"")) != NULL) + { + // Set begin of the boundary + begin = begin + 10; + + // Is there also end? + if((end = strchr(begin, '\"')) != NULL) + { + CascStrCopy(boundary, _countof(boundary), begin, end - begin); + return true; + } + } + + return false; +} + +DWORD CASC_MIME_ELEMENT::DecodeTextPlain(char * content_begin, char * content_end, unsigned char * data_buffer, size_t * ptr_length) +{ + size_t data_length = (size_t)(content_end - content_begin); + + // Sanity checks + assert(content_begin && content_end); + assert(content_end > content_begin); + + // Plain copy + memcpy(data_buffer, content_begin, data_length); + + // Give the result + if(ptr_length != NULL) + ptr_length[0] = data_length; + return ERROR_SUCCESS; +} + + +DWORD CASC_MIME_ELEMENT::DecodeQuotedPrintable(char * content_begin, char * content_end, unsigned char * data_buffer, size_t * ptr_length) +{ + unsigned char * save_data_buffer = data_buffer; + char * content_ptr; + DWORD dwErrCode; + + // Sanity checks + assert(content_begin && content_end); + assert(content_end > content_begin); + + // Decode the data + for(content_ptr = content_begin; content_ptr < content_end; ) + { + // If the data begins with '=', there is either newline or 2-char hexa number + if(content_ptr[0] == '=') + { + // Is there a newline after the equal sign? + if(content_ptr[1] == 0x0D && content_ptr[2] == 0x0A) + { + content_ptr += 3; + continue; + } + + // Is there hexa number after the equal sign? + dwErrCode = BinaryFromString(content_ptr + 1, 2, data_buffer); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + + content_ptr += 3; + data_buffer++; + continue; + } + else + { + *data_buffer++ = (unsigned char)(*content_ptr++); + } + } + + if(ptr_length != NULL) + ptr_length[0] = (size_t)(data_buffer - save_data_buffer); + return ERROR_SUCCESS; +} + +DWORD CASC_MIME_ELEMENT::DecodeBase64(char * content_begin, char * content_end, unsigned char * data_buffer, size_t * ptr_length) +{ + unsigned char * save_data_buffer = data_buffer; + DWORD BitBuffer = 0; + DWORD BitCount = 0; + BYTE OneByte; + + // One time preparation of the conversion table + if(CascBase64ToBits[0] == 0) + { + // Fill the entire table with 0xFF to mark invalid characters + memset(CascBase64ToBits, BASE64_INVALID_CHAR, sizeof(CascBase64ToBits)); + + // Set all whitespace characters + for(BYTE i = 1; i <= 0x20; i++) + CascBase64ToBits[i] = BASE64_WHITESPACE_CHAR; + + // Set all valid characters + for(BYTE i = 0; CascBase64Table[i] != 0; i++) + { + OneByte = CascBase64Table[i]; + CascBase64ToBits[OneByte] = i; + } + } + + // Do the decoding + while(content_begin < content_end && content_begin[0] != '=') + { + // Check for end of string + if(content_begin[0] > sizeof(CascBase64ToBits)) + return ERROR_BAD_FORMAT; + if((OneByte = CascBase64ToBits[*content_begin++]) == BASE64_INVALID_CHAR) + return ERROR_BAD_FORMAT; + if(OneByte == BASE64_WHITESPACE_CHAR) + continue; + + // Put the 6 bits into the bit buffer + BitBuffer = (BitBuffer << 6) | OneByte; + BitCount += 6; + + // Flush all values + while(BitCount >= 8) + { + // Decrement the bit count in the bit buffer + BitCount -= 8; + + // The byte is the upper 8 bits of the bit buffer + OneByte = (BYTE)(BitBuffer >> BitCount); + BitBuffer = BitBuffer % (1 << BitCount); + + // Put the byte value. The buffer can not overflow, + // because it is guaranteed to be equal to the length of the base64 string + *data_buffer++ = OneByte; + } + } + + // Return the decoded length + if(ptr_length != NULL) + ptr_length[0] = (data_buffer - save_data_buffer); + return ERROR_SUCCESS; +} + +//----------------------------------------------------------------------------- +// The MIME class + +CASC_MIME::CASC_MIME() +{} + +CASC_MIME::~CASC_MIME() +{} + +unsigned char * CASC_MIME::GiveAway(size_t * ptr_data_length) +{ + CASC_MIME_ELEMENT * pElement = &root; + unsigned char * data; + + // 1) Give the data from the root + if((data = root.GiveAway(ptr_data_length)) != NULL) + return data; + + // 2) If we have children, then give away from the first child + pElement = root.GetChild(); + if(pElement && (data = pElement->GiveAway(ptr_data_length)) != NULL) + return data; + + // Return NULL + if(ptr_data_length != NULL) + ptr_data_length[0] = 0; + return NULL; +} + +DWORD CASC_MIME::Load(char * data, size_t length) +{ + // Clear the root element + memset(&root, 0, sizeof(CASC_MIME_ELEMENT)); + + //FILE * fp = fopen("E:\\html_response.txt", "wb"); + //if(fp != NULL) + //{ + // fwrite(data, 1, length, fp); + // fclose(fp); + //} + + // Load the root element + return root.Load(data, data + length); +} + +DWORD CASC_MIME::Load(LPCTSTR szFileName) +{ + char * szFileData; + DWORD cbFileData = 0; + DWORD dwErrCode = ERROR_SUCCESS; + + // Note that LoadFileToMemory allocated one byte more and puts zero at the end + // Thus, we can treat it as zero-terminated string + szFileData = (char *)LoadFileToMemory(szFileName, &cbFileData); + if(szFileData != NULL) + { + dwErrCode = Load(szFileData, cbFileData); + CASC_FREE(szFileData); + } + else + { + dwErrCode = GetCascError(); + } + + return dwErrCode; +} + +#ifdef _DEBUG +void CASC_MIME::Print() +{ + root.Print(0, 0); +} +#endif + diff --git a/src/common/Mime.h b/src/common/Mime.h new file mode 100644 index 00000000..01dc35fd --- /dev/null +++ b/src/common/Mime.h @@ -0,0 +1,130 @@ +/*****************************************************************************/ +/* Mime.h Copyright (c) Ladislav Zezula 2021 */ +/*---------------------------------------------------------------------------*/ +/* MIME parsing functions for CascLib */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 21.01.21 1.00 Lad Created */ +/*****************************************************************************/ + +#ifndef __MIME_H__ +#define __MIME_H__ + +//----------------------------------------------------------------------------- +// MIME constants + +#define MAX_LENGTH_BOUNDARY 128 + +enum CASC_MIME_ENCODING +{ + MimeEncodingTextPlain, + MimeEncodingBase64, + MimeEncodingQuotedPrintable, + MimeEncodingMax +}; + +//----------------------------------------------------------------------------- +// Structure for caching parsed HTTP response information + +struct CASC_MIME_HTTP +{ + CASC_MIME_HTTP() + { + response_valid = content_length = content_offset = total_length = 0; + } + + bool IsDataComplete(const char * response, size_t response_length); + + 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) +}; + +//----------------------------------------------------------------------------- +// MIME blob class + +struct CASC_MIME_BLOB +{ + CASC_MIME_BLOB(char * mime_ptr, char * mime_end); + ~CASC_MIME_BLOB(); + + char * GetNextLine(); + + char * ptr; + char * end; +}; + +//----------------------------------------------------------------------------- +// MIME class + +class CASC_MIME_ELEMENT +{ + public: + + CASC_MIME_ELEMENT(); + ~CASC_MIME_ELEMENT(); + + unsigned char * GiveAway(size_t * ptr_data_length); + + DWORD Load(char * mime_data_begin, char * mime_data_end, const char * boundary_ptr = NULL); + + CASC_MIME_ELEMENT * GetChild() { return folder.pChild; } + +#ifdef _DEBUG + void Print(size_t nLevel, size_t nIndex); +#endif + + protected: + + CASC_MIME_ELEMENT * AllocateAndLoadElement(char * a_mime_data, char * a_mime_data_end, const char * boundary_begin); + bool ExtractEncoding(const char * line, CASC_MIME_ENCODING & Encoding); + bool ExtractBoundary(const char * line); + + DWORD DecodeTextPlain(char * content_begin, char * content_end, unsigned char * data_ptr, size_t * ptr_length); + DWORD DecodeQuotedPrintable(char * content_begin, char * content_end, unsigned char * data_ptr, size_t * ptr_length); + DWORD DecodeBase64(char * content_begin, char * content_end, unsigned char * data_ptr, size_t * ptr_length); + + struct + { + CASC_MIME_ELEMENT * pChild; // Pointer to the first child + CASC_MIME_ELEMENT * pNext; // Pointer to the next-in-folder element + } folder; + + struct + { + unsigned char * begin; + size_t length; + } data; + + char boundary[MAX_LENGTH_BOUNDARY]; +}; + +class CASC_MIME +{ + public: + + CASC_MIME(); + ~CASC_MIME(); + + unsigned char * GiveAway(size_t * ptr_data_length); + + DWORD Load(char * data, size_t length); + DWORD Load(LPCTSTR fileName); + +#ifdef _DEBUG + void Print(); +#endif + + protected: + + 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/src/common/Path.h b/src/common/Path.h index b77dde0c..98ba601d 100644 --- a/src/common/Path.h +++ b/src/common/Path.h @@ -17,11 +17,11 @@ template struct CASC_PATH { - CASC_PATH(xchar chSeparator = PATH_SEP_CHAR) + CASC_PATH(int chSeparator = PATH_SEP_CHAR) { m_szBufferBegin = m_szBufferPtr = m_Buffer; m_szBufferEnd = m_szBufferBegin + _countof(m_Buffer); - m_chSeparator = chSeparator; + m_chSeparator = (xchar)chSeparator; m_Buffer[0] = 0; } diff --git a/src/common/Sockets.cpp b/src/common/Sockets.cpp new file mode 100644 index 00000000..e1f58f6f --- /dev/null +++ b/src/common/Sockets.cpp @@ -0,0 +1,441 @@ +/*****************************************************************************/ +/* Sockets.cpp Copyright (c) Ladislav Zezula 2021 */ +/*---------------------------------------------------------------------------*/ +/* Don't call this module "Socket.cpp", otherwise VS 2019 will not link it */ +/* Socket functions for CascLib. */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 13.02.21 1.00 Lad Created */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "../CascLib.h" +#include "../CascCommon.h" + +//----------------------------------------------------------------------------- +// Local variables + +CASC_SOCKET_CACHE SocketCache; + +//----------------------------------------------------------------------------- +// CASC_SOCKET functions + +// Guarantees that there is zero terminator after the response +char * CASC_SOCKET::ReadResponse(const char * request, size_t request_length, size_t * PtrLength) +{ + CASC_MIME_HTTP HttpInfo; + char * server_response = NULL; + size_t total_received = 0; + size_t block_increment = 0x8000; + size_t buffer_size = block_increment; + int bytes_received = 0; + + // Pre-set the result length + if(PtrLength != NULL) + PtrLength[0] = 0; + if(request_length == 0) + request_length = strlen(request); + + // Lock the socket + CascLock(Lock); + + // Send the request to the remote host. On Linux, this call may send signal(SIGPIPE), + // we need to prevend that by using the MSG_NOSIGNAL flag. On Windows, it fails normally. + while(send(sock, request, (int)request_length, MSG_NOSIGNAL) == SOCKET_ERROR) + { + // If the connection was closed by the remote host, we try to reconnect + if(ReconnectAfterShutdown(sock, remoteItem) == INVALID_SOCKET) + { + SetCascError(ERROR_NETWORK_NOT_AVAILABLE); + CascUnlock(Lock); + return NULL; + } + } + + // Allocate buffer for server response. Allocate one extra byte for zero terminator + if((server_response = CASC_ALLOC(buffer_size + 1)) != NULL) + { + for(;;) + { + // Reallocate the buffer size, if needed + if(total_received == buffer_size) + { + if((server_response = CASC_REALLOC(char, server_response, buffer_size + block_increment + 1)) == NULL) + { + SetCascError(ERROR_NOT_ENOUGH_MEMORY); + CascUnlock(Lock); + return NULL; + } + buffer_size += block_increment; + } + + // 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); + if(bytes_received <= 0) + 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; + } + } + + // Unlock the socket + CascUnlock(Lock); + + // Give the result to the caller + if(PtrLength != NULL) + PtrLength[0] = total_received; + return server_response; +} + +DWORD CASC_SOCKET::AddRef() +{ + return CascInterlockedIncrement(&dwRefCount); +} + +void CASC_SOCKET::Release() +{ + // Note: If this is a cached socket, there will be extra reference from the cache + if(CascInterlockedDecrement(&dwRefCount) == 0) + { + Delete(); + } +} + +int CASC_SOCKET::GetSockError() +{ +#ifdef PLATFORM_WINDOWS + return WSAGetLastError(); +#else + return errno; +#endif +} + +DWORD CASC_SOCKET::GetAddrInfoWrapper(const char * hostName, unsigned portNum, PADDRINFO hints, PADDRINFO * ppResult) +{ + char portNumString[16]; + + // Prepare the port number + CascStrPrintf(portNumString, _countof(portNumString), "%d", portNum); + + // Attempt to connect + for(;;) + { + // Attempt to call the addrinfo + DWORD dwErrCode = getaddrinfo(hostName, portNumString, hints, ppResult); + + // Error-specific handling + switch(dwErrCode) + { +#ifdef PLATFORM_WINDOWS + case WSANOTINITIALISED: // Windows-specific: WSAStartup not called + { + WSADATA wsd; + + WSAStartup(MAKEWORD(2, 2), &wsd); + continue; + } +#endif + case EAI_AGAIN: // Temporary error, try again + continue; + + default: // Any other result, incl. ERROR_SUCCESS + return dwErrCode; + } + } +} + +SOCKET CASC_SOCKET::CreateAndConnect(addrinfo * remoteItem) +{ + SOCKET sock; + + // Create new socket + // On error, returns returns INVALID_SOCKET (-1) on Windows, -1 on Linux + if((sock = socket(remoteItem->ai_family, remoteItem->ai_socktype, remoteItem->ai_protocol)) > 0) + { + // Connect to the remote host + // On error, returns SOCKET_ERROR (-1) on Windows, -1 on Linux + if(connect(sock, remoteItem->ai_addr, (int)remoteItem->ai_addrlen) == 0) + return sock; + + // Failed. Close the socket and return 0 + closesocket(sock); + sock = INVALID_SOCKET; + } + + return sock; +} + +SOCKET CASC_SOCKET::ReconnectAfterShutdown(SOCKET & sock, addrinfo * remoteItem) +{ + // Retrieve the error code related to previous socket operation + switch(GetSockError()) + { + case EPIPE: // Non-Windows + case WSAECONNRESET: // Windows + { + // Close the old socket + if(sock != INVALID_SOCKET) + closesocket(sock); + + // Attempt to reconnect + sock = CreateAndConnect(remoteItem); + return sock; + } + } + + // Another problem + return INVALID_SOCKET; +} + +PCASC_SOCKET CASC_SOCKET::New(addrinfo * remoteList, addrinfo * remoteItem, const char * hostName, unsigned portNum, SOCKET sock) +{ + PCASC_SOCKET pSocket; + size_t length = strlen(hostName); + + // Allocate enough bytes + pSocket = (PCASC_SOCKET)CASC_ALLOC(sizeof(CASC_SOCKET) + length); + if(pSocket != NULL) + { + // Fill the entire object with zero + memset(pSocket, 0, sizeof(CASC_SOCKET) + length); + pSocket->remoteList = remoteList; + pSocket->remoteItem = remoteItem; + pSocket->dwRefCount = 1; + pSocket->portNum = portNum; + pSocket->sock = sock; + + // Init the remote host name + CascStrCopy((char *)pSocket->hostName, length + 1, hostName); + + // Init the socket lock + CascInitLock(pSocket->Lock); + } + + return pSocket; +} + +PCASC_SOCKET CASC_SOCKET::Connect(const char * hostName, unsigned portNum) +{ + PCASC_SOCKET pSocket; + addrinfo * remoteList; + addrinfo * remoteItem; + addrinfo hints = {0}; + SOCKET sock; + int nErrCode; + + // Retrieve the information about the remote host + // This will fail immediately if there is no connection to the internet + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + nErrCode = GetAddrInfoWrapper(hostName, portNum, &hints, &remoteList); + + // Handle error code + if(nErrCode == 0) + { + // Try to connect to any address provided by the getaddrinfo() + for(remoteItem = remoteList; remoteItem != NULL; remoteItem = remoteItem->ai_next) + { + // Create new socket and connect to the remote host + if((sock = CreateAndConnect(remoteItem)) != 0) + { + // Create new instance of the CASC_SOCKET structure + if((pSocket = CASC_SOCKET::New(remoteList, remoteItem, hostName, portNum, sock)) != NULL) + { + return pSocket; + } + + // Close the socket + closesocket(sock); + } + } + + // Couldn't find a network + nErrCode = ERROR_NETWORK_NOT_AVAILABLE; + } + + SetCascError(nErrCode); + return NULL; +} + +void CASC_SOCKET::Delete() +{ + PCASC_SOCKET pThis = this; + + // Remove the socket from the cache + if(pCache != NULL) + pCache->UnlinkSocket(this); + pCache = NULL; + + // Close the socket, if any + if(sock != 0) + closesocket(sock); + sock = 0; + + // Free the lock + CascFreeLock(Lock); + + // Free the socket itself + CASC_FREE(pThis); +} + +//----------------------------------------------------------------------------- +// The CASC_SOCKET_CACHE class + +CASC_SOCKET_CACHE::CASC_SOCKET_CACHE() +{ + pFirst = pLast = NULL; + dwRefCount = 0; +} + +CASC_SOCKET_CACHE::~CASC_SOCKET_CACHE() +{ + PurgeAll(); +} + +PCASC_SOCKET CASC_SOCKET_CACHE::Find(const char * hostName, unsigned portNum) +{ + PCASC_SOCKET pSocket; + + for(pSocket = pFirst; pSocket != NULL; pSocket = pSocket->pNext) + { + if(!_stricmp(pSocket->hostName, hostName) && (pSocket->portNum == portNum)) + break; + } + + return pSocket; +} + +PCASC_SOCKET CASC_SOCKET_CACHE::InsertSocket(PCASC_SOCKET pSocket) +{ + if(pSocket != NULL && pSocket->pCache == NULL) + { + // Do we have caching turned on? + if(dwRefCount > 0) + { + // Insert one reference to the socket to mark it as cached + pSocket->AddRef(); + + // Insert the socket to the chain + if(pFirst == NULL && pLast == NULL) + { + pFirst = pLast = pSocket; + } + else + { + pSocket->pPrev = pLast; + pLast->pNext = pSocket; + pLast = pSocket; + } + + // Mark the socket as cached + pSocket->pCache = this; + } + } + + return pSocket; +} + +void CASC_SOCKET_CACHE::UnlinkSocket(PCASC_SOCKET pSocket) +{ + // Only if it's a valid socket + if(pSocket != NULL) + { + // Check the first and the last items + if(pSocket == pFirst) + pFirst = pSocket->pNext; + if(pSocket == pLast) + pLast = pSocket->pPrev; + + // Disconnect the socket from the chain + if(pSocket->pPrev != NULL) + pSocket->pPrev->pNext = pSocket->pNext; + if(pSocket->pNext != NULL) + pSocket->pNext->pPrev = pSocket->pPrev; + } +} + +void CASC_SOCKET_CACHE::SetCaching(bool bAddRef) +{ + PCASC_SOCKET pSocket; + PCASC_SOCKET pNext; + + // We need to increment reference count for each enabled caching + if(bAddRef) + { + // Add one reference to all currently held sockets + if(dwRefCount == 0) + { + for(pSocket = pFirst; pSocket != NULL; pSocket = pSocket->pNext) + pSocket->AddRef(); + } + + // Increment of references for the future sockets + CascInterlockedIncrement(&dwRefCount); + } + else + { + // Sanity check for multiple calls to dereference + assert(dwRefCount > 0); + + // Dereference the reference count. If drops to zero, dereference all sockets as well + if(CascInterlockedDecrement(&dwRefCount) == 0) + { + for(pSocket = pFirst; pSocket != NULL; pSocket = pNext) + { + pNext = pSocket->pNext; + pSocket->Release(); + } + } + } +} + +void CASC_SOCKET_CACHE::PurgeAll() +{ + PCASC_SOCKET pSocket; + PCASC_SOCKET pNext; + + // Dereference all current sockets + for(pSocket = pFirst; pSocket != NULL; pSocket = pNext) + { + pNext = pSocket->pNext; + pSocket->Delete(); + } +} + +//----------------------------------------------------------------------------- +// Public functions + +PCASC_SOCKET sockets_connect(const char * hostName, unsigned portNum) +{ + PCASC_SOCKET pSocket; + + // Try to find the item in the cache + if((pSocket = SocketCache.Find(hostName, portNum)) != NULL) + { + pSocket->AddRef(); + } + else + { + // Create new socket and connect it to the remote host + pSocket = CASC_SOCKET::Connect(hostName, portNum); + + // Insert it to the cache, if it's a HTTP connection + if(pSocket->portNum == CASC_PORT_HTTP) + pSocket = SocketCache.InsertSocket(pSocket); + } + + return pSocket; +} + +void sockets_set_caching(bool caching) +{ + SocketCache.SetCaching(caching); +} diff --git a/src/common/Sockets.h b/src/common/Sockets.h new file mode 100644 index 00000000..5d1aa677 --- /dev/null +++ b/src/common/Sockets.h @@ -0,0 +1,115 @@ +/*****************************************************************************/ +/* Sockets.h Copyright (c) Ladislav Zezula 2021 */ +/*---------------------------------------------------------------------------*/ +/* MIME parsing functions for CascLib */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 13.02.21 1.00 Lad Created */ +/*****************************************************************************/ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +//----------------------------------------------------------------------------- +// Defines + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (SOCKET)(-1) +#endif + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR (-1) +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#ifndef WSAECONNRESET +#define WSAECONNRESET 10054L +#endif + +#ifndef EPIPE +#define EPIPE 32 +#endif + +#define CASC_PORT_HTTP 80 +#define CASC_PORT_RIBBIT 1119 + +//----------------------------------------------------------------------------- +// The CASC_SOCKET class + +typedef class CASC_SOCKET_CACHE * PCASC_SOCKET_CACHE; +typedef class CASC_SOCKET * PCASC_SOCKET; +typedef struct addrinfo * PADDRINFO; + +class CASC_SOCKET +{ + public: + + char * ReadResponse(const char * request, size_t request_length = 0, size_t * PtrLength = NULL); + DWORD AddRef(); + void Release(); + + private: + + // Constructor and destructor + static int GetSockError(); + static DWORD GetAddrInfoWrapper(const char * hostName, unsigned portNum, PADDRINFO hints, PADDRINFO * ppResult); + static SOCKET CreateAndConnect(addrinfo * remoteItem); + static SOCKET ReconnectAfterShutdown(SOCKET & sock, addrinfo * remoteItem); + static PCASC_SOCKET New(addrinfo * remoteList, addrinfo * remoteItem, const char * hostName, unsigned portNum, SOCKET sock); + static PCASC_SOCKET Connect(const char * hostName, unsigned portNum); + + // Frees all resources and deletes the socket + void Delete(); + + // Entities allowed to manipulate with the class + friend CASC_SOCKET * sockets_connect(const char * hostName, unsigned portNum); + friend char * sockets_read_response(PCASC_SOCKET pSocket, const char * request, size_t request_length, size_t * PtrLength); + friend class CASC_SOCKET_CACHE; + + PCASC_SOCKET_CACHE pCache; // Pointer to the cache. If NULL, the socket is not cached + PCASC_SOCKET pPrev; // Pointer to the prev socket in the list + PCASC_SOCKET pNext; // Pointer to the next socket in the list + PADDRINFO remoteList; // List of the remote host informations + PADDRINFO remoteItem; // The particular host picked during the last connection attempt + CASC_LOCK Lock; // Lock for single threaded access + SOCKET sock; // Opened and connected socket + DWORD dwRefCount; // Number of references + DWORD portNum; // Port number + char hostName[1]; // Buffer for storing remote host (variable length) +}; + +//----------------------------------------------------------------------------- +// Socket cache class + +class CASC_SOCKET_CACHE +{ + public: + + CASC_SOCKET_CACHE(); + ~CASC_SOCKET_CACHE(); + + PCASC_SOCKET Find(const char * hostName, unsigned portNum); + PCASC_SOCKET InsertSocket(PCASC_SOCKET pSocket); + void UnlinkSocket(PCASC_SOCKET pSocket); + + void SetCaching(bool bAddRef); + void PurgeAll(); + + private: + + PCASC_SOCKET pFirst; + PCASC_SOCKET pLast; + DWORD dwRefCount; +}; + +//----------------------------------------------------------------------------- +// Public functions + +PCASC_SOCKET sockets_connect(const char * hostName, unsigned portNum); +void sockets_set_caching(bool caching); + +#endif // __SOCKET_H__ diff --git a/test/CascTest.cpp b/test/CascTest.cpp index 7b8867be..79941c2c 100644 --- a/test/CascTest.cpp +++ b/test/CascTest.cpp @@ -15,9 +15,12 @@ #include #include -#ifndef _WIN32 -#include // STD implementaion of threads -#include +#ifdef __has_include + #if __has_include() + #define PLATFORM_STD_THREAD + #include + #include + #endif #endif #include "../src/CascLib.h" @@ -43,19 +46,17 @@ #endif #ifdef PLATFORM_LINUX +#define CASC_PATH_ROOT "/media/ladik/CascStorages/CASC" +#define CASC_WORK_ROOT "/home/ladik/CASC/Work" +#endif + +#ifdef PLATFORM_MAC #define CASC_PATH_ROOT "/media/ladik/CascStorages" -#define CASC_WORK_ROOT "/media/ladik/CascStorages/Work" -#define UNREFERENCED_PARAMETER(var) (void)var; +#define CASC_WORK_ROOT "/home/ladik/CASC/Work" // TODO #endif static const char szCircleChar[] = "|/-\\"; -#if defined(_MSC_VER) && defined(_DEBUG) -#define GET_TICK_COUNT() GetTickCount() -#else -#define GET_TICK_COUNT() 0 -#endif - #define SHORT_NAME_SIZE 59 //----------------------------------------------------------------------------- @@ -548,7 +549,35 @@ static DWORD WINAPI Worker_ExtractFiles(PCASC_FIND_DATA_ARRAY pFiles) static void RunExtractWorkers(PCASC_FIND_DATA_ARRAY pFiles) { -#ifdef _WIN32 +#ifdef PLATFORM_STD_THREAD + + std::vector threads; + size_t dwCoresUsed = 10; + +#ifdef PLATFORM_WINDOWS + // Retrieve the number of available cores + SYSTEM_INFO si = {0}; + DWORD dwFreeCPUs = 2; + + GetSystemInfo(&si); + dwCoresUsed = (si.dwNumberOfProcessors > dwFreeCPUs) ? (si.dwNumberOfProcessors - dwFreeCPUs) : 1; + if(dwCoresUsed > MAXIMUM_WAIT_OBJECTS) + dwCoresUsed = MAXIMUM_WAIT_OBJECTS; +#endif + + // Run up to 40 worker threads + for (size_t i = 0; i < dwCoresUsed; i++) + { + threads.emplace_back(&Worker_ExtractFiles, pFiles); + } + + // Let them threads finish their job + for (auto &thread : threads) + { + thread.join(); + } + +#else SYSTEM_INFO si = { 0 }; HANDLE ThreadHandles[MAXIMUM_WAIT_OBJECTS]; @@ -574,23 +603,6 @@ static void RunExtractWorkers(PCASC_FIND_DATA_ARRAY pFiles) // Let them threads finish their job WaitForMultipleObjects(dwThreads, ThreadHandles, TRUE, INFINITE); -#else - - std::vector threads; - size_t dwCoresUsed = 10; - - // Run up to 40 worker threads - for (size_t i = 0; i < dwCoresUsed; i++) - { - threads.emplace_back(&Worker_ExtractFiles, pFiles); - } - - // Let them threads finish their job - for (auto &thread : threads) - { - thread.join(); - } - #endif } @@ -1033,73 +1045,70 @@ static DWORD OnlineStorage_Test(PFN_RUN_TEST PfnRunTest, LPCSTR szCodeName, LPCS static STORAGE_INFO1 StorageInfo1[] = { //- Name of the storage folder -------- Compound file name hash ----------- Compound file data hash ----------- Example file to extract ----------------------------------------------------------- - //{"Beta TVFS/00001", "44833489ccf495e78d3a8f2ee9688ba6", "96e6457b649b11bcee54d52fa4be12e5", "ROOT"}, - //{"Beta TVFS/00002", "0ada2ba6b0decfa4013e0465f577abf1", "4da83fa60e0e505d14a5c21284142127", "ENCODING"}, + {"Beta TVFS/00001", "44833489ccf495e78d3a8f2ee9688ba6", "96e6457b649b11bcee54d52fa4be12e5", "ROOT"}, + {"Beta TVFS/00002", "0ada2ba6b0decfa4013e0465f577abf1", "4da83fa60e0e505d14a5c21284142127", "ENCODING"}, - //{"CoD4/3376209", "e01180b36a8cfd82cb2daa862f5bbf3e", "79cd4cfc9eddad53e4b4d394c36b8b0c", "zone/base.xpak" }, + {"CoD4/3376209", "e01180b36a8cfd82cb2daa862f5bbf3e", "79cd4cfc9eddad53e4b4d394c36b8b0c", "zone/base.xpak" }, - //{"Diablo III/30013", "86ba76b46c88eb7c6188d28a27d00f49", "19e37cc3c178ea0521369c09d67791ac", "ENCODING"}, - //{"Diablo III/50649", "18cd3eb87a46e2d3aa0c57d1d8f8b8ff", "9225b3fa85dd958209ad20495ff6457e", "ENCODING"}, - //{"Diablo III/58979", "3c5e033739bb58ce1107e59b8d30962a", "901dd9dde4e793ee42414c81874d1c8f", "ENCODING"}, - //{"Diablo III/68722", "34cb5a5cea775b7194d9cd0ec3458d3b", "eeaa6a963aa19d93bdafc049fe6d3aaf", "ENCODING"}, + {"Diablo III/30013", "86ba76b46c88eb7c6188d28a27d00f49", "19e37cc3c178ea0521369c09d67791ac", "ENCODING"}, + {"Diablo III/50649", "18cd3eb87a46e2d3aa0c57d1d8f8b8ff", "9225b3fa85dd958209ad20495ff6457e", "ENCODING"}, + {"Diablo III/58979", "3c5e033739bb58ce1107e59b8d30962a", "901dd9dde4e793ee42414c81874d1c8f", "ENCODING"}, + {"Diablo III/68722", "34cb5a5cea775b7194d9cd0ec3458d3b", "eeaa6a963aa19d93bdafc049fe6d3aaf", "ENCODING"}, {"Heroes of the Storm/29049", "98396c1a521e5dee511d835b9e8086c7", "b37e7edc07d465a8e97b47cabcd3fc04", "mods\\core.stormmod\\base.stormassets\\assets\\textures\\aicommand_autoai1.dds"}, - //{"Heroes of the Storm/30027", "6bcbe7c889cc465e4993f92d6ae1ee75", "978f6332a2f2149d74d48414b834c8f6", "mods\\core.stormmod\\base.stormassets\\assets\\textures\\aicommand_claim1.dds"}, - //{"Heroes of the Storm/30414", "4b377fa69dab736b2ae495920663832e", "367eef337676c902bf6855f54bbda182", "mods\\heromods\\murky.stormmod\\base.stormdata\\gamedata\\buttondata.xml"}, - //{"Heroes of the Storm/31726", "f997a06b3f8c10d9095e542f1ef83a74", "0eb064b28fc6203a48321a15d17f7df8", "mods\\heroes.stormmod\\base.stormassets\\Assets\\modeltextures.db"}, - //{"Heroes of the Storm/39445", "c672b26f8f14ab2e68a9f9d7d6ca6062", "62376a66045c7806e865ef4b056c7060", "versions.osxarchive\\Versions\\Base39153\\Heroes.app\\Contents\\_CodeSignature\\CodeResources"}, - //{"Heroes of the Storm/50286", "d1d57e83cbd72cbecd76916c22f6c4b6", "c1fe97f5fc04a2824449b6c43cf31ce5", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, - //{"Heroes of the Storm/65943", "c5d75f4e12dbc05d4560fe61c4b88773", "f046b2ed9ecc7b27d2a114e16c34c8fd", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, - //{"Heroes of the Storm/75589", "ae2209f1fcb26c730e9757a42bcce17e", "a7f7fbf1e04c87ead423fb567cd6fa5c", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, - //{"Heroes of the Storm/81376", "25597a3f8adc3fa79df243197fecd1cc", "2c36eb3dde7d545a0fa413ccebf84202", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, - - //{"Overwatch/24919/data/casc", "53afa15570c29bd40bba4707b607657e", "6f9131fc0e7ad558328bbded2c996959", "ROOT"}, - //{"Overwatch/47161", "53db1f3da005211204997a6b50aa71e1", "12be32a2f86ea1f4e0bf2b62fe4b7f6e", "TactManifest\\Win_SPWin_RCN_LesMX_EExt.apm"}, - //{"Overwatch/72127", "bef17230badb29e5c7dad18a2b30df8a", "bae70b787316d724646b954978284c14", "TactManifest\\Win_SPWin_RCN_LesMX_EExt.apm"}, - - //{"Starcraft/2457", "3eabb81825735cf66c0fc10990f423fa", "ce752a323819c369fba03401ba400332", "music\\radiofreezerg.ogg"}, - //{"Starcraft/4037", "bb2b76d657a841953fe093b75c2bdaf6", "2f1e9df40da0f6f682ffecbbd920d4fc", "music\\radiofreezerg.ogg"}, - //{"Starcraft/4261", "59ea96addacccb73938fdf688d7aa29b", "4e07a768999c7887c8c21364961ab07a", "music\\radiofreezerg.ogg"}, - //{"Starcraft/6434", "e3f929b881ad07028578d202f97c107e", "9bf9597b1f10d32944194334e8dc442a", "music\\radiofreezerg.ogg"}, - //{"Starcraft/8713", "57da9e2768368d3e31473a70a9286a69", "6a425e9d9e7f3b44773a021ea89f85e3", "music\\radiofreezerg.ogg"}, - - //{"Starcraft II/45364/\\/", "28f8b15b5bbd87c16796246eac3f800c", "f9cd7fc20fa53701846109d3d6947d08", "mods\\novastoryassets.sc2mod\\base2.sc2maps\\maps\\campaign\\nova\\nova04.sc2map\\base.sc2data\\GameData\\ActorData.xml"}, - //{"Starcraft II/75025", "79c044e1286b7b18478556e571901294", "e290febb90e06e97b4db6f0eb519ca91", "mods\\novastoryassets.sc2mod\\base2.sc2maps\\maps\\campaign\\nova\\nova04.sc2map\\base.sc2data\\GameData\\ActorData.xml"}, - //{"Starcraft II/81102", "cb6bea299820895f6dcbc72067553743", "63b47f03b1717ded751e0d24d3ddff4f", "mods\\novastoryassets.sc2mod\\base2.sc2maps\\maps\\campaign\\nova\\nova04.sc2map\\base.sc2data\\GameData\\ActorData.xml"}, - - //{"Warcraft III/09231", "8147106d7c05eaaf3f3611cc6f5314fe", "1b47c84d9b4ce58beeb2604a934cf83c", "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" }, - //{"Warcraft III/09655", "f3f5470aa0ab4939fa234d3e29c3d347", "e45792b7459dc0c78ecb25130fa34d88", "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" }, - //{"Warcraft III/11889", "ff36cd4f58aae23bd77d4a90c333bdb5", "4cba488e57f7dccfb77eca8c86578a37", "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" }, - //{"Warcraft III/13369", "9c3fce648bf75d93a8765e84dcd10377", "4ac831db9bf0734f01b9d20455a68ab6", "ENCODING" }, - //{"Warcraft III/14883", "a4b269415f1f4adec4df8bb736dc1297", "3fd108674117ad4f93885bdd1a525f30", NULL }, - //{"Warcraft III/15801", "e1c3cfa897c8a25ef493455469955186", "f162cd3448219fd9956f9ff8fb5ba915", NULL }, - - //{"WoW/18125", "b31531af094f78f58592249c4d216a8e", "e5c9b3f0da7806d8b239c13bff1d836e", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/18379", "fab30626cf94ed1523519729c3701812", "606e4bfd6f8100ae875eb4c00789233b", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/18865", "7f252a8c6001938f601b0c91abbb0f2a", "cee96fa43cddc008f564b4615fdbd109", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/18888", "a007d0433c71ddc6e9acaa45cbdc4e61", "a093c596240a6b71de125eaa83ea8568", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/19116", "a3be9cfd4a15ba184e21eed9ec90417b", "11a973871aef6ab3236676a25381a1e6", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/19342", "66f0de0cff477e1d8e982683771f1ada", "69b4c91c977b875fd0a6ffbf89b06408", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/21742", "a357c3cbed98e83ac5cd394ceabc01e8", "90ce1aac44299aa2ac6fb44d249d2561", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/22267", "101949dfbed06d417d24a65054e8a6b6", "4ef8df3cf9b00b5c7b2c1b9f4166ec0d", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/23420", "e62a798989e6db00044b079e74faa1eb", "854e58816e6eb2795d14fe81470ad19e", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/29981", "a35f7de61584644d4877aac1380ef090", "3cba30b5e439a6e59b0953d17da9ac6c", "dbfilesclient\\battlepetspeciesstate.db2"}, - //{"WoW/31299:wow", "6220549f2b8936af6e63179f6ece78ab", "05627c131969bd9394fb345f4037e249", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/31299:wowt", "959fa63cbcd9ced02a8977ed128df828", "423c1b99b14a615a02d8ffc7a7eff4ef", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - //{"WoW/31299:wow_classic", "184794b8a191429e2aae9b8a5334651b", "b46bd2f81ead285e810e5a049ca2db74", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, - - {NULL} + {"Heroes of the Storm/30027", "6bcbe7c889cc465e4993f92d6ae1ee75", "978f6332a2f2149d74d48414b834c8f6", "mods\\core.stormmod\\base.stormassets\\assets\\textures\\aicommand_claim1.dds"}, + {"Heroes of the Storm/30414", "4b377fa69dab736b2ae495920663832e", "367eef337676c902bf6855f54bbda182", "mods\\heromods\\murky.stormmod\\base.stormdata\\gamedata\\buttondata.xml"}, + {"Heroes of the Storm/31726", "f997a06b3f8c10d9095e542f1ef83a74", "0eb064b28fc6203a48321a15d17f7df8", "mods\\heroes.stormmod\\base.stormassets\\Assets\\modeltextures.db"}, + {"Heroes of the Storm/39445", "c672b26f8f14ab2e68a9f9d7d6ca6062", "62376a66045c7806e865ef4b056c7060", "versions.osxarchive\\Versions\\Base39153\\Heroes.app\\Contents\\_CodeSignature\\CodeResources"}, + {"Heroes of the Storm/50286", "d1d57e83cbd72cbecd76916c22f6c4b6", "c1fe97f5fc04a2824449b6c43cf31ce5", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, + {"Heroes of the Storm/65943", "c5d75f4e12dbc05d4560fe61c4b88773", "f046b2ed9ecc7b27d2a114e16c34c8fd", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, + {"Heroes of the Storm/75589", "ae2209f1fcb26c730e9757a42bcce17e", "a7f7fbf1e04c87ead423fb567cd6fa5c", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, + {"Heroes of the Storm/81376", "25597a3f8adc3fa79df243197fecd1cc", "2c36eb3dde7d545a0fa413ccebf84202", "mods\\gameplaymods\\percentscaling.stormmod\\base.stormdata\\GameData\\EffectData.xml"}, + + {"Overwatch/24919/data/casc", "53afa15570c29bd40bba4707b607657e", "6f9131fc0e7ad558328bbded2c996959", "ROOT"}, + {"Overwatch/47161", "53db1f3da005211204997a6b50aa71e1", "12be32a2f86ea1f4e0bf2b62fe4b7f6e", "TactManifest\\Win_SPWin_RCN_LesMX_EExt.apm"}, + {"Overwatch/72127", "bef17230badb29e5c7dad18a2b30df8a", "bae70b787316d724646b954978284c14", "TactManifest\\Win_SPWin_RCN_LesMX_EExt.apm"}, + + {"Starcraft/2457", "3eabb81825735cf66c0fc10990f423fa", "ce752a323819c369fba03401ba400332", "music\\radiofreezerg.ogg"}, + {"Starcraft/4037", "bb2b76d657a841953fe093b75c2bdaf6", "2f1e9df40da0f6f682ffecbbd920d4fc", "music\\radiofreezerg.ogg"}, + {"Starcraft/4261", "59ea96addacccb73938fdf688d7aa29b", "4e07a768999c7887c8c21364961ab07a", "music\\radiofreezerg.ogg"}, + {"Starcraft/6434", "e3f929b881ad07028578d202f97c107e", "9bf9597b1f10d32944194334e8dc442a", "music\\radiofreezerg.ogg"}, + {"Starcraft/8713", "57da9e2768368d3e31473a70a9286a69", "6a425e9d9e7f3b44773a021ea89f85e3", "music\\radiofreezerg.ogg"}, + + {"Starcraft II/45364/\\/", "28f8b15b5bbd87c16796246eac3f800c", "f9cd7fc20fa53701846109d3d6947d08", "mods\\novastoryassets.sc2mod\\base2.sc2maps\\maps\\campaign\\nova\\nova04.sc2map\\base.sc2data\\GameData\\ActorData.xml"}, + {"Starcraft II/75025", "79c044e1286b7b18478556e571901294", "e290febb90e06e97b4db6f0eb519ca91", "mods\\novastoryassets.sc2mod\\base2.sc2maps\\maps\\campaign\\nova\\nova04.sc2map\\base.sc2data\\GameData\\ActorData.xml"}, + {"Starcraft II/81102", "cb6bea299820895f6dcbc72067553743", "63b47f03b1717ded751e0d24d3ddff4f", "mods\\novastoryassets.sc2mod\\base2.sc2maps\\maps\\campaign\\nova\\nova04.sc2map\\base.sc2data\\GameData\\ActorData.xml"}, + + {"Warcraft III/09231", "8147106d7c05eaaf3f3611cc6f5314fe", "1b47c84d9b4ce58beeb2604a934cf83c", "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" }, + {"Warcraft III/09655", "f3f5470aa0ab4939fa234d3e29c3d347", "e45792b7459dc0c78ecb25130fa34d88", "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" }, + {"Warcraft III/11889", "ff36cd4f58aae23bd77d4a90c333bdb5", "4cba488e57f7dccfb77eca8c86578a37", "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" }, + {"Warcraft III/13369", "9c3fce648bf75d93a8765e84dcd10377", "4ac831db9bf0734f01b9d20455a68ab6", "ENCODING" }, + {"Warcraft III/14883", "a4b269415f1f4adec4df8bb736dc1297", "3fd108674117ad4f93885bdd1a525f30", NULL }, + {"Warcraft III/15801", "e1c3cfa897c8a25ef493455469955186", "f162cd3448219fd9956f9ff8fb5ba915", NULL }, + + {"WoW/18125", "b31531af094f78f58592249c4d216a8e", "e5c9b3f0da7806d8b239c13bff1d836e", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/18379", "fab30626cf94ed1523519729c3701812", "606e4bfd6f8100ae875eb4c00789233b", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/18865", "7f252a8c6001938f601b0c91abbb0f2a", "cee96fa43cddc008f564b4615fdbd109", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/18888", "a007d0433c71ddc6e9acaa45cbdc4e61", "a093c596240a6b71de125eaa83ea8568", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/19116", "a3be9cfd4a15ba184e21eed9ec90417b", "11a973871aef6ab3236676a25381a1e6", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/19342", "66f0de0cff477e1d8e982683771f1ada", "69b4c91c977b875fd0a6ffbf89b06408", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/21742", "a357c3cbed98e83ac5cd394ceabc01e8", "90ce1aac44299aa2ac6fb44d249d2561", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/22267", "101949dfbed06d417d24a65054e8a6b6", "4ef8df3cf9b00b5c7b2c1b9f4166ec0d", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/23420", "e62a798989e6db00044b079e74faa1eb", "854e58816e6eb2795d14fe81470ad19e", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/29981", "a35f7de61584644d4877aac1380ef090", "3cba30b5e439a6e59b0953d17da9ac6c", "dbfilesclient\\battlepetspeciesstate.db2"}, + {"WoW/31299:wow", "6220549f2b8936af6e63179f6ece78ab", "05627c131969bd9394fb345f4037e249", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/31299:wowt", "959fa63cbcd9ced02a8977ed128df828", "423c1b99b14a615a02d8ffc7a7eff4ef", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, + {"WoW/31299:wow_classic", "184794b8a191429e2aae9b8a5334651b", "b46bd2f81ead285e810e5a049ca2db74", "Sound\\music\\Draenor\\MUS_60_FelWasteland_A.mp3"}, }; static STORAGE_INFO2 StorageInfo2[] = { // {"agent", "us"}, // {"bna", "us"}, -// {"catalogs", NULL}, +// {"catalogs", NULL}, // {"clnt", "us"}, // {"hsb", "us"}, {"wow_classic", "us"}, - {NULL} }; //----------------------------------------------------------------------------- @@ -1115,6 +1124,21 @@ int main(int argc, char * argv[]) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif // defined(_MSC_VER) && defined(_DEBUG) +#ifdef _DEBUG + //PCASC_SOCKET pSocket; + //const char * request; + //char * response; + + //if((pSocket = sockets_connect("level3.blizzard.com", CASC_PORT_HTTP)) != NULL) + //{ + // request = "GET /tpr/wow/data/a3/e6/a3e604a2b89d7a9e0784cbbee57793b4.index HTTP/1.1\r\nHost: level3.blizzard.com\r\nConnection: Keep-Alive\r\n\r\n"; + // response = pSocket->ReadResponse(request); + // if(response != NULL) + // CASC_FREE(response); + + // pSocket->Release(); + //} +#endif // // Run tests for each storage entered on command line @@ -1130,27 +1154,27 @@ int main(int argc, char * argv[]) // // Run the tests for every local storage in my collection // - //for(size_t i = 0; StorageInfo1[i].szPath != NULL; i++) - //{ - // // Attempt to open the storage and extract single file - // dwErrCode = LocalStorage_Test(Storage_ReadFiles, StorageInfo1[i].szPath, StorageInfo1[i].szNameHash, StorageInfo1[i].szDataHash); - // if(dwErrCode != ERROR_SUCCESS && dwErrCode != ERROR_FILE_NOT_FOUND) - // break; - //} - - // - // Run the tests for every available online storage in my collection - // - for (size_t i = 0; StorageInfo2[i].szCodeName != NULL; i++) + for(size_t i = 0; i < _countof(StorageInfo1); i++) { // Attempt to open the storage and extract single file - dwErrCode = OnlineStorage_Test(Storage_EnumFiles, StorageInfo2[i].szCodeName, StorageInfo2[i].szRegion, StorageInfo2[i].szFile); - if (dwErrCode != ERROR_SUCCESS) + dwErrCode = LocalStorage_Test(Storage_ReadFiles, StorageInfo1[i].szPath, StorageInfo1[i].szNameHash, StorageInfo1[i].szDataHash); + if(dwErrCode != ERROR_SUCCESS && dwErrCode != ERROR_FILE_NOT_FOUND) break; } + // + // Run the tests for every available online storage in my collection + // + //for (size_t i = 0; i < _countof(StorageInfo2); i++) + //{ + // // Attempt to open the storage and extract single file + // dwErrCode = OnlineStorage_Test(Storage_EnumFiles, StorageInfo2[i].szCodeName, StorageInfo2[i].szRegion, StorageInfo2[i].szFile); + // if (dwErrCode != ERROR_SUCCESS) + // break; + //} + #ifdef _MSC_VER - _CrtDumpMemoryLeaks(); + //_CrtDumpMemoryLeaks(); #endif // _MSC_VER return (int)dwErrCode; diff --git a/test/TLogHelper.cpp b/test/TLogHelper.cpp index 931c2517..075fe5b8 100644 --- a/test/TLogHelper.cpp +++ b/test/TLogHelper.cpp @@ -106,7 +106,7 @@ class TLogHelper size_t nRemainingWidth; size_t nConsoleWidth = GetConsoleWidth(); size_t nLength = 0; - DWORD dwErrCode = GetLastError(); + DWORD dwErrCode = GetCascError(); // Always start the buffer with '\r' szBufferEnd = szMessage + sizeof(szMessage);