diff options
author | Shauren <shauren.trinity@gmail.com> | 2019-08-10 19:01:24 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2019-08-10 19:01:24 +0200 |
commit | cd720efbfa60f434f420ab66e220eca742c48e45 (patch) | |
tree | 3c960ac5249d0711b71fbfdc62f6c0b665ed85ee | |
parent | 0d6320dfd3932865edb69c8528327b767bd476ef (diff) |
Dep/CascLib: Update to ladislav-zezula/CascLib@b91f87c770c78340dcd96df970e55b5c0469e884
37 files changed, 3318 insertions, 2296 deletions
diff --git a/dep/CascLib/CMakeLists.txt b/dep/CascLib/CMakeLists.txt index 5c3fa7c55e6..cce693a6146 100644 --- a/dep/CascLib/CMakeLists.txt +++ b/dep/CascLib/CMakeLists.txt @@ -9,8 +9,10 @@ set(HEADER_FILES src/common/Directory.h src/common/FileStream.h src/common/FileTree.h + src/common/IndexMap.h src/common/ListFile.h src/common/Map.h + src/common/Path.h src/common/RootHandler.h src/jenkins/lookup.h ) @@ -24,7 +26,6 @@ set(SRC_FILES src/common/ListFile.cpp src/common/RootHandler.cpp src/jenkins/lookup3.c - src/CascCommon.cpp src/CascDecompress.cpp src/CascDecrypt.cpp src/CascDumpData.cpp diff --git a/dep/CascLib/src/CascCommon.cpp b/dep/CascLib/src/CascCommon.cpp deleted file mode 100644 index f5e346ec14c..00000000000 --- a/dep/CascLib/src/CascCommon.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/*****************************************************************************/ -/* CascCommon.cpp Copyright (c) Ladislav Zezula 2014 */ -/*---------------------------------------------------------------------------*/ -/* Common functions for CascLib */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 29.04.14 1.00 Lad The first version of CascCommon.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "CascLib.h" -#include "CascCommon.h" - -//----------------------------------------------------------------------------- -// Functions - -LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData) -{ - LPBYTE pbFileData = NULL; - HANDLE hFile = NULL; - DWORD cbFileData = pcbFileData[0]; - DWORD dwBytesRead = 0; - int nError = ERROR_SUCCESS; - - // Open the file either by CKey or by EKey - if(OpenFileByCKeyEntry(hs, pCKeyEntry, CASC_STRICT_DATA_CHECK, &hFile)) - { - // Retrieve the size of the file. Note that the caller might specify - // the real size of the file, in case the file size is not retrievable - // or if the size is wrong. Example: ENCODING file has size specified in BUILD - if(cbFileData == 0 || cbFileData == CASC_INVALID_SIZE) - { - cbFileData = CascGetFileSize(hFile, NULL); - if(cbFileData == 0 || cbFileData == CASC_INVALID_SIZE) - nError = ERROR_FILE_CORRUPT; - } - - // Retrieve the size of the ENCODING file - if(nError == ERROR_SUCCESS) - { - // Allocate space for the ENCODING file - pbFileData = CASC_ALLOC(BYTE, cbFileData); - if(pbFileData != NULL) - { - // Read the entire file to memory - CascReadFile(hFile, pbFileData, cbFileData, &dwBytesRead); - if(dwBytesRead != cbFileData) - { - nError = ERROR_FILE_CORRUPT; - } - } - else - { - nError = ERROR_NOT_ENOUGH_MEMORY; - } - } - - // Close the file - CascCloseFile(hFile); - } - else - { - nError = GetLastError(); - } - - // Handle errors - if(nError != ERROR_SUCCESS) - { - // Free the file data - CASC_FREE(pbFileData); - cbFileData = 0; - - // Set the last error - SetLastError(nError); - } - - // Give the loaded file length - if(pcbFileData != NULL) - *pcbFileData = cbFileData; - return pbFileData; -} - -LPBYTE LoadFileToMemory(const TCHAR * szFileName, DWORD * pcbFileData) -{ - TFileStream * pStream; - ULONGLONG FileSize = 0; - LPBYTE pbFileData = NULL; - DWORD cbFileData = 0; - - // Open the stream for read-only access and read the file - // Note that this fails when the game is running (sharing violation). - pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); - if(pStream != NULL) - { - // Retrieve the file size - FileStream_GetSize(pStream, &FileSize); - cbFileData = (DWORD)FileSize; - - // Do not load zero files or too larget files - if(0 < FileSize && FileSize <= 0x2000000) - { - // Allocate file data buffer. Make it 1 byte longer - // so string functions can put '\0' there - pbFileData = CASC_ALLOC(BYTE, cbFileData + 1); - if(pbFileData != NULL) - { - if(!FileStream_Read(pStream, NULL, pbFileData, cbFileData)) - { - CASC_FREE(pbFileData); - cbFileData = 0; - } - } - else - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - cbFileData = 0; - } - } - else - { - SetLastError(ERROR_BAD_FORMAT); - cbFileData = 0; - assert(false); - } - - // Close the file stream - FileStream_Close(pStream); - } - - // Give out values - if(pcbFileData != NULL) - pcbFileData[0] = cbFileData; - return pbFileData; -} - -void FreeCascBlob(PQUERY_KEY pBlob) -{ - if(pBlob != NULL) - { - CASC_FREE(pBlob->pbData); - pBlob->cbData = 0; - } -} diff --git a/dep/CascLib/src/CascCommon.h b/dep/CascLib/src/CascCommon.h index b1248544b9f..a0caa5d85eb 100644 --- a/dep/CascLib/src/CascCommon.h +++ b/dep/CascLib/src/CascCommon.h @@ -30,6 +30,7 @@ #include "common/Directory.h" #include "common/ListFile.h" #include "common/Csv.h" +#include "common/Path.h" #include "common/RootHandler.h" // Headers from Alexander Peslyak's MD5 implementation @@ -52,6 +53,14 @@ #define BREAKIF(condition) { /* NOTHING */ } #endif +#define CASC_MAGIC_STORAGE 0x524F545343534143 // 'CASCSTOR' +#define CASC_MAGIC_FILE 0x454C494643534143 // 'CASCFILE' +#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 + //----------------------------------------------------------------------------- // In-memory structures @@ -63,6 +72,13 @@ typedef enum _CBLD_TYPE CascVersionsDb // versions (downloaded online) } CBLD_TYPE, *PCBLD_TYPE; +typedef enum _CSTRTG +{ + CascCacheInvalid, // Do not cache anything. Used as invalid value + CascCacheNothing, // Do not cache anything. Used on internal files, where the content is loaded directly to the user buffer + CascCacheLastFrame, // Only cache one file frame +} CSTRTG, *PCSTRTG; + // Tag file entry, loaded from the DOWNLOAD file typedef struct _CASC_TAG_ENTRY { @@ -97,7 +113,7 @@ typedef struct _CASC_ARCINDEX_FOOTER BYTE Version; // Version of the index footer BYTE OffsetBytes; // Length, in bytes, of the file offset field BYTE SizeBytes; // Length, in bytes, of the file size field - BYTE EKeyBytes; // Length, in bytes, of the EKey field + BYTE EKeyLength; // Length, in bytes, of the EKey field BYTE FooterHashBytes; // Length, in bytes, of the hash and checksum BYTE Alignment[3]; size_t ElementCount; // total number of elements in the index file @@ -107,15 +123,6 @@ typedef struct _CASC_ARCINDEX_FOOTER } CASC_ARCINDEX_FOOTER, *PCASC_ARCINDEX_FOOTER; -// Normalized structure for archive index entry -typedef struct _CASC_ARCINDEX_ENTRY -{ - BYTE IndexHash[MD5_HASH_SIZE]; - BYTE EKey[MD5_HASH_SIZE]; - DWORD ArchiveOffset; - DWORD EncodedSize; -} CASC_ARCINDEX_ENTRY, *PCASC_ARCINDEX_ENTRY; - // Normalized header of the ENCODING file typedef struct _CASC_ENCODING_HEADER { @@ -188,12 +195,51 @@ typedef struct _CASC_INSTALL_HEADER typedef struct _CASC_FILE_FRAME { CONTENT_KEY FrameHash; // MD5 hash of the file frame + ULONGLONG StartOffset; // Starting offset of the file span + ULONGLONG EndOffset; // Ending offset of the file span DWORD DataFileOffset; // Offset in the data file (data.###) - DWORD FileOffset; // File offset of this frame DWORD EncodedSize; // Encoded size of the frame DWORD ContentSize; // Content size of the frame } CASC_FILE_FRAME, *PCASC_FILE_FRAME; +typedef struct _CASC_FILE_SPAN +{ + ULONGLONG StartOffset; // Starting offset of the file span + ULONGLONG EndOffset; // Ending offset of the file span + PCASC_FILE_FRAME pFrames; + TFileStream * pStream; // [Opened] stream for the file span + DWORD ArchiveIndex; // Index of the archive + DWORD ArchiveOffs; // Offset in the archive + DWORD HeaderSize; // Size of encoded frame headers + DWORD FrameCount; // Number of frames in this span + +} CASC_FILE_SPAN, *PCASC_FILE_SPAN; + +// Structure for downloading a file from the CDN (https://wowdev.wiki/TACT#File_types) +// Remote path is combined as the following: +// [szCdnsHost] /[szCdnsPath]/[szPathType]/EKey[0-1]/EKey[2-3]/[EKey].[Extension] +// level3.blizzard.com/tpr/bnt001 /data /fe /3d /fe3d7cf9d04e07066de32bd95a5c2627.index +typedef struct _CASC_CDN_DOWNLOAD +{ + ULONGLONG ArchiveOffs; // Archive offset (if pbArchiveKey != NULL) + LPCTSTR szCdnsHost; // Address of the remote CDN server. ("level3.blizzard.com") + // If NULL, the downloader will try all CDN servers from the storage + LPCTSTR szCdnsPath; // Remote CDN path ("tpr/bnt001") + LPCTSTR szPathType; // Path type ("config", "data", "patch") + LPCTSTR szLoPaType; // Local path type ("config", "data", "patch"). If NULL, it's gonna be the same like szPathType, If "", then it's not used + LPCTSTR szFileName; // Plain file name, without path and extension + LPBYTE pbArchiveKey; // If non-NULL, then the file is present in the archive. + LPBYTE pbEKey; // 16-byte EKey of the file of of the archive + LPCTSTR szExtension; // Extension for the file. Can be NULL. + + LPTSTR szLocalPath; // Pointer to the variable that, upon success, reveives the local path where the file was downloaded + size_t ccLocalPath; // Maximum length of szLocalPath, in TCHARs + DWORD ArchiveIndex; // Index of the archive (if pbArchiveKey != NULL) + DWORD EncodedSize; // Encoded length (if pbArchiveKey != NULL) + DWORD Flags; // See CASC_CDN_FLAG_XXX + +} CASC_CDN_DOWNLOAD, *PCASC_CDN_DOWNLOAD; + //----------------------------------------------------------------------------- // Structures for CASC storage and CASC file @@ -201,16 +247,26 @@ struct TCascStorage { TCascStorage(); ~TCascStorage(); + TCascStorage * AddRef(); TCascStorage * Release(); - static TCascStorage * IsValid(HANDLE hStorage); + static TCascStorage * IsValid(HANDLE hStorage) + { + TCascStorage * hs = (TCascStorage *)hStorage; + + return (hs != INVALID_HANDLE_VALUE && + hs != NULL && + hs->ClassName == CASC_MAGIC_STORAGE) ? hs : NULL; + } + + // Class recognizer. Has constant value of 'CASCSTOR' (CASC_MAGIC_STORAGE) + ULONGLONG ClassName; + // Class members PCASC_OPEN_STORAGE_ARGS pArgs; // Open storage arguments. Only valid during opening the storage LPCTSTR szIndexFormat; // Format of the index file name - LPCSTR szClassName; // "TCascStorage" - LPCSTR szProductName; // String representation of the product name LPTSTR szCodeName; // On local storage, this select a product in a multi-product storage. For online storage, this selects a product LPTSTR szRootPath; // Path where the build file is LPTSTR szDataPath; // This is the directory where data files are @@ -219,12 +275,10 @@ struct TCascStorage LPTSTR szCdnServers; // Multi-SZ list of CDN servers LPTSTR szCdnPath; // Remote CDN sub path for the product LPSTR szRegion; // Product region. Only when "versions" is used as storage root file - CASC_PRODUCT Product; // Product enum value (see CASC_PRODUCT) + DWORD dwDefaultLocale; // Default locale, read from ".build.info" DWORD dwBuildNumber; // Product build number DWORD dwRefCount; // Number of references - DWORD dwDefaultLocale; // Default locale, read from ".build.info" DWORD dwFeatures; // List of CASC features. See CASC_FEATURE_XXX - bool bAllowOrphans; // If TRUE, then orphaned items are allowed CBLD_TYPE BuildFileType; // Type of the build file @@ -249,20 +303,17 @@ struct TCascStorage CASC_ARRAY VfsRootList; // List of CASC_EKEY_ENTRY for each TVFS sub-root TRootHandler * pRootHandler; // Common handler for various ROOT file formats - CASC_ARRAY ArcIndexArray; // Array of CASC_ARCINDEX_ENTRY, loaded from archive indexes - CASC_ARRAY IndexArray; // Array of CASC_CKEY_ENTRY loaded from local index files - CASC_ARRAY CKeyArray; // Array of CASC_CKEY_ENTRY, one entry for each physical file + CASC_ARRAY IndexArray; // Array of CASC_EKEY_ENTRY, loaded from online indexes + CASC_ARRAY CKeyArray; // Array of CASC_CKEY_ENTRY, loaded from ENCODING file CASC_ARRAY TagsArray; // Array of CASC_DOWNLOAD_TAG2 - CASC_MAP ArcIndexMap; // Map of EKey -> CASC_ARCINDEX_ENTRY - CASC_MAP CKeyMap; // Map of CKey -> CASC_CKEY_ENTRY - CASC_MAP EKeyMap; // Map of EKey -> CASC_EKEY_ENTRY + CASC_MAP IndexMap; // Map of EKey -> IndexArray (for online archives) + CASC_MAP CKeyMap; // Map of CKey -> CKeyArray + CASC_MAP EKeyMap; // Map of EKey -> CKeyArray size_t LocalFiles; // Number of files that are present locally size_t TotalFiles; // Total number of files in the storage, some may not be present locally size_t EKeyEntries; // Number of CKeyEntry-ies loaded from text build file - size_t OrphanItems; // Number of EKey entries in indexes that do not have their counterpart in ENCODING - size_t SkippedItems; // Number of EKey entries in indexes that were ignored due to insufficient capacity of CKeyArray size_t EKeyLength; // EKey length from the index files - DWORD FileOffsetBits; // Nimber of bits in the storage offset which mean data segent offset + DWORD FileOffsetBits; // Number of bits in the storage offset which mean data segent offset CASC_ARRAY ExtraKeysList; // List additional encryption keys CASC_MAP EncryptionKeys; // Map of encryption keys @@ -271,49 +322,12 @@ struct TCascStorage struct TCascFile { - TCascFile(TCascStorage * ahs, PCASC_CKEY_ENTRY apCKeyEntry) - { - ULONGLONG StorageOffset; - ULONGLONG FileOffsMask; - - // Reference the storage handle - hs = ahs->AddRef(); - szClassName = "TCascFile"; - - // Init class variables - pCKeyEntry = apCKeyEntry; - pFrames = NULL; - pbFileCache = NULL; - cbFileCache = 0; - - // Supply the sizes - StorageOffset = pCKeyEntry->StorageOffset; - FileOffsMask = ((ULONGLONG)1 << hs->FileOffsetBits) - 1; - ArchiveIndex = (DWORD)(StorageOffset >> hs->FileOffsetBits); - ArchiveOffset = (DWORD)(StorageOffset & FileOffsMask); - EncodedSize = pCKeyEntry->EncodedSize; - ContentSize = pCKeyEntry->ContentSize; - FilePointer = 0; - FrameCount = 0; - bVerifyIntegrity = false; - bDownloadFileIf = false; - bLocalFileStream = false; - } - - ~TCascFile() - { - // Close (dereference) the archive handle - hs = hs->Release(); - szClassName = NULL; + TCascFile(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry); + ~TCascFile(); - // Free the file cache and frame array - CASC_FREE(pbFileCache); - CASC_FREE(pFrames); - - // If we are supposed to close the file stream, do it - if (pStream && bLocalFileStream) - FileStream_Close(pStream); - } + DWORD OpenFileSpans(LPCTSTR szSpanList); + void InitFileSpans(PCASC_FILE_SPAN pSpans, DWORD dwSpanCount); + void InitCacheStrategy(); static TCascFile * IsValid(HANDLE hFile) { @@ -321,31 +335,32 @@ struct TCascFile return (hf != INVALID_HANDLE_VALUE && hf != NULL && - hf->hs != NULL && - hf->szClassName != NULL && + hf->ClassName == CASC_MAGIC_FILE && hf->pCKeyEntry != NULL) ? hf : NULL; } + // Class recognizer. Has constant value of 'CASCFILE' (CASC_MAGIC_FILE) + ULONGLONG ClassName; + + // Class members TCascStorage * hs; // Pointer to storage structure - TFileStream * pStream; // An open data stream - const char * szClassName; // "TCascFile" - - PCASC_CKEY_ENTRY pCKeyEntry; - PCASC_FILE_FRAME pFrames; // Array of file frames - DWORD ArchiveIndex; // Index of the archive (data.###) - DWORD ArchiveOffset; // Offset in the archive (data.###) - DWORD FilePointer; // Current file pointer - DWORD EncodedSize; // Encoded size. This is the size of encoded header, all file frame headers and all file frames - DWORD ContentSize; // Content size. This is the size of the file content, aka the file size - DWORD FrameCount; // Number of the file frames + + PCASC_CKEY_ENTRY pCKeyEntry; // Pointer to the first CKey entry. Each entry describes one file span + PCASC_FILE_SPAN pFileSpan; // Pointer to the first file span entry + ULONGLONG ContentSize; // Content size. This is the summed content size of all file spans + ULONGLONG EncodedSize; // Encoded size. This is the summed encoded size of all file spans + ULONGLONG FilePointer; // Current file pointer + DWORD SpanCount; // Number of file spans. There is one CKey entry for each file span 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 bLocalFileStream:1; // If true, then the file stream is a local file + 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 bFreeCKeyEntries:1; // If true, dectructor will free the array of CKey entries - LPBYTE pbFileCache; // Pointer to file cache - DWORD cbFileCache; // Size of the file cache - + ULONGLONG FileCacheStart; // Starting offset of the file cached area + ULONGLONG FileCacheEnd; // Ending offset of the file cached area + LPBYTE pbFileCache; // Pointer to file cached area + CSTRTG CacheStrategy; // Caching strategy. See CSTRTG enum for more info }; struct TCascSearch @@ -353,7 +368,7 @@ struct TCascSearch TCascSearch(TCascStorage * ahs, LPCTSTR aszListFile, const char * aszMask) { // Init the class - szClassName = "TCascSearch"; + ClassName = CASC_MAGIC_FIND; hs = ahs->AddRef(); // Init provider-specific data @@ -371,7 +386,7 @@ struct TCascSearch { // Dereference the CASC storage hs = hs->Release(); - szClassName = NULL; + ClassName = 0; // Free the rest of the members CASC_FREE(szMask); @@ -385,14 +400,14 @@ struct TCascSearch return (hFind != INVALID_HANDLE_VALUE && hFind != NULL && - pSearch->szClassName != NULL && - !strcmp(pSearch->szClassName, "TCascSearch") && + pSearch->ClassName == CASC_MAGIC_FIND && pSearch->szMask != NULL) ? pSearch : NULL; } + ULONGLONG ClassName; // Contains 'CASCFIND' + TCascStorage * hs; // Pointer to the storage handle - const char * szClassName; // Contains "TCascSearch" - TCHAR * szListFile; // Name of the listfile + LPTSTR szListFile; // Name of the listfile void * pCache; // Listfile cache char * szMask; // Search mask @@ -405,22 +420,32 @@ struct TCascSearch //----------------------------------------------------------------------------- // Common functions (CascCommon.cpp) -bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD dwOpenFlags, HANDLE * PtrFileHandle); -LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData); -LPBYTE LoadFileToMemory(const TCHAR * szFileName, DWORD * pcbFileData); -void FreeCascBlob(PQUERY_KEY pQueryKey); +inline void FreeCascBlob(PQUERY_KEY pBlob) +{ + if(pBlob != NULL) + { + CASC_FREE(pBlob->pbData); + pBlob->cbData = 0; + } +} //----------------------------------------------------------------------------- // Text file parsing (CascFiles.cpp) bool InvokeProgressCallback(TCascStorage * hs, LPCSTR szMessage, LPCSTR szObject, DWORD CurrentValue, DWORD TotalValue); -DWORD DownloadFileFromCDN(TCascStorage * hs, const TCHAR * szSubDir, LPBYTE pbEKey, const TCHAR * szExtension, TCHAR * szOutLocalPath, size_t cchOutLocalPath); -DWORD CheckGameDirectory(TCascStorage * hs, TCHAR * szDirectory); -DWORD LoadCdnsInfo(TCascStorage * hs); +DWORD GetFileSpanInfo(PCASC_CKEY_ENTRY pCKeyEntry, PULONGLONG PtrContentSize, PULONGLONG PtrEncodedSize = NULL); +DWORD DownloadFileFromCDN(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo); +DWORD CheckGameDirectory(TCascStorage * hs, LPTSTR szDirectory); +DWORD LoadCdnsFile(TCascStorage * hs); DWORD LoadBuildInfo(TCascStorage * hs); DWORD LoadCdnConfigFile(TCascStorage * hs); DWORD LoadCdnBuildFile(TCascStorage * hs); +LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData); +LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData); +bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD dwOpenFlags, HANDLE * PtrFileHandle); +bool SetCacheStrategy(HANDLE hFile, CSTRTG CacheStrategy); + //----------------------------------------------------------------------------- // Internal file functions @@ -431,27 +456,27 @@ PCASC_CKEY_ENTRY FindCKeyEntry_EKey(TCascStorage * hs, LPBYTE pbEKey, PDWORD Ptr size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount); -int CascDecompress(LPBYTE pvOutBuffer, PDWORD pcbOutBuffer, LPBYTE pvInBuffer, DWORD cbInBuffer); -int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer); +DWORD CascDecompress(LPBYTE pvOutBuffer, PDWORD pcbOutBuffer, LPBYTE pvInBuffer, DWORD cbInBuffer); +DWORD CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer); -int CascLoadEncryptionKeys(TCascStorage * hs); -int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex); +DWORD CascLoadEncryptionKeys(TCascStorage * hs); +DWORD CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex); //----------------------------------------------------------------------------- // Support for index files -int LoadIndexFiles(TCascStorage * hs); +DWORD LoadIndexFiles(TCascStorage * hs); //----------------------------------------------------------------------------- // Support for ROOT file -int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); -int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); -int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); -int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask); -int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); -int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); -int RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +DWORD RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +DWORD RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +DWORD RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +DWORD RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask); +DWORD RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +DWORD RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); +DWORD RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile); //----------------------------------------------------------------------------- // Dumpers (CascDumpData.cpp) diff --git a/dep/CascLib/src/CascDecompress.cpp b/dep/CascLib/src/CascDecompress.cpp index 3262c59ddf7..42c4758eb79 100644 --- a/dep/CascLib/src/CascDecompress.cpp +++ b/dep/CascLib/src/CascDecompress.cpp @@ -15,12 +15,12 @@ //----------------------------------------------------------------------------- // Public functions -int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer) +DWORD CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer) { z_stream z; // Stream information for zlib + DWORD dwErrCode = ERROR_FILE_CORRUPT; uInt cbOutBuffer = *pcbOutBuffer; int nResult; - int nError = ERROR_FILE_CORRUPT; // Fill the stream structure for zlib z.next_in = pbInBuffer; @@ -44,7 +44,7 @@ int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, D { // Give the size of the uncompressed data cbOutBuffer = z.total_out; - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } inflateEnd(&z); @@ -52,5 +52,5 @@ int CascDecompress(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, D // Give the caller the number of bytes needed pcbOutBuffer[0] = cbOutBuffer; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascDecrypt.cpp b/dep/CascLib/src/CascDecrypt.cpp index c5d491da2de..1e07342d9c5 100644 --- a/dep/CascLib/src/CascDecrypt.cpp +++ b/dep/CascLib/src/CascDecrypt.cpp @@ -16,16 +16,17 @@ // Local structures #define CASC_EXTRA_KEYS 0x80 +#define CASC_KEY_LENGTH 0x10 typedef struct _CASC_ENCRYPTION_KEY { ULONGLONG KeyName; // "Name" of the key - BYTE Key[0x10]; // The key itself + BYTE Key[CASC_KEY_LENGTH]; // The key itself } CASC_ENCRYPTION_KEY, *PCASC_ENCRYPTION_KEY; typedef struct _CASC_SALSA20 { - DWORD Key[0x10]; + DWORD Key[CASC_KEY_LENGTH]; DWORD dwRounds; } CASC_SALSA20, *PCASC_SALSA20; @@ -57,15 +58,15 @@ static CASC_ENCRYPTION_KEY CascKeys[] = { 0xDEE3A0521EFF6F03ULL, { 0xAD, 0x74, 0x0C, 0xE3, 0xFF, 0xFF, 0x92, 0x31, 0x46, 0x81, 0x26, 0x98, 0x57, 0x08, 0xE1, 0xB9 } }, // 0.8.0.24919_retailx64 (streamed from server) { 0x8C9106108AA84F07ULL, { 0x53, 0xD8, 0x59, 0xDD, 0xA2, 0x63, 0x5A, 0x38, 0xDC, 0x32, 0xE7, 0x2B, 0x11, 0xB3, 0x2F, 0x29 } }, // 0.8.0.24919_retailx64 (streamed from server) { 0x49166D358A34D815ULL, { 0x66, 0x78, 0x68, 0xCD, 0x94, 0xEA, 0x01, 0x35, 0xB9, 0xB1, 0x6C, 0x93, 0xB1, 0x12, 0x4A, 0xBA } }, // 0.8.0.24919_retailx64 (streamed from server) - { 0x1463A87356778D14ULL, { 0x69, 0xBD, 0x2A, 0x78, 0xD0, 0x5C, 0x50, 0x3E, 0x93, 0x99, 0x49, 0x59, 0xB3, 0x0E, 0x5A, 0xEC } }, // ? 1.0.3.0 (streamed from server) - { 0x5E152DE44DFBEE01ULL, { 0xE4, 0x5A, 0x17, 0x93, 0xB3, 0x7E, 0xE3, 0x1A, 0x8E, 0xB8, 0x5C, 0xEE, 0x0E, 0xEE, 0x1B, 0x68 } }, // ? 1.0.3.0 (streamed from server) + { 0x1463A87356778D14ULL, { 0x69, 0xBD, 0x2A, 0x78, 0xD0, 0x5C, 0x50, 0x3E, 0x93, 0x99, 0x49, 0x59, 0xB3, 0x0E, 0x5A, 0xEC } }, // ? 1.0.3.0 (streamed from server) + { 0x5E152DE44DFBEE01ULL, { 0xE4, 0x5A, 0x17, 0x93, 0xB3, 0x7E, 0xE3, 0x1A, 0x8E, 0xB8, 0x5C, 0xEE, 0x0E, 0xEE, 0x1B, 0x68 } }, // ? 1.0.3.0 (streamed from server) { 0x9B1F39EE592CA415ULL, { 0x54, 0xA9, 0x9F, 0x08, 0x1C, 0xAD, 0x0D, 0x08, 0xF7, 0xE3, 0x36, 0xF4, 0x36, 0x8E, 0x89, 0x4C } }, // ? 1.0.3.0 (streamed from server) { 0x24C8B75890AD5917ULL, { 0x31, 0x10, 0x0C, 0x00, 0xFD, 0xE0, 0xCE, 0x18, 0xBB, 0xB3, 0x3F, 0x3A, 0xC1, 0x5B, 0x30, 0x9F } }, // ? 1.0.3.0 (included in game) { 0xEA658B75FDD4890FULL, { 0xDE, 0xC7, 0xA4, 0xE7, 0x21, 0xF4, 0x25, 0xD1, 0x33, 0x03, 0x98, 0x95, 0xC3, 0x60, 0x36, 0xF8 } }, // ? 1.0.3.0 (included in game) { 0x026FDCDF8C5C7105ULL, { 0x8F, 0x41, 0x80, 0x9D, 0xA5, 0x53, 0x66, 0xAD, 0x41, 0x6D, 0x3C, 0x33, 0x74, 0x59, 0xEE, 0xE3 } }, // (included in game) { 0xCAE3FAC925F20402ULL, { 0x98, 0xB7, 0x8E, 0x87, 0x74, 0xBF, 0x27, 0x50, 0x93, 0xCB, 0x1B, 0x5F, 0xC7, 0x14, 0x51, 0x1B } }, // (included in game) - { 0x061581CA8496C80CULL, { 0xDA, 0x2E, 0xF5, 0x05, 0x2D, 0xB9, 0x17, 0x38, 0x0B, 0x8A, 0xA6, 0xEF, 0x7A, 0x5F, 0x8E, 0x6A } }, // - { 0xBE2CB0FAD3698123ULL, { 0x90, 0x2A, 0x12, 0x85, 0x83, 0x6C, 0xE6, 0xDA, 0x58, 0x95, 0x02, 0x0D, 0xD6, 0x03, 0xB0, 0x65 } }, // + { 0x061581CA8496C80CULL, { 0xDA, 0x2E, 0xF5, 0x05, 0x2D, 0xB9, 0x17, 0x38, 0x0B, 0x8A, 0xA6, 0xEF, 0x7A, 0x5F, 0x8E, 0x6A } }, // + { 0xBE2CB0FAD3698123ULL, { 0x90, 0x2A, 0x12, 0x85, 0x83, 0x6C, 0xE6, 0xDA, 0x58, 0x95, 0x02, 0x0D, 0xD6, 0x03, 0xB0, 0x65 } }, // { 0x57A5A33B226B8E0AULL, { 0xFD, 0xFC, 0x35, 0xC9, 0x9B, 0x9D, 0xB1, 0x1A, 0x32, 0x62, 0x60, 0xCA, 0x24, 0x6A, 0xCB, 0x41 } }, // 1.1.0.0.30200 Ana { 0x42B9AB1AF5015920ULL, { 0xC6, 0x87, 0x78, 0x82, 0x3C, 0x96, 0x4C, 0x6F, 0x24, 0x7A, 0xCC, 0x0F, 0x4A, 0x25, 0x84, 0xF8 } }, // 1.2.0.1.30684 Summer Games { 0x4F0FE18E9FA1AC1AULL, { 0x89, 0x38, 0x1C, 0x74, 0x8F, 0x65, 0x31, 0xBB, 0xFC, 0xD9, 0x77, 0x53, 0xD0, 0x6C, 0xC3, 0xCD } }, // 1.2.0.1.30684 @@ -78,7 +79,7 @@ static CASC_ENCRYPTION_KEY CascKeys[] = { 0x90CA73B2CDE3164BULL, { 0x5C, 0xBF, 0xF1, 0x1F, 0x22, 0x72, 0x0B, 0xAC, 0xC2, 0xAE, 0x6A, 0xAD, 0x8F, 0xE5, 0x33, 0x17 } }, // 1.6.1.0.33236 Oasis map { 0x6DD3212FB942714AULL, { 0xE0, 0x2C, 0x16, 0x43, 0x60, 0x2E, 0xC1, 0x6C, 0x3A, 0xE2, 0xA4, 0xD2, 0x54, 0xA0, 0x8F, 0xD9 } }, // 1.6.1.0.33236 { 0x11DDB470ABCBA130ULL, { 0x66, 0x19, 0x87, 0x66, 0xB1, 0xC4, 0xAF, 0x75, 0x89, 0xEF, 0xD1, 0x3A, 0xD4, 0xDD, 0x66, 0x7A } }, // 1.6.1.0.33236 Winter Wonderland - { 0x5BEF27EEE95E0B4BULL, { 0x36, 0xBC, 0xD2, 0xB5, 0x51, 0xFF, 0x1C, 0x84, 0xAA, 0x3A, 0x39, 0x94, 0xCC, 0xEB, 0x03, 0x3E } }, // + { 0x5BEF27EEE95E0B4BULL, { 0x36, 0xBC, 0xD2, 0xB5, 0x51, 0xFF, 0x1C, 0x84, 0xAA, 0x3A, 0x39, 0x94, 0xCC, 0xEB, 0x03, 0x3E } }, // { 0x9359B46E49D2DA42ULL, { 0x17, 0x3D, 0x65, 0xE7, 0xFC, 0xAE, 0x29, 0x8A, 0x93, 0x63, 0xBD, 0x6A, 0xA1, 0x89, 0xF2, 0x00 } }, // Diablo's 20th anniversary { 0x1A46302EF8896F34ULL, { 0x80, 0x29, 0xAD, 0x54, 0x51, 0xD4, 0xBC, 0x18, 0xE9, 0xD0, 0xF5, 0xAC, 0x44, 0x9D, 0xC0, 0x55 } }, // 1.7.0.2.34156 Year of the Rooster { 0x693529F7D40A064CULL, { 0xCE, 0x54, 0x87, 0x3C, 0x62, 0xDA, 0xA4, 0x8E, 0xFF, 0x27, 0xFC, 0xC0, 0x32, 0xBD, 0x07, 0xE3 } }, // 1.8.0.0.34470 CTF Maps @@ -86,8 +87,8 @@ static CASC_ENCRYPTION_KEY CascKeys[] = { 0xE218F69AAC6C104DULL, { 0xF4, 0x3D, 0x12, 0xC9, 0x4A, 0x9A, 0x52, 0x84, 0x97, 0x97, 0x1F, 0x1C, 0xBE, 0x41, 0xAD, 0x4D } }, // 1.9.0.0.34986 Orisa { 0xF432F0425363F250ULL, { 0xBA, 0x69, 0xF2, 0xB3, 0x3C, 0x27, 0x68, 0xF5, 0xF2, 0x9B, 0xFE, 0x78, 0xA5, 0xA1, 0xFA, 0xD5 } }, // 1.10.0.0.35455 Uprising { 0x061D52F86830B35DULL, { 0xD7, 0x79, 0xF9, 0xC6, 0xCC, 0x9A, 0x4B, 0xE1, 0x03, 0xA4, 0xE9, 0x0A, 0x73, 0x38, 0xF7, 0x93 } }, // 1.10.0.0.35455 D.Va Officer Skin (HotS Nexus Challenge 2) - { 0x1275C84CF113EF65ULL, { 0xCF, 0x58, 0xB6, 0x93, 0x3E, 0xAF, 0x98, 0xAF, 0x53, 0xE7, 0x6F, 0x84, 0x26, 0xCC, 0x7E, 0x6C } }, // - { 0xD9C7C7AC0F14C868ULL, { 0x3A, 0xFD, 0xF6, 0x8E, 0x3A, 0x5D, 0x63, 0xBA, 0xBA, 0x1E, 0x68, 0x21, 0x88, 0x3F, 0x06, 0x7D } }, // + { 0x1275C84CF113EF65ULL, { 0xCF, 0x58, 0xB6, 0x93, 0x3E, 0xAF, 0x98, 0xAF, 0x53, 0xE7, 0x6F, 0x84, 0x26, 0xCC, 0x7E, 0x6C } }, // + { 0xD9C7C7AC0F14C868ULL, { 0x3A, 0xFD, 0xF6, 0x8E, 0x3A, 0x5D, 0x63, 0xBA, 0xBA, 0x1E, 0x68, 0x21, 0x88, 0x3F, 0x06, 0x7D } }, // { 0xBD4E42661A432951ULL, { 0x6D, 0xE8, 0xE2, 0x8C, 0x85, 0x11, 0x64, 0x4D, 0x55, 0x95, 0xFC, 0x45, 0xE5, 0x35, 0x14, 0x72 } }, // 1.11.0.0.36376 Anniversary event { 0xC43CB14355249451ULL, { 0x0E, 0xA2, 0xB4, 0x4F, 0x96, 0xA2, 0x69, 0xA3, 0x86, 0x85, 0x6D, 0x04, 0x9A, 0x3D, 0xEC, 0x86 } }, // 1.12.0.0.37104 Horizon Lunar Colony { 0xE6D914F8E4744953ULL, { 0xC8, 0x47, 0x7C, 0x28, 0x9D, 0xCE, 0x66, 0xD9, 0x13, 0x65, 0x07, 0xA3, 0x3A, 0xA3, 0x33, 0x01 } }, // 1.13.0.0.37646 Doomfist @@ -132,12 +133,12 @@ static CASC_ENCRYPTION_KEY CascKeys[] = { 0xFEBBF66DAEF6C9BEULL, { 0x4A, 0x22, 0x0A, 0xE3, 0xA6, 0x80, 0x8E, 0xD2, 0x69, 0x74, 0x10, 0xC9, 0x4C, 0x1C, 0xE9, 0x70 } }, // 1.30.0.1.????? Ashe { 0x2AD4834DAB3986ABULL, { 0xEA, 0x69, 0x71, 0xF7, 0x8A, 0xBE, 0xBD, 0x2A, 0xF8, 0x83, 0xD4, 0x6A, 0x54, 0x86, 0xF3, 0x9F } }, // 1.31.?.?.????? Winter 2018 { 0xD89B24D62F00A04EULL, { 0xC3, 0xA4, 0xD0, 0x10, 0xAA, 0xA7, 0x28, 0x7A, 0x3E, 0xAC, 0xCD, 0x45, 0xED, 0x47, 0x13, 0x45 } }, // 1.31.?.?.????? Bastet Challenge - { 0x32DDC40236DAEA7BULL, { 0x2D, 0xBD, 0xE4, 0xFB, 0x9F, 0xDA, 0x77, 0x6A, 0xA2, 0x94, 0x87, 0x08, 0x54, 0xFE, 0x9B, 0x02 } }, // 1.31.?.?.????? Lunar New Year 2019 (Pig) - { 0xF481EFC2302EE415ULL, { 0x37, 0xA7, 0xF2, 0xB8, 0x7D, 0x0B, 0x8B, 0x70, 0x04, 0x56, 0xC6, 0xA9, 0x2C, 0x71, 0xD6, 0xDD } }, // 1.33.?.?.????? Paris Map - { 0xD1AC8C1903524D9AULL, { 0xD7, 0x81, 0xD0, 0xAA, 0x35, 0xE5, 0xC1, 0x06, 0xBC, 0xA7, 0xCF, 0x01, 0xDE, 0xBD, 0x14, 0x94 } }, // 1.34.0.1.55918 Baptiste + { 0x32DDC40236DAEA7BULL, { 0x2D, 0xBD, 0xE4, 0xFB, 0x9F, 0xDA, 0x77, 0x6A, 0xA2, 0x94, 0x87, 0x08, 0x54, 0xFE, 0x9B, 0x02 } }, // 1.31.?.?.????? Lunar New Year 2019 (Pig) + { 0xF481EFC2302EE415ULL, { 0x37, 0xA7, 0xF2, 0xB8, 0x7D, 0x0B, 0x8B, 0x70, 0x04, 0x56, 0xC6, 0xA9, 0x2C, 0x71, 0xD6, 0xDD } }, // 1.33.?.?.????? Paris Map + { 0xD1AC8C1903524D9AULL, { 0xD7, 0x81, 0xD0, 0xAA, 0x35, 0xE5, 0xC1, 0x06, 0xBC, 0xA7, 0xCF, 0x01, 0xDE, 0xBD, 0x14, 0x94 } }, // 1.34.0.1.55918 Baptiste { 0x71EEBE93590AA903ULL, { 0x0C, 0xCD, 0x10, 0xD4, 0x55, 0x3E, 0xEC, 0x7E, 0x97, 0xFD, 0x36, 0xA9, 0xE8, 0xAD, 0xF0, 0xFF } }, // 1.34.0.1.55918 Baptiste Cosmetics - // Streamed WoW keys + // Streamed WoW keys { 0xFA505078126ACB3EULL, { 0xBD, 0xC5, 0x18, 0x62, 0xAB, 0xED, 0x79, 0xB2, 0xDE, 0x48, 0xC8, 0xE7, 0xE6, 0x6C, 0x62, 0x00 } }, // 15 WOW-20740patch7.0.1_Beta <not used between 7.0 and 7.3> { 0xFF813F7D062AC0BCULL, { 0xAA, 0x0B, 0x5C, 0x77, 0xF0, 0x88, 0xCC, 0xC2, 0xD3, 0x90, 0x49, 0xBD, 0x26, 0x7F, 0x06, 0x6D } }, // 25 WOW-20740patch7.0.1_Beta <not used between 7.0 and 7.3> { 0xD1E9B5EDF9283668ULL, { 0x8E, 0x4A, 0x25, 0x79, 0x89, 0x4E, 0x38, 0xB4, 0xAB, 0x90, 0x58, 0xBA, 0x5C, 0x73, 0x28, 0xEE } }, // 39 WOW-20740patch7.0.1_Beta Enchanted Torch pet @@ -217,7 +218,7 @@ static CASC_ENCRYPTION_KEY CascKeys[] = { 0xE2F6BD41298A2AB9ULL, { 0xC5, 0xDC, 0x1B, 0xB4, 0x3B, 0x8C, 0xF3, 0xF0, 0x85, 0xD6, 0x98, 0x68, 0x26, 0xB9, 0x28, 0xEC } }, // 241 WOW-28151patch8.1.0_PTR Horde fireworks { 0x14C4257E557B49A1ULL, { 0x06, 0x4A, 0x97, 0x09, 0xF4, 0x2D, 0x50, 0xCB, 0x5F, 0x8B, 0x94, 0xBC, 0x1A, 0xCF, 0xDD, 0x5D } }, // 242 WOW-28440patch8.1.0_PTR dor cinematic { 0x1254E65319C6EEFFULL, { 0x79, 0xD2, 0xB3, 0xD1, 0xCC, 0xB0, 0x15, 0x47, 0x4E, 0x71, 0x58, 0x81, 0x38, 0x64, 0xB8, 0xE6 } }, // 243 WOW-28440patch8.1.0_PTR akt cinematic -// { 0xC8753773ADF1174CULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 244 WOW-28938patch8.1.5_PTR starts at fdid 2615771, total of 15 fdids + { 0xC8753773ADF1174CULL, { 0x1E, 0x0E, 0x37, 0xD4, 0x2E, 0xE5, 0xCE, 0x5E, 0x80, 0x67, 0xF0, 0x39, 0x4B, 0x09, 0x05, 0xF2 } }, // 244 WOW-28938patch8.1.5_PTR starts at fdid 2615771, total of 15 fdids // { 0x2170BCAA9FA96E22ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 245 WOW-28938patch8.1.5_PTR alpaca mount // { 0x75485627AA225F4DULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 246 WOW-28938patch8.1.5_PTR fdid 2741546, 2741548, 2741549 { 0x08717B15BF3C7955ULL, { 0x4B, 0x06, 0xBF, 0x9D, 0x17, 0x66, 0x3C, 0xEB, 0x33, 0x12, 0xEA, 0x3C, 0x69, 0xFB, 0xC5, 0xDD } }, // 248 WOW-29220patch8.1.5_PTR inv_encrypted20.blp (fdid 2823166) @@ -225,13 +226,23 @@ static CASC_ENCRYPTION_KEY CascKeys[] = { 0x9FD609902B4B2E07ULL, { 0xAB, 0xE0, 0xC5, 0xF9, 0xC1, 0x23, 0xE6, 0xE2, 0x4E, 0x7B, 0xEA, 0x43, 0xC2, 0xBF, 0x00, 0xAC } }, // 250 WOW-29418patch8.1.5_PTR Derek Proudmoore cinematic (dpr, 5 files) // { 0xCB26B441FAE4C8CDULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 251 WOW-30080patch8.2.0_PTR fdid 2888623, 2892270, 2892271, 2892272, 2892274, 2892275 // { 0xA98C7594F55C02F0ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 252 WOW-30080patch8.2.0_PTR starts at fdid 2921871, total of 24 fdids -// { 0x259EE68CD9E76DBAULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 253 WOW-30080patch8.2.0_PTR starts at fdid 2957406, total of 30 fdids + { 0x259EE68CD9E76DBAULL, { 0x46, 0x5D, 0x78, 0x4F, 0x10, 0x19, 0x66, 0x1C, 0xCF, 0x41, 0x7F, 0xE4, 0x66, 0x80, 0x12, 0x83 } }, // 253 WOW-30080patch8.2.0_PTR starts at fdid 2957406, total of 30 fdids // { 0x6A026290FBDB3754ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 255 WOW-30080patch8.2.0_PTR starts at fdid 2976294, total of 201 fdids { 0xCF72FD04608D36EDULL, { 0xA0, 0xA8, 0x89, 0x97, 0x6D, 0x02, 0xFA, 0x8D, 0x00, 0xF7, 0xAF, 0x00, 0x17, 0xAD, 0x72, 0x1F } }, // 257 WOW-30262patch8.2.0_PTR Azshara Warbringer cinematic (5 files) -// { 0x17F07C2E3A45DB3DULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 258 WOW-30262patch8.2.0_PTR starts at fdid 982457, total of 17 fdids + { 0x17F07C2E3A45DB3DULL, { 0x6D, 0x38, 0x86, 0xBD, 0xB9, 0x1E, 0x71, 0x5A, 0xE7, 0x18, 0x2D, 0x9F, 0x3A, 0x08, 0xF2, 0xC9 } }, // 258 WOW-30262patch8.2.0_PTR Solesa Naksu Nazjatar phase (17 files) +// { 0xDFAB5841B87802B5ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 259 WOW-31337patch8.2.5_PTR starts at fdid 3016206, total of 8 fdids { 0xC050FA06BB0538F6ULL, { 0xC5, 0x52, 0xF5, 0xD0, 0xB7, 0x22, 0x31, 0x50, 0x2D, 0x25, 0x47, 0x31, 0x4E, 0x60, 0x15, 0xF7 } }, // 260 WOW-30495patch8.2.0_PTR Crossroads cinematic (5 files) -// { 0xAB5CDD3FC321831FULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 261 WOW-30495patch8.2.0_PTR Unknown cinematic fdid 3022943-3022947 (5 files) + { 0xAB5CDD3FC321831FULL, { 0xE1, 0x38, 0x4F, 0x5B, 0x06, 0xEB, 0xBC, 0xD3, 0x33, 0x69, 0x5A, 0xA6, 0xFF, 0xC6, 0x83, 0x18 } }, // 261 WOW-30495patch8.2.0_PTR Azshara kill cinematic (5 files) { 0xA7B7D1F12395040EULL, { 0x36, 0xAD, 0x3B, 0x31, 0x27, 0x3F, 0x1E, 0xBC, 0xEE, 0x85, 0x20, 0xAA, 0xA7, 0x4B, 0x12, 0xF2 } }, // 262 WOW-30495patch8.2.0_PTR Nazjatar intro cinematics (9 files) +// { 0x83A2AB72DD8AE992ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 263 WOW-31337patch8.2.5_PTR starts at fdid 801707, total of 372 fdids +// { 0xBEAF567CC45362F0ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 264 WOW-31337patch8.2.5_PTR starts at fdid 1002186, total of 71 fdids +// { 0x7BB3A77FD8D14783ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 265 WOW-31337patch8.2.5_PTR fdid 1251882, 1251883 +// { 0x8F4098E2470FE0C8ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 266 WOW-31337patch8.2.5_PTR starts at fdid 841604, total of 58 fdids +// { 0x6AC5C837A2027A6BULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 267 WOW-31337patch8.2.5_PTR starts at fdid 3037834, total of 263 fdids +// { 0x302AAD8B1F441D95ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 271 WOW-31337patch8.2.5_PTR starts at fdid 1376212, total of 300 fdids +// { 0x5C909F00088734B9ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 272 WOW-31337patch8.2.5_PTR +// { 0xF785977C76DE9C77ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 273 WOW-31337patch8.2.5_PTR starts at fdid 3071600, total of 295 fdids +// { 0x1CDAF3931871BEC3ULL, { 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x??, 0x?? } }, // 275 WOW-31337patch8.2.5_PTR fdid 988200, 1140079, 1263818, 1347275 }; //----------------------------------------------------------------------------- @@ -374,27 +385,27 @@ static LPBYTE CascFindKey(TCascStorage * hs, ULONGLONG KeyName) //----------------------------------------------------------------------------- // Public functions -int CascLoadEncryptionKeys(TCascStorage * hs) +DWORD CascLoadEncryptionKeys(TCascStorage * hs) { size_t nKeyCount = (sizeof(CascKeys) / sizeof(CASC_ENCRYPTION_KEY)); size_t nMaxItems = nKeyCount + CASC_EXTRA_KEYS; - int nError; + DWORD dwErrCode; // Create fast map of KeyName -> Key - nError = hs->EncryptionKeys.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_ENCRYPTION_KEY, KeyName)); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = hs->EncryptionKeys.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_ENCRYPTION_KEY, KeyName)); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Insert all static keys for (size_t i = 0; i < nKeyCount; i++) hs->EncryptionKeys.InsertObject(&CascKeys[i], &CascKeys[i].KeyName); // Create array for extra keys - nError = hs->ExtraKeysList.Create<CASC_ENCRYPTION_KEY>(CASC_EXTRA_KEYS); - return nError; + dwErrCode = hs->ExtraKeysList.Create<CASC_ENCRYPTION_KEY>(CASC_EXTRA_KEYS); + return dwErrCode; } -int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex) +DWORD CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer, DWORD dwFrameIndex) { ULONGLONG KeyName = 0; LPBYTE pbBufferEnd = pbInBuffer + cbInBuffer; @@ -404,7 +415,7 @@ int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBY DWORD IVSize; BYTE Vector[0x08]; BYTE EncryptionType; - int nError; + DWORD dwErrCode; // Verify and retrieve the key name size if(pbInBuffer >= pbBufferEnd) @@ -462,15 +473,15 @@ int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBY switch(EncryptionType) { case 'S': // Salsa20 - nError = Decrypt_Salsa20(pbOutBuffer, pbInBuffer, (pbBufferEnd - pbInBuffer), pbKey, 0x10, Vector); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = Decrypt_Salsa20(pbOutBuffer, pbInBuffer, (pbBufferEnd - pbInBuffer), pbKey, 0x10, Vector); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Supply the size of the output buffer pcbOutBuffer[0] = (DWORD)(pbBufferEnd - pbInBuffer); return ERROR_SUCCESS; -// case 'A': +// case 'A': // return ERROR_NOT_SUPPORTED; } @@ -478,7 +489,7 @@ int CascDecrypt(TCascStorage * hs, LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBY return ERROR_NOT_SUPPORTED; } -int CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer) +DWORD CascDirectCopy(LPBYTE pbOutBuffer, PDWORD pcbOutBuffer, LPBYTE pbInBuffer, DWORD cbInBuffer) { // Check the buffer size if((cbInBuffer - 1) > pcbOutBuffer[0]) @@ -526,7 +537,34 @@ bool WINAPI CascAddEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPBYTE Key) pEncKey->KeyName = KeyName; // Also insert the key to the map - return hs->EncryptionKeys.InsertObject(pEncKey, &pEncKey->KeyName); + if (!hs->EncryptionKeys.InsertObject(pEncKey, &pEncKey->KeyName)) + { + SetLastError(ERROR_ALREADY_EXISTS); + return false; + } + + return true; +} + +bool WINAPI CascAddStringEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPCSTR szKey) +{ + BYTE Key[CASC_KEY_LENGTH]; + + // Check the length of the string key + if(strlen(szKey) != CASC_KEY_LENGTH * 2) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + // Convert the string key to the binary array + if(ConvertStringToBinary(szKey, CASC_KEY_LENGTH * 2, Key) != ERROR_SUCCESS) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + return CascAddEncryptionKey(hStorage, KeyName, Key); } LPBYTE WINAPI CascFindEncryptionKey(HANDLE hStorage, ULONGLONG KeyName) diff --git a/dep/CascLib/src/CascFiles.cpp b/dep/CascLib/src/CascFiles.cpp index 5f4080bc4f2..8d90df23000 100644 --- a/dep/CascLib/src/CascFiles.cpp +++ b/dep/CascLib/src/CascFiles.cpp @@ -17,8 +17,8 @@ // Local defines typedef DWORD (*PARSECSVFILE)(TCascStorage * hs, CASC_CSV & Csv); -typedef int (*PARSETEXTFILE)(TCascStorage * hs, void * pvListFile); -typedef int (*PARSE_VARIABLE)(TCascStorage * hs, const char * szVariableName, const char * szDataBegin, const char * szDataEnd, void * pvParam); +typedef DWORD (*PARSETEXTFILE)(TCascStorage * hs, void * pvListFile); +typedef DWORD (*PARSE_VARIABLE)(TCascStorage * hs, const char * szVariableName, const char * szDataBegin, const char * szDataEnd, void * pvParam); #define MAX_VAR_NAME 80 @@ -56,26 +56,6 @@ static const TCHAR * DataDirs[] = NULL, }; -static const TGameLocaleString LocaleStrings[] = -{ - {"enUS", CASC_LOCALE_ENUS}, - {"koKR", CASC_LOCALE_KOKR}, - {"frFR", CASC_LOCALE_FRFR}, - {"deDE", CASC_LOCALE_DEDE}, - {"zhCN", CASC_LOCALE_ZHCN}, - {"esES", CASC_LOCALE_ESES}, - {"zhTW", CASC_LOCALE_ZHTW}, - {"enGB", CASC_LOCALE_ENGB}, - {"enCN", CASC_LOCALE_ENCN}, - {"enTW", CASC_LOCALE_ENTW}, - {"esMX", CASC_LOCALE_ESMX}, - {"ruRU", CASC_LOCALE_RURU}, - {"ptBR", CASC_LOCALE_PTBR}, - {"itIT", CASC_LOCALE_ITIT}, - {"ptPT", CASC_LOCALE_PTPT}, - {NULL, 0} -}; - //----------------------------------------------------------------------------- // Local functions @@ -94,6 +74,11 @@ static bool IsCharDigit(BYTE OneByte) return ('0' <= OneByte && OneByte <= '9'); } +static bool StringEndsWith(LPCSTR szString, size_t nLength, LPCSTR szSuffix, size_t nSuffixLength) +{ + return ((nLength > nSuffixLength) && !strcmp(szString + nLength - nSuffixLength, szSuffix)); +} + static const char * CaptureDecimalInteger(const char * szDataPtr, const char * szDataEnd, PDWORD PtrValue) { const char * szSaveDataPtr = szDataPtr; @@ -197,14 +182,34 @@ static const char * CaptureHashCount(const char * szDataPtr, const char * szData return szDataPtr; } -static DWORD GetLocaleMask(const char * szTag) +static DWORD GetLocaleValue(LPCSTR szTag) { - for(size_t i = 0; LocaleStrings[i].szLocale != NULL; i++) + DWORD Language = 0; + + // Convert the string language to integer + Language = (Language << 0x08) | szTag[0]; + Language = (Language << 0x08) | szTag[1]; + Language = (Language << 0x08) | szTag[2]; + Language = (Language << 0x08) | szTag[3]; + + // Language-specific action + switch(Language) { - if(!strncmp(LocaleStrings[i].szLocale, szTag, 4)) - { - return LocaleStrings[i].dwLocale; - } + case 0x656e5553: return CASC_LOCALE_ENUS; + case 0x656e4742: return CASC_LOCALE_ENGB; + case 0x656e434e: return CASC_LOCALE_ENCN; + case 0x656e5457: return CASC_LOCALE_ENTW; + case 0x65734553: return CASC_LOCALE_ESES; + case 0x65734d58: return CASC_LOCALE_ESMX; + case 0x70744252: return CASC_LOCALE_PTBR; + case 0x70745054: return CASC_LOCALE_PTPT; + case 0x7a68434e: return CASC_LOCALE_ZHCN; + case 0x7a685457: return CASC_LOCALE_ZHTW; + case 0x6b6f4b52: return CASC_LOCALE_KOKR; + case 0x66724652: return CASC_LOCALE_FRFR; + case 0x64654445: return CASC_LOCALE_DEDE; + case 0x72755255: return CASC_LOCALE_RURU; + case 0x69744954: return CASC_LOCALE_ITIT; } return 0; @@ -240,17 +245,17 @@ static bool CheckConfigFileVariable( return (PfnParseProc(hs, szVariableName, szLinePtr, szLineEnd, pvParseParam) == ERROR_SUCCESS); } -static int LoadHashArray( +static DWORD LoadHashArray( PQUERY_KEY pBlob, const char * szLinePtr, const char * szLineEnd, size_t HashCount) { - int nError = ERROR_NOT_ENOUGH_MEMORY; + DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; // Allocate the blob buffer pBlob->cbData = (DWORD)(HashCount * MD5_HASH_SIZE); - pBlob->pbData = CASC_ALLOC(BYTE, pBlob->cbData); + pBlob->pbData = CASC_ALLOC<BYTE>(pBlob->cbData); if (pBlob->pbData != NULL) { LPBYTE pbBuffer = pBlob->pbData; @@ -266,16 +271,16 @@ static int LoadHashArray( pbBuffer += MD5_HASH_SIZE; } - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } - return nError; + return dwErrCode; } -static int LoadMultipleHashes(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd) +static DWORD LoadMultipleHashes(PQUERY_KEY pBlob, const char * szLineBegin, const char * szLineEnd) { size_t HashCount = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Retrieve the hash count if (CaptureHashCount(szLineBegin, szLineEnd, &HashCount) == NULL) @@ -284,41 +289,47 @@ static int LoadMultipleHashes(PQUERY_KEY pBlob, const char * szLineBegin, const // Do nothing if there is no data if(HashCount != 0) { - nError = LoadHashArray(pBlob, szLineBegin, szLineEnd, HashCount); + dwErrCode = LoadHashArray(pBlob, szLineBegin, szLineEnd, HashCount); } - return nError; + return dwErrCode; } // Loads a query key from the text form // A QueryKey is an array of "ContentKey EncodedKey1 ... EncodedKeyN" -static int LoadQueryKey(TCascStorage * /* hs */, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * pvParam) +static DWORD LoadQueryKey(TCascStorage * /* hs */, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * pvParam) { return LoadMultipleHashes((PQUERY_KEY)pvParam, szDataBegin, szDataEnd); } -static int LoadCKeyEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) +static DWORD LoadCKeyEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) { PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)pvParam; size_t nLength = strlen(szVariableName); size_t HashCount = 0; + // Ignore "xxx-config" items + if(StringEndsWith(szVariableName, nLength, "-config", 7)) + return ERROR_SUCCESS; + // If the variable ends at "-size", it means we need to capture the size - if((nLength > 5) && !strcmp(szVariableName + nLength - 5, "-size")) + if(StringEndsWith(szVariableName, nLength, "-size", 5)) { DWORD ContentSize = CASC_INVALID_SIZE; DWORD EncodedSize = CASC_INVALID_SIZE; // Load the content size szDataPtr = CaptureDecimalInteger(szDataPtr, szDataEnd, &ContentSize); - if(szDataPtr != NULL) - { - CaptureDecimalInteger(szDataPtr, szDataEnd, &EncodedSize); - pCKeyEntry->ContentSize = ContentSize; - pCKeyEntry->EncodedSize = EncodedSize; - return ERROR_SUCCESS; - } + if(szDataPtr == NULL) + return ERROR_BAD_FORMAT; + + CaptureDecimalInteger(szDataPtr, szDataEnd, &EncodedSize); + pCKeyEntry->ContentSize = ContentSize; + pCKeyEntry->EncodedSize = EncodedSize; + return ERROR_SUCCESS; } + + // If the string doesn't end with "-config", assume it's the CKey/EKey else { // Get the number of hashes. It is expected to be 1 or 2 @@ -355,7 +366,30 @@ static int LoadCKeyEntry(TCascStorage * hs, const char * szVariableName, const c return ERROR_BAD_FORMAT; } -static int LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) +// For files like PATCH, which only contain EKey in the build file +static DWORD LoadEKeyEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) +{ + PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)pvParam; + DWORD dwErrCode; + + // Load the entry as if it was a CKey. Then move CKey into EKey and adjust flags + if((dwErrCode = LoadCKeyEntry(hs, szVariableName, szDataPtr, szDataEnd, pvParam)) == ERROR_SUCCESS) + { + if((pCKeyEntry->Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL)) == CASC_CE_HAS_CKEY) + { + // Move the CKey into EKey + CopyMemory16(pCKeyEntry->EKey, pCKeyEntry->CKey); + ZeroMemory16(pCKeyEntry->CKey); + + // Adjust flags + pCKeyEntry->Flags = (pCKeyEntry->Flags & ~CASC_CE_HAS_CKEY) | CASC_CE_HAS_EKEY; + } + } + + return dwErrCode; +} + +static DWORD LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, const char * szDataPtr, const char * szDataEnd, void * pvParam) { PCASC_CKEY_ENTRY pCKeyEntry; CASC_ARRAY * pArray = (CASC_ARRAY *)pvParam; @@ -397,93 +431,16 @@ static int LoadVfsRootEntry(TCascStorage * hs, const char * szVariableName, cons return ERROR_SUCCESS; } -static int LoadBuildProductId(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */) +static DWORD LoadBuildProductId(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */) { - DWORD dwBuildUid = 0; - - // Convert up to 4 chars to DWORD - for(size_t i = 0; i < 4 && szDataBegin < szDataEnd; i++) - { - dwBuildUid = (dwBuildUid << 0x08) | (BYTE)szDataBegin[0]; - szDataBegin++; - } + size_t nLength = (szDataEnd - szDataBegin); - // Product-specific. See https://wowdev.wiki/TACT#Products - switch(dwBuildUid) + if(hs->szCodeName == NULL) { - case 0x00006433: // 'd3' - case 0x00643364: // 'd3b': Diablo 3 Beta (2013) - case 0x6433636e: // 'd3cn': Diablo 3 China - case 0x00643374: // 'd3t': Diablo 3 Test - hs->szProductName = "Diablo 3"; - hs->Product = Diablo3; - break; - - case 0x64737432: // 'dst2': - hs->szProductName = "Destiny 2"; - hs->Product = Destiny2; - break; - - case 0x00626e74: // 'bnt': Heroes of the Storm Alpha - case 0x6865726f: // 'hero': Heroes of the Storm Retail - case 0x73746f72: // 'stor': Heroes of the Storm (deprecated) - hs->szProductName = "Heroes Of The Storm"; - hs->Product = HeroesOfTheStorm; - break; - - case 0x0070726f: // 'pro': - case 0x70726f63: // 'proc': - case 0x70726f64: // 'prod': "prodev": Overwatch Dev - case 0x70726f65: // 'proe': Not on public CDNs - case 0x70726f74: // 'prot': Overwatch Test - case 0x70726f76: // 'prov': Overwatch Vendor - case 0x70726f6d: // 'prom': "proms": Overwatch World Cup Viewer - hs->szProductName = "Overwatch"; - hs->Product = Overwatch; - break; - - case 0x00007331: // 's1': StarCraft 1 - case 0x00733161: // 's1a': Starcraft 1 Alpha - case 0x00733174: // 's1t': StarCraft 1 Test - hs->szProductName = "Starcraft 1"; - hs->Product = StarCraft1; - break; - - case 0x00007332: // 's2': StarCraft 2 - case 0x00733262: // 's2b': Starcraft 2 Beta - case 0x00733274: // 's2t': StarCraft 2 Test - case 0x00736332: // 'sc2': StarCraft 2 (deprecated) - hs->szProductName = "Starcraft 2"; - hs->Product = StarCraft2; - break; - - case 0x76697065: // "viper", "viperdev", "viperv1": Call of Duty Black Ops 4 - hs->szProductName = "Call Of Duty Black Ops 4"; - hs->Product = CallOfDutyBlackOps4; - break; - - case 0x00007733: // 'w3': Warcraft III - case 0x00773374: // 'w3t': Warcraft III Public Test - case 0x77617233: // 'war3': Warcraft III (old) - hs->szProductName = "WarCraft 3"; - hs->Product = WarCraft3; - break; - - case 0x00776f77: // 'wow': World of Warcraft - case 0x776f775f: // "wow_beta", "wow_classic", "wow_classic_beta" - case 0x776f7764: // "wowdev", "wowdemo" - case 0x776f7765: // "wowe1", "wowe3", "wowe3" - case 0x776f7774: // 'wowt': World of Warcraft Test - case 0x776f7776: // 'wowv': World of Warcraft Vendor - case 0x776f777a: // 'wowz': World of Warcraft Submission (previously Vendor) - hs->szProductName = "World Of Warcraft"; - hs->Product = WorldOfWarcraft; - break; - - default: - hs->szProductName = "Unknown Product"; - hs->Product = UnknownProduct; - break; + if((hs->szCodeName = CASC_ALLOC<TCHAR>(nLength + 1)) != NULL) + { + CascStrCopy(hs->szCodeName, nLength + 1, szDataBegin, nLength); + } } return ERROR_SUCCESS; @@ -493,7 +450,7 @@ static int LoadBuildProductId(TCascStorage * hs, const char * /* szVariableName // "WOW-18125patch6.0.1" // "30013_Win32_2_2_0_Ptr_ptr" // "prometheus-0_8_0_0-24919" -static int LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */) +static DWORD LoadBuildNumber(TCascStorage * hs, const char * /* szVariableName */, const char * szDataBegin, const char * szDataEnd, void * /* pvParam */) { DWORD dwBuildNumber = 0; DWORD dwMaxValue = 0; @@ -535,19 +492,30 @@ static int LoadQueryKey(const CASC_CSV_COLUMN & Column, QUERY_KEY & Key) return LoadHashArray(&Key, Column.szValue, Column.szValue + Column.nLength, 1); } +static void SetProductCodeName(TCascStorage * hs, LPCSTR szCodeName) +{ + TCHAR szCodeNameT[0x40]; + + if(hs->szCodeName == NULL && szCodeName != NULL) + { + CascStrCopy(szCodeNameT, _countof(szCodeNameT), szCodeName); + hs->szCodeName = CascNewStr(szCodeNameT); + } +} + static int GetDefaultLocaleMask(TCascStorage * hs, const CASC_CSV_COLUMN & Column) { - const char * szTagEnd = Column.szValue + Column.nLength; - const char * szTagPtr = Column.szValue; + LPCSTR szTagEnd = Column.szValue + Column.nLength - 4; + LPCSTR szTagPtr = Column.szValue; DWORD dwLocaleMask = 0; while(szTagPtr < szTagEnd) { - // Could this be a locale string? - if((szTagPtr + 4) <= szTagEnd && (szTagPtr[4] == ',' || szTagPtr[4] == ' ')) + DWORD dwLocaleValue = GetLocaleValue(szTagPtr); + + if(dwLocaleValue != 0) { - // Check whether the current tag is a language identifier - dwLocaleMask = dwLocaleMask | GetLocaleMask(szTagPtr); + dwLocaleMask = dwLocaleMask | GetLocaleValue(szTagPtr); szTagPtr += 4; } else @@ -560,17 +528,6 @@ static int GetDefaultLocaleMask(TCascStorage * hs, const CASC_CSV_COLUMN & Colum return ERROR_SUCCESS; } -static void SetProductCodeName(TCascStorage * hs, LPCSTR szCodeName) -{ - TCHAR szCodeNameT[0x40]; - - if(hs->szCodeName == NULL && szCodeName != NULL) - { - CascStrCopy(szCodeNameT, _countof(szCodeNameT), szCodeName); - hs->szCodeName = CascNewStr(szCodeNameT); - } -} - static DWORD ParseFile_CDNS(TCascStorage * hs, CASC_CSV & Csv) { const char * szWantedRegion = hs->szRegion; @@ -617,7 +574,6 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv) size_t nLineCount = Csv.GetLineCount(); size_t nSelected = CASC_INVALID_INDEX; DWORD dwErrCode; - char szWantedCodeName[0x40] = ""; // // Find the configuration that we're gonna load. @@ -664,12 +620,6 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv) } } - // If the product is specified by hs->szCodeName and the ".build.info" contains "Product!STRING:0", we watch for that product. - if(hs->szCodeName != NULL && nProductColumnIndex != CASC_INVALID_INDEX) - { - CascStrCopy(szWantedCodeName, _countof(szWantedCodeName), hs->szCodeName); - } - // Parse all lines in the CSV file. Either take the first that is active // or take the one that has been selected by the callback for(size_t i = 0; i < nLineCount; i++) @@ -677,29 +627,28 @@ static DWORD ParseFile_BuildInfo(TCascStorage * hs, CASC_CSV & Csv) // Ignore anything that is not marked "active" if(!strcmp(Csv[i]["Active!DEC:1"].szValue, "1")) { - // If we have no product code name specified, we pick the very first active build - if(hs->szCodeName == NULL) + // If the product code is specified and exists in the build file, we need to check it + if(hs->szCodeName != NULL && (szCodeName = Csv[i]["Product!STRING:0"].szValue) != NULL) { + TCHAR szBuffer[0x20]; + + // Skip if they are not equal + CascStrCopy(szBuffer, _countof(szBuffer), szCodeName); + if(_tcsicmp(hs->szCodeName, szBuffer)) + continue; + // Save the code name of the selected product SetProductCodeName(hs, Csv[i]["Product!STRING:0"].szValue); nSelected = i; - goto __ChooseThisProduct; + break; } - // If we have a product code name specified, pick the first matching - else if((szCodeName = Csv[i]["Product!STRING:0"].szValue) != NULL) - { - if(!strcmp(szCodeName, szWantedCodeName)) - { - nSelected = i; - goto __ChooseThisProduct; - } - } + // If the caller didn't specify the product code name, pick the first active + nSelected = i; + break; } } - __ChooseThisProduct: - // Load the selected product if(nSelected < nLineCount) { @@ -779,18 +728,15 @@ static DWORD ParseFile_BuildDb(TCascStorage * hs, CASC_CSV & Csv) if (dwErrCode != ERROR_SUCCESS) return dwErrCode; - // Load the the tags - GetDefaultLocaleMask(hs, Csv[CSV_ZERO][2]); - // Verify all variables return (hs->CdnBuildKey.pbData != NULL && hs->CdnConfigKey.pbData != NULL) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; } -static int ParseFile_CdnConfig(TCascStorage * hs, void * pvListFile) +static DWORD ParseFile_CdnConfig(TCascStorage * hs, void * pvListFile) { const char * szLineBegin; const char * szLineEnd; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Keep parsing the listfile while there is something in there for(;;) @@ -827,19 +773,19 @@ static int ParseFile_CdnConfig(TCascStorage * hs, void * pvListFile) if(hs->ArchivesKey.pbData == NULL || hs->ArchivesKey.cbData == 0) return ERROR_BAD_FORMAT; - return nError; + return dwErrCode; } -static int ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) +static DWORD ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) { const char * szLineBegin; const char * szLineEnd = NULL; - int nError; + DWORD dwErrCode; // Initialize the empty VFS array - nError = hs->VfsRootList.Create<CASC_CKEY_ENTRY>(0x10); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = hs->VfsRootList.Create<CASC_CKEY_ENTRY>(0x10); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Parse all variables while(ListFile_GetNextLine(pvListFile, &szLineBegin, &szLineEnd) != 0) @@ -870,7 +816,7 @@ static int ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) continue; // PATCH file - if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "patch*", LoadCKeyEntry, &hs->PatchFile)) + if(CheckConfigFileVariable(hs, szLineBegin, szLineEnd, "patch*", LoadEKeyEntry, &hs->PatchFile)) continue; // SIZE file @@ -887,30 +833,21 @@ static int ParseFile_CdnBuild(TCascStorage * hs, void * pvListFile) } // Special case: Some builds of WoW (22267) don't have the variable in the build file - if(hs->szProductName == NULL || hs->Product == UnknownProduct) + if(hs->szCodeName == NULL && hs->szCdnPath != NULL) { - if(hs->szCdnPath != NULL) - { - TCHAR * szPlainName = (TCHAR *)GetPlainFileName(hs->szCdnPath); - - if(szPlainName[0] == 'w' && szPlainName[1] == 'o' && szPlainName[2] == 'w') - { - hs->szProductName = "World Of Warcraft"; - hs->Product = WorldOfWarcraft; - } - } + hs->szCodeName = CascNewStr(GetPlainFileName(hs->szCdnPath)); } // Both CKey and EKey of ENCODING file is required if((hs->EncodingCKey.Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY)) != (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY)) - return ERROR_BAD_FORMAT; - return nError; + dwErrCode = ERROR_BAD_FORMAT; + return dwErrCode; } -static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory) +static DWORD CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory) { TCHAR * szDataPath; - int nError = ERROR_FILE_NOT_FOUND; + DWORD dwErrCode = ERROR_FILE_NOT_FOUND; // Try all known subdirectories for(size_t i = 0; DataDirs[i] != NULL; i++) @@ -932,7 +869,7 @@ static int CheckDataDirectory(TCascStorage * hs, TCHAR * szDirectory) } } - return nError; + return dwErrCode; } static DWORD LoadCsvFile(TCascStorage * hs, const TCHAR * szFileName, PARSECSVFILE PfnParseProc, bool bHasHeader) @@ -973,12 +910,68 @@ static const TCHAR * ExtractCdnServerName(TCHAR * szServerName, size_t cchServer return NULL; } -static int ForcePathExist(const TCHAR * szFileName, bool bIsFileName) +static void CreateRemoteAndLocalPath(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo, CASC_PATH<TCHAR> & RemotePath, CASC_PATH<TCHAR> & LocalPath) +{ + PCASC_EKEY_ENTRY pEKeyEntry; + ULONGLONG ByteMask = 1; + DWORD ArchiveIndex; + + // Append the CDN host / root folder + RemotePath.SetPathRoot(CdnsInfo.szCdnsHost); + RemotePath.AppendString(CdnsInfo.szCdnsPath, true); + LocalPath.SetPathRoot(hs->szRootPath); + + // If there is an EKey, take EKey + if(CdnsInfo.pbEKey != NULL) + { + // 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) + { + // Change the path type to "data" + RemotePath.AppendString(_T("data"), true); + LocalPath.AppendString(_T("data"), true); + + // Append the EKey of the archive instead of the file itself + ArchiveIndex = (DWORD)(pEKeyEntry->StorageOffset >> hs->FileOffsetBits); + CdnsInfo.pbArchiveKey = hs->ArchivesKey.pbData + (MD5_HASH_SIZE * ArchiveIndex); + RemotePath.AppendEKey(CdnsInfo.pbArchiveKey); + LocalPath.AppendEKey(CdnsInfo.pbArchiveKey); + + // Get the archive index and archive offset + CdnsInfo.ArchiveIndex = ArchiveIndex; + CdnsInfo.ArchiveOffs = pEKeyEntry->StorageOffset & ((ByteMask << hs->FileOffsetBits) - 1); + CdnsInfo.EncodedSize = pEKeyEntry->EncodedSize; + } + else + { + // Append the path type + RemotePath.AppendString(CdnsInfo.szPathType, true); + LocalPath.AppendString((CdnsInfo.szLoPaType != NULL) ? CdnsInfo.szLoPaType : CdnsInfo.szPathType, true); + + // Append the EKey + RemotePath.AppendEKey(CdnsInfo.pbEKey); + LocalPath.AppendEKey(CdnsInfo.pbEKey); + } + } + else + { + assert(CdnsInfo.szFileName != NULL); + RemotePath.AppendString(CdnsInfo.szFileName, true); + LocalPath.AppendString(CdnsInfo.szFileName, true); + } + + // Append extension + RemotePath.AppendString(CdnsInfo.szExtension, false); + LocalPath.AppendString(CdnsInfo.szExtension, false); +} + +static DWORD ForcePathExist(const TCHAR * szFileName, bool bIsFileName) { TCHAR * szLocalPath; size_t nIndex; bool bFirstSeparator = false; - int nError = ERROR_NOT_ENOUGH_MEMORY; + DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; // Sanity checks if(szFileName && szFileName[0]) @@ -1007,7 +1000,7 @@ static int ForcePathExist(const TCHAR * szFileName, bool bIsFileName) // Is it there? if(DirectoryExists(szLocalPath) == false && MakeDirectory(szLocalPath) == false) { - nError = ERROR_PATH_NOT_FOUND; + dwErrCode = ERROR_PATH_NOT_FOUND; break; } } @@ -1015,31 +1008,31 @@ static int ForcePathExist(const TCHAR * szFileName, bool bIsFileName) // Restore the character szLocalPath[nIndex] = PATH_SEP_CHAR; bFirstSeparator = true; - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } } // Now check the final path if(DirectoryExists(szLocalPath) || MakeDirectory(szLocalPath)) { - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } } else { - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } CASC_FREE(szLocalPath); } } - return nError; + return dwErrCode; } -static int DownloadFile( - const TCHAR * szRemoteName, - const TCHAR * szLocalName, +static DWORD DownloadFile( + LPCTSTR szRemoteName, + LPCTSTR szLocalName, PULONGLONG PtrByteOffset, DWORD cbReadSize, DWORD dwPortFlags) @@ -1047,7 +1040,7 @@ static int DownloadFile( TFileStream * pRemStream; TFileStream * pLocStream; LPBYTE pbFileData; - int nError = ERROR_NOT_ENOUGH_MEMORY; + DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; // Open the remote stream pRemStream = FileStream_OpenFile(szRemoteName, BASE_PROVIDER_HTTP | STREAM_PROVIDER_FLAT | dwPortFlags); @@ -1058,8 +1051,8 @@ static int DownloadFile( { ULONGLONG FileSize = 0; - // Retrieve the file size - if (FileStream_GetSize(pRemStream, &FileSize) && 0 < FileSize && FileSize < 0x10000000) + // Retrieve the file size, but not longer than 1 GB + if (FileStream_GetSize(pRemStream, &FileSize) && 0 < FileSize && FileSize < 0x40000000) { // Cut the file size down to 32 bits cbReadSize = (DWORD)FileSize; @@ -1067,7 +1060,7 @@ static int DownloadFile( } // Shall we read something? - if ((cbReadSize != 0) && (pbFileData = CASC_ALLOC(BYTE, cbReadSize)) != NULL) + if ((cbReadSize != 0) && (pbFileData = CASC_ALLOC<BYTE>(cbReadSize)) != NULL) { // Read all required data from the remote file if (FileStream_Read(pRemStream, PtrByteOffset, pbFileData, cbReadSize)) @@ -1076,7 +1069,7 @@ static int DownloadFile( if (pLocStream != NULL) { if (FileStream_Write(pLocStream, NULL, pbFileData, cbReadSize)) - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; FileStream_Close(pLocStream); } @@ -1091,90 +1084,102 @@ static int DownloadFile( } else { - nError = GetLastError(); + dwErrCode = GetLastError(); } - return nError; + return dwErrCode; } -static int DownloadFile( - TCascStorage * hs, - const TCHAR * szServerName, - const TCHAR * szServerPath1, - const TCHAR * szServerPath2, - const TCHAR * szLocalPath2, - PULONGLONG PtrByteOffset, - DWORD cbReadSize, - TCHAR * szOutLocalPath, - size_t cchOutLocalPath, - DWORD dwPortFlags, - bool bAlwaysDownload, - bool bWowClassicRedirect) +static DWORD DownloadFileFromCDN2(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo) { - TCHAR szRemotePath[MAX_PATH]; - TCHAR szLocalPath[MAX_PATH]; - size_t nLength; - int nError = ERROR_NOT_ENOUGH_MEMORY; - - // Format the target URL - if(bWowClassicRedirect && !_tcsicmp(szServerPath1, _T("wow_classic_beta"))) - szServerPath1 = _T("wow"); - if (szLocalPath2 == NULL) - szLocalPath2 = szServerPath2; - - // Create remote path - CombinePath(szRemotePath, _countof(szRemotePath), URL_SEP_CHAR, szServerName, szServerPath1, szServerPath2, NULL); - CombinePath(szLocalPath, _countof(szLocalPath), PATH_SEP_CHAR, hs->szRootPath, szLocalPath2, NULL); + CASC_PATH<TCHAR> RemotePath(URL_SEP_CHAR); + CASC_PATH<TCHAR> LocalPath(PATH_SEP_CHAR); + DWORD dwPortFlags = (CdnsInfo.Flags & CASC_CDN_FLAG_PORT1119) ? STREAM_FLAG_USE_PORT_1119 : 0; + DWORD dwErrCode; - // Make sure that the path exists - ForcePathExist(szLocalPath, true); + // Assemble both the remote and local path + assert(CdnsInfo.szCdnsHost != NULL && CdnsInfo.szCdnsHost[0] != 0); + CreateRemoteAndLocalPath(hs, CdnsInfo, RemotePath, LocalPath); - // If we are not forced to download a new one, try if local file exists. - if ((bAlwaysDownload == false) && (_taccess(szLocalPath, 0) == 0)) - { - nError = ERROR_SUCCESS; - } - else + // Check whether the local file exists + if((CdnsInfo.Flags & CASC_CDN_FORCE_DOWNLOAD) || (_taccess(LocalPath, 0) == -1)) { - nError = DownloadFile(szRemotePath, szLocalPath, PtrByteOffset, cbReadSize, dwPortFlags); + // Make sure that the path exists + dwErrCode = ForcePathExist(LocalPath, true); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + + // Attempt to download the file + dwErrCode = DownloadFile(RemotePath, LocalPath, NULL, 0, dwPortFlags); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; } - // If succeeded, give the local path - if(nError == ERROR_SUCCESS) + // Give the path to the caller, if any + LocalPath.Copy(CdnsInfo.szLocalPath, CdnsInfo.ccLocalPath); + return ERROR_SUCCESS; +} + +DWORD DownloadFileFromCDN(TCascStorage * hs, CASC_CDN_DOWNLOAD & CdnsInfo) +{ + LPCTSTR szCdnServers = hs->szCdnServers; + TCHAR szCdnHost[MAX_PATH] = _T(""); + DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE; + + // If we have a given CDN server, use it. If not, try all CDNs + // from the storage's configuration + if(CdnsInfo.szCdnsHost == NULL) { - // If the caller wanted local path, give it to him - if (szOutLocalPath && cchOutLocalPath) + // Try all download servers + while((szCdnServers = ExtractCdnServerName(szCdnHost, _countof(szCdnHost), szCdnServers)) != NULL) { - nLength = _tcslen(szLocalPath); - if ((nLength + 1) <= cchOutLocalPath) - { - CascStrCopy(szOutLocalPath, cchOutLocalPath, szLocalPath); - } + CdnsInfo.szCdnsHost = szCdnHost; + if((dwErrCode = DownloadFileFromCDN2(hs, CdnsInfo)) == ERROR_SUCCESS) + return ERROR_SUCCESS; } } + else + { + dwErrCode = DownloadFileFromCDN2(hs, CdnsInfo); + } - return nError; + return dwErrCode; } -static int FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARSETEXTFILE PfnParseProc) +static DWORD FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARSETEXTFILE PfnParseProc) { - const TCHAR * szPathType = _T("config"); + LPCTSTR szPathType = _T("config"); TCHAR szLocalPath[MAX_PATH]; TCHAR szSubDir[0x80] = _T(""); void * pvListFile = NULL; - int nError = ERROR_CAN_NOT_COMPLETE; + DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE; // If online storage, we download the file. Otherwise, create a local path if(hs->dwFeatures & CASC_FEATURE_ONLINE) { - nError = DownloadFileFromCDN(hs, szPathType, pFileKey->pbData, NULL, szLocalPath, _countof(szLocalPath)); - if(nError != ERROR_SUCCESS) - return nError; + CASC_CDN_DOWNLOAD CdnsInfo = {0}; + + // Prepare the download structure for "%CDNS_HOST%/%CDNS_PATH%/##/##/EKey" file + CdnsInfo.szCdnsPath = hs->szCdnPath; + CdnsInfo.szPathType = szPathType; + CdnsInfo.pbEKey = pFileKey->pbData; + CdnsInfo.szLocalPath = szLocalPath; + CdnsInfo.ccLocalPath = _countof(szLocalPath); + + // Download the config file + dwErrCode = DownloadFileFromCDN(hs, CdnsInfo); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; } else { - CreateCascSubdirectoryName(szSubDir, _countof(szSubDir), szPathType, NULL, pFileKey->pbData); - CombinePath(szLocalPath, _countof(szLocalPath), PATH_SEP_CHAR, hs->szDataPath, szSubDir, NULL); + CASC_PATH<TCHAR> Path; + + Path.AppendString(hs->szDataPath, false); + Path.AppendString(szSubDir, true); + Path.AppendString(szPathType, true); + Path.AppendEKey(pFileKey->pbData); + Path.Copy(szLocalPath, _countof(szLocalPath)); } // Load and verify the external listfile @@ -1183,20 +1188,20 @@ static int FetchAndLoadConfigFile(TCascStorage * hs, PQUERY_KEY pFileKey, PARSET { if (ListFile_VerifyMD5(pvListFile, pFileKey->pbData)) { - nError = PfnParseProc(hs, pvListFile); + dwErrCode = PfnParseProc(hs, pvListFile); } else { - nError = ERROR_FILE_CORRUPT; + dwErrCode = ERROR_FILE_CORRUPT; } CASC_FREE(pvListFile); } else { - nError = ERROR_FILE_NOT_FOUND; + dwErrCode = ERROR_FILE_NOT_FOUND; } - return nError; + return dwErrCode; } //----------------------------------------------------------------------------- @@ -1212,57 +1217,33 @@ bool InvokeProgressCallback(TCascStorage * hs, LPCSTR szMessage, LPCSTR szObject return bResult; } -DWORD DownloadFileFromCDN(TCascStorage * hs, const TCHAR * szSubDir, LPBYTE pbEKey, const TCHAR * szExtension, TCHAR * szOutLocalPath, size_t cchOutLocalPath) +DWORD GetFileSpanInfo(PCASC_CKEY_ENTRY pCKeyEntry, PULONGLONG PtrContentSize, PULONGLONG PtrEncodedSize) { - PCASC_ARCINDEX_ENTRY pIndexEntry; - const TCHAR * szCdnServers = hs->szCdnServers; - TCHAR szRemoteFolder[MAX_PATH]; - TCHAR szLocalFolder[MAX_PATH]; - TCHAR szServerName[MAX_PATH]; - DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE; + ULONGLONG ContentSize = 0; + ULONGLONG EncodedSize = 0; + DWORD dwSpanCount = pCKeyEntry->SpanCount; + bool bContentSizeError = false; + bool bEncodedSizeError = false; - // Try all download servers - while((szCdnServers = ExtractCdnServerName(szServerName, _countof(szServerName), szCdnServers)) != NULL) - { - // Create the local subdirectory - CreateCascSubdirectoryName(szLocalFolder, _countof(szLocalFolder), szSubDir, szExtension, pbEKey); + // Sanity check + assert(pCKeyEntry->SpanCount != 0); - // If the EKey is in an archive, we need to change the source - if ((pIndexEntry = (PCASC_ARCINDEX_ENTRY)hs->ArcIndexMap.FindObject(pbEKey)) != NULL) - { - ULONGLONG ByteOffset; - - // Change the subpath to an archive - CreateCascSubdirectoryName(szRemoteFolder, _countof(szRemoteFolder), szSubDir, szExtension, pIndexEntry->IndexHash); - ByteOffset = pIndexEntry->ArchiveOffset; - dwErrCode = DownloadFile(hs, - szServerName, - hs->szCdnPath, - szRemoteFolder, - szLocalFolder, - &ByteOffset, - pIndexEntry->EncodedSize, - szOutLocalPath, - cchOutLocalPath, 0, false, false); - } - else - { - dwErrCode = DownloadFile(hs, - szServerName, - hs->szCdnPath, - szLocalFolder, - szLocalFolder, - NULL, - 0, - szOutLocalPath, - cchOutLocalPath, 0, false, false); - } + // Sum all span size + for(DWORD i = 0; i < dwSpanCount; i++, pCKeyEntry++) + { + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + bContentSizeError = true; + if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) + bEncodedSizeError = true; - if (dwErrCode == ERROR_SUCCESS) - break; + ContentSize = ContentSize + pCKeyEntry->ContentSize; + EncodedSize = EncodedSize + pCKeyEntry->EncodedSize; } - return dwErrCode; + // Reset the sizes if there was an error + PtrContentSize[0] = (bContentSizeError == false) ? ContentSize : CASC_INVALID_SIZE64; + PtrEncodedSize[0] = (bEncodedSizeError == false) ? EncodedSize : CASC_INVALID_SIZE64; + return dwSpanCount; } // Checks whether there is a ".build.info" or ".build.db". @@ -1314,19 +1295,33 @@ DWORD LoadBuildInfo(TCascStorage * hs) // 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 = DownloadFile(hs, _T("us.patch.battle.net"), hs->szCodeName, _T("versions"), NULL, NULL, 0, NULL, 0, STREAM_FLAG_USE_PORT_1119, true, false); + 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 either ".build.info" or ".build.db" + // We support ".build.info", ".build.db" or "versions" switch (hs->BuildFileType) { case CascBuildInfo: @@ -1346,11 +1341,13 @@ DWORD LoadBuildInfo(TCascStorage * hs) return ERROR_NOT_SUPPORTED; } + assert(hs->szBuildFile != NULL); return LoadCsvFile(hs, hs->szBuildFile, PfnParseProc, bHasHeader); } -DWORD LoadCdnsInfo(TCascStorage * hs) +DWORD LoadCdnsFile(TCascStorage * hs) { + CASC_CDN_DOWNLOAD CdnsInfo = {0}; TCHAR szLocalPath[MAX_PATH]; DWORD dwErrCode = ERROR_SUCCESS; @@ -1361,9 +1358,17 @@ DWORD LoadCdnsInfo(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 - dwErrCode = DownloadFile(hs, _T("us.patch.battle.net"), hs->szCodeName, _T("cdns"), NULL, NULL, 0, szLocalPath, _countof(szLocalPath), STREAM_FLAG_USE_PORT_1119, false, true); - if(dwErrCode == ERROR_SUCCESS) + if((dwErrCode = DownloadFileFromCDN(hs, CdnsInfo)) == ERROR_SUCCESS) { dwErrCode = LoadCsvFile(hs, szLocalPath, ParseFile_CDNS, true); } @@ -1377,7 +1382,7 @@ DWORD LoadCdnConfigFile(TCascStorage * hs) assert(hs->CdnConfigKey.pbData != NULL && hs->CdnConfigKey.cbData == MD5_HASH_SIZE); // Inform the user about what we are doing - if(InvokeProgressCallback(hs, "Downloading CDN config file", NULL, 0, 0)) + if(InvokeProgressCallback(hs, "Loading CDN config file", NULL, 0, 0)) return ERROR_CANCELLED; // Load the CDN config file @@ -1390,10 +1395,139 @@ DWORD LoadCdnBuildFile(TCascStorage * hs) assert(hs->CdnBuildKey.pbData != NULL && hs->CdnBuildKey.cbData == MD5_HASH_SIZE); // Inform the user about what we are doing - if(InvokeProgressCallback(hs, "Downloading CDN build file", NULL, 0, 0)) + if(InvokeProgressCallback(hs, "Loading CDN build file", NULL, 0, 0)) return ERROR_CANCELLED; // Load the CDN config file. Note that we don't // need it for anything, really, so we don't care if it fails return FetchAndLoadConfigFile(hs, &hs->CdnBuildKey, ParseFile_CdnBuild); } + +LPBYTE LoadInternalFileToMemory(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD * pcbFileData) +{ + LPBYTE pbFileData = NULL; + HANDLE hFile = NULL; + DWORD dwFileSizeHi = 0; + DWORD cbFileData = 0; + DWORD dwBytesRead = 0; + DWORD dwErrCode = ERROR_SUCCESS; + + // Open the file either by CKey or by EKey + if(OpenFileByCKeyEntry(hs, pCKeyEntry, CASC_STRICT_DATA_CHECK, &hFile)) + { + // Make the file not cached. We always load the entire file to memory, + // so no need to cache (and needlessly copy from one buffer to another) + SetCacheStrategy(hFile, CascCacheNothing); + + // Retrieve the size of the file. Note that the caller might specify + // the real size of the file, in case the file size is not retrievable + // or if the size is wrong. Example: ENCODING file has size specified in BUILD + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + { + cbFileData = CascGetFileSize(hFile, &dwFileSizeHi); + if(cbFileData == CASC_INVALID_SIZE || dwFileSizeHi != 0) + dwErrCode = ERROR_FILE_CORRUPT; + } + else + { + cbFileData = pCKeyEntry->ContentSize; + } + + // Retrieve the size of the ENCODING file + if(dwErrCode == ERROR_SUCCESS) + { + // Allocate space for the ENCODING file + pbFileData = CASC_ALLOC<BYTE>(cbFileData); + if(pbFileData != NULL) + { + // Read the entire file to memory + CascReadFile(hFile, pbFileData, cbFileData, &dwBytesRead); + if(dwBytesRead != cbFileData) + { + dwErrCode = ERROR_FILE_CORRUPT; + } + } + else + { + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + } + } + + // Close the file + CascCloseFile(hFile); + } + else + { + dwErrCode = GetLastError(); + } + + // Handle errors + if(dwErrCode != ERROR_SUCCESS) + { + // Free the file data + CASC_FREE(pbFileData); + cbFileData = 0; + + // Set the last error + SetLastError(dwErrCode); + } + + // Give the loaded file length + if(pcbFileData != NULL) + *pcbFileData = cbFileData; + return pbFileData; +} + +LPBYTE LoadFileToMemory(LPCTSTR szFileName, DWORD * pcbFileData) +{ + TFileStream * pStream; + ULONGLONG FileSize = 0; + LPBYTE pbFileData = NULL; + DWORD cbFileData = 0; + + // Open the stream for read-only access and read the file + // Note that this fails when the game is running (sharing violation). + pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE); + if(pStream != NULL) + { + // Retrieve the file size + FileStream_GetSize(pStream, &FileSize); + cbFileData = (DWORD)FileSize; + + // Do not load zero files or too larget files + if(0 < FileSize && FileSize <= 0x2000000) + { + // Allocate file data buffer. Make it 1 byte longer + // so string functions can put '\0' there + pbFileData = CASC_ALLOC<BYTE>(cbFileData + 1); + if(pbFileData != NULL) + { + if(!FileStream_Read(pStream, NULL, pbFileData, cbFileData)) + { + CASC_FREE(pbFileData); + cbFileData = 0; + } + } + else + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + cbFileData = 0; + } + } + else + { + SetLastError(ERROR_BAD_FORMAT); + cbFileData = 0; + assert(false); + } + + // Close the file stream + FileStream_Close(pStream); + } + + // Give out values + if(pcbFileData != NULL) + pcbFileData[0] = cbFileData; + return pbFileData; +} + diff --git a/dep/CascLib/src/CascFindFile.cpp b/dep/CascLib/src/CascFindFile.cpp index 129403188c5..f33541f07d0 100644 --- a/dep/CascLib/src/CascFindFile.cpp +++ b/dep/CascLib/src/CascFindFile.cpp @@ -24,49 +24,52 @@ static void ResetFindData(PCASC_FIND_DATA pFindData) pFindData->szFileName[0] = 0; pFindData->szPlainName = pFindData->szFileName; pFindData->TagBitMask = 0; + pFindData->FileSize = CASC_INVALID_SIZE64; pFindData->dwFileDataId = CASC_INVALID_ID; - pFindData->dwFileSize = CASC_INVALID_SIZE; pFindData->dwLocaleFlags = CASC_INVALID_ID; pFindData->dwContentFlags = CASC_INVALID_ID; + pFindData->dwSpanCount = 1; pFindData->NameType = CascNameFull; pFindData->bFileAvailable = false; - pFindData->bCanOpenByName = false; - pFindData->bCanOpenByDataId = false; - pFindData->bCanOpenByCKey = false; - pFindData->bCanOpenByEKey = false; } -static void SupplyFakeFileName(PCASC_FIND_DATA pFindData) +static void SupplyFakeFileName(PCASC_FIND_DATA pFindData, PCASC_CKEY_ENTRY pCKeyEntry) { - // If the file can be open by file data id, create fake file name - if(pFindData->bCanOpenByDataId) + // If there is a file data ID, create fake file name + if(pFindData->dwFileDataId != CASC_INVALID_ID) { CascStrPrintf(pFindData->szFileName, _countof(pFindData->szFileName), "FILE%08X.dat", pFindData->dwFileDataId); pFindData->NameType = CascNameDataId; return; } - // If the file can be open by CKey, convert the CKey to file name - if(pFindData->bCanOpenByCKey) + // If there is a CKey, convert the CKey to file name + if(pCKeyEntry->Flags & CASC_CE_HAS_CKEY) { StringFromBinary(pFindData->CKey, MD5_HASH_SIZE, pFindData->szFileName); pFindData->NameType = CascNameCKey; return; } - // CKey should be always present - StringFromBinary(pFindData->EKey, MD5_HASH_SIZE, pFindData->szFileName); - pFindData->NameType = CascNameEKey; - assert(pFindData->bCanOpenByEKey != false); + // EKey should be always present + if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY) + { + StringFromBinary(pFindData->EKey, MD5_HASH_SIZE, pFindData->szFileName); + pFindData->NameType = CascNameEKey; + return; + } + + assert(false); } static bool CopyCKeyEntryToFindData(PCASC_FIND_DATA pFindData, PCASC_CKEY_ENTRY pCKeyEntry) { + ULONGLONG ContentSize = 0; + ULONGLONG EncodedSize = 0; + // Supply both keys CopyMemory16(pFindData->CKey, pCKeyEntry->CKey); CopyMemory16(pFindData->EKey, pCKeyEntry->EKey); - pFindData->bCanOpenByCKey = (pCKeyEntry->Flags & CASC_CE_HAS_CKEY) ? true : false; - pFindData->bCanOpenByEKey = (pCKeyEntry->Flags & CASC_CE_HAS_EKEY) ? true : false; // Supply the tag mask pFindData->TagBitMask = pCKeyEntry->TagBitMask; @@ -77,15 +80,21 @@ static bool CopyCKeyEntryToFindData(PCASC_FIND_DATA pFindData, PCASC_CKEY_ENTRY // If we retrieved the file size directly from the root provider, use it // Otherwise, supply EncodedSize or ContentSize, whichever is available (but ContentSize > EncodedSize) - if(pFindData->dwFileSize == CASC_INVALID_SIZE) - pFindData->dwFileSize = pCKeyEntry->ContentSize; + pFindData->dwSpanCount = GetFileSpanInfo(pCKeyEntry, &ContentSize, &EncodedSize); + if(pFindData->FileSize == CASC_INVALID_SIZE64) + { + if(ContentSize != CASC_INVALID_SIZE64) + pFindData->FileSize = ContentSize; + else + pFindData->FileSize = EncodedSize; + } // Set flag indicating that the file is locally available pFindData->bFileAvailable = (pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL); // Supply a fake file name, if there is none supplied by the root handler if(pFindData->szFileName[0] == 0) - SupplyFakeFileName(pFindData); + SupplyFakeFileName(pFindData, pCKeyEntry); return true; } @@ -127,9 +136,12 @@ static bool DoStorageSearch_CKey(TCascSearch * pSearch, PCASC_FIND_DATA pFindDat // Check for CKeys that haven't been found yet while(pSearch->nFileIndex < nTotalItems) { - // Locate the n-th CKey entry. If this entry is not referenced by the root handler, we include it in the search result + // Locate the n-th CKey entry. pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(pSearch->nFileIndex++); - if((pCKeyEntry->Flags & CASC_CE_FOLDER_ENTRY) == 0 && pCKeyEntry->RefCount == 0) +// BREAK_ON_XKEY3(pCKeyEntry->CKey, 0x2B, 0xfc, 0xe4); + + // Only report files that are unreferenced by the ROOT handler + if(pCKeyEntry->IsFile() && pCKeyEntry->RefCount == 0) { return CopyCKeyEntryToFindData(pFindData, pCKeyEntry); } @@ -189,31 +201,31 @@ HANDLE WINAPI CascFindFirstFile( { TCascStorage * hs; TCascSearch * pSearch = NULL; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Check parameters if((hs = TCascStorage::IsValid(hStorage)) == NULL) - nError = ERROR_INVALID_HANDLE; + dwErrCode = ERROR_INVALID_HANDLE; if(szMask == NULL || pFindData == NULL) - nError = ERROR_INVALID_PARAMETER; + dwErrCode = ERROR_INVALID_PARAMETER; // Init the search structure and search handle - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { // Allocate the search handle pSearch = new TCascSearch(hs, szListFile, szMask); if(pSearch == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } // Perform search - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { if(!DoStorageSearch(pSearch, pFindData)) - nError = ERROR_NO_MORE_FILES; + dwErrCode = ERROR_NO_MORE_FILES; } - if(nError != ERROR_SUCCESS) + if(dwErrCode != ERROR_SUCCESS) { delete pSearch; pSearch = (TCascSearch *)INVALID_HANDLE_VALUE; diff --git a/dep/CascLib/src/CascIndexFiles.cpp b/dep/CascLib/src/CascIndexFiles.cpp index ed680f4c4d9..9cab9c6a901 100644 --- a/dep/CascLib/src/CascIndexFiles.cpp +++ b/dep/CascLib/src/CascIndexFiles.cpp @@ -129,6 +129,17 @@ static TCHAR * CreateIndexFileName(TCascStorage * hs, DWORD IndexValue, DWORD In return CombinePath(hs->szIndexPath, szPlainName); } +static void SaveFileOffsetBitsAndEKeyLength(TCascStorage * hs, BYTE FileOffsetBits, BYTE EKeyLength) +{ + if(hs->FileOffsetBits == 0) + hs->FileOffsetBits = FileOffsetBits; + assert(hs->FileOffsetBits == FileOffsetBits); + + if(hs->EKeyLength == 0) + hs->EKeyLength = EKeyLength; + assert(hs->EKeyLength == EKeyLength); +} + // Verifies a guarded block - data availability and checksum match static LPBYTE CaptureGuardedBlock1(LPBYTE pbFileData, LPBYTE pbFileEnd) { @@ -206,74 +217,84 @@ static LPBYTE CaptureGuardedBlock3(LPBYTE pbFileData, LPBYTE pbFileEnd, size_t E return (LPBYTE)(PtrEntryHash + 1); } -static bool CaptureIndexEntry(CASC_INDEX_HEADER & InHeader, PCASC_CKEY_ENTRY pCKeyEntry, LPBYTE pbEKeyEntry) +static bool CaptureIndexEntry(CASC_INDEX_HEADER & InHeader, PCASC_EKEY_ENTRY pEKeyEntry, LPBYTE pbEKeyEntry) { - // Zero both CKey and EKey - ZeroMemory16(pCKeyEntry->CKey); - ZeroMemory16(pCKeyEntry->EKey); - - // Copy the EKey. We assume 9 bytes - pCKeyEntry->EKey[0x00] = pbEKeyEntry[0]; - pCKeyEntry->EKey[0x01] = pbEKeyEntry[1]; - pCKeyEntry->EKey[0x02] = pbEKeyEntry[2]; - pCKeyEntry->EKey[0x03] = pbEKeyEntry[3]; - pCKeyEntry->EKey[0x04] = pbEKeyEntry[4]; - pCKeyEntry->EKey[0x05] = pbEKeyEntry[5]; - pCKeyEntry->EKey[0x06] = pbEKeyEntry[6]; - pCKeyEntry->EKey[0x07] = pbEKeyEntry[7]; - pCKeyEntry->EKey[0x08] = pbEKeyEntry[8]; - pCKeyEntry->EKey[0x09] = pbEKeyEntry[9]; - pbEKeyEntry += InHeader.EKeyLength; - - // Copy the storage offset - pCKeyEntry->StorageOffset = ConvertBytesToInteger_5(pbEKeyEntry); - pbEKeyEntry += InHeader.StorageOffsetLength; - - // Clear the tag bit mask - pCKeyEntry->TagBitMask = 0; - - // Copy the encoded length - pCKeyEntry->EncodedSize = ConvertBytesToInteger_4_LE(pbEKeyEntry); - pCKeyEntry->ContentSize = CASC_INVALID_SIZE; - pCKeyEntry->RefCount = 0; - pCKeyEntry->Priority = 0; - pCKeyEntry->Flags = CASC_CE_FILE_IS_LOCAL | CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL; + // Copy the EKey of the variable length + pbEKeyEntry = CaptureEncodedKey(pEKeyEntry->EKey, pbEKeyEntry, InHeader.EKeyLength); + + // Copy the storage offset and encoded size + pEKeyEntry->StorageOffset = ConvertBytesToInteger_5(pbEKeyEntry); + pEKeyEntry->EncodedSize = ConvertBytesToInteger_4_LE(pbEKeyEntry + InHeader.StorageOffsetLength); + pEKeyEntry->Alignment = 0; // We ignore items that have EncodedSize of 0x1E - return (pCKeyEntry->EncodedSize > FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature)); + return (pEKeyEntry->EncodedSize > FIELD_OFFSET(BLTE_ENCODED_HEADER, Signature)); } -static void CheckForEncodingManifestCKey(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry) +static void InsertCKeyEntry(TCascStorage * hs, CASC_EKEY_ENTRY & EKeyEntry, DWORD Flags) { - // If the encoding file was not found yet - if(hs->EncodingCKey.StorageOffset == CASC_INVALID_OFFS64) + PCASC_CKEY_ENTRY pCKeyEntry; + + // Multiple items with the same EKey in the index files may exist. + // Example: "2018 - New CASC\00001", EKey 37 89 16 5b 2d cc 71 c1 25 00 00 00 00 00 00 00 + // Positions: 0x1D, 0x1E, 0x1F + // In that case, we only take the first one into account + // BREAK_ON_XKEY3(EKeyEntry.EKey, 0x09, 0xF3, 0xCD); + + // If the item is not there yet, insert a new one + if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKeyEntry.EKey)) == NULL) + { + // Insert a new entry to the array. DO NOT ALLOW enlarge array here + pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); + if(pCKeyEntry == NULL) + return; + + // Fill-in the information + ZeroMemory16(pCKeyEntry->CKey); + CopyMemory16(pCKeyEntry->EKey, EKeyEntry.EKey); + pCKeyEntry->StorageOffset = EKeyEntry.StorageOffset; + pCKeyEntry->TagBitMask = 0; + pCKeyEntry->ContentSize = CASC_INVALID_SIZE; + pCKeyEntry->EncodedSize = EKeyEntry.EncodedSize; + pCKeyEntry->Flags = CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL; + pCKeyEntry->RefCount = 0; + pCKeyEntry->SpanCount = 1; + pCKeyEntry->Priority = 0; + + // Insert the item to the EKey table + hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); + } + else { - if(!memcmp(pCKeyEntry->EKey, hs->EncodingCKey.EKey, hs->EKeyLength)) + // The entry already exists. True e.g. for ENCODING. + // Only copy the storage offset and sizes if not available yet + if(pCKeyEntry->StorageOffset == CASC_INVALID_OFFS64) { - hs->EncodingCKey.StorageOffset = pCKeyEntry->StorageOffset; - hs->EncodingCKey.EncodedSize = pCKeyEntry->EncodedSize; - hs->EncodingCKey.Flags |= CASC_CE_FILE_IS_LOCAL; + pCKeyEntry->StorageOffset = EKeyEntry.StorageOffset; + pCKeyEntry->EncodedSize = EKeyEntry.EncodedSize; } } + + // Add the extra flag + pCKeyEntry->Flags |= Flags; } -static int LoadIndexItems(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbEKeyEntry, LPBYTE pbEKeyEnd) +static DWORD LoadIndexItems(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbEKeyEntry, LPBYTE pbEKeyEnd) { size_t EntryLength = InHeader.EntryLength; while((pbEKeyEntry + EntryLength) <= pbEKeyEnd) { - CASC_CKEY_ENTRY CKeyEntry; + CASC_EKEY_ENTRY EKeyEntry; // Capture the index entry and verify it. - if(CaptureIndexEntry(InHeader, &CKeyEntry, pbEKeyEntry)) + if(CaptureIndexEntry(InHeader, &EKeyEntry, pbEKeyEntry)) { - // Insert new entry to the array of CKey entries - if(hs->IndexArray.Insert(&CKeyEntry, 1) == NULL) - return ERROR_NOT_ENOUGH_MEMORY; + // DOWNLOAD in HOTS + //BREAK_ON_XKEY3(EKeyEntry.EKey, 0x09, 0xF3, 0xCD); - // Verify whether the key is not a CKEy entry for ENCODING file - CheckForEncodingManifestCKey(hs, &CKeyEntry); + // Insert the index entry to the central table + InsertCKeyEntry(hs, EKeyEntry, CASC_CE_FILE_IS_LOCAL); } pbEKeyEntry += EntryLength; @@ -282,7 +303,7 @@ static int LoadIndexItems(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYT return ERROR_SUCCESS; } -static int CaptureIndexHeader_V1(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) +static DWORD CaptureIndexHeader_V1(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) { PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData; LPBYTE pbKeyEntries; @@ -342,7 +363,7 @@ static int CaptureIndexHeader_V1(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData return ERROR_SUCCESS; } -static int CaptureIndexHeader_V2(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) +static DWORD CaptureIndexHeader_V2(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) { PFILE_INDEX_HEADER_V2 pIndexHeader; LPBYTE pbFileEnd = pbFileData + cbFileData; @@ -376,30 +397,28 @@ static int CaptureIndexHeader_V2(CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData return ERROR_SUCCESS; } -static int LoadIndexFile_V1(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData) +static DWORD LoadIndexFile_V1(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData) { LPBYTE pbEKeyEntries = pbFileData + InHeader.HeaderLength + InHeader.HeaderPadding; // Remember the values from the index header - hs->FileOffsetBits = InHeader.FileOffsetBits; - hs->EKeyLength = InHeader.EKeyLength; + SaveFileOffsetBitsAndEKeyLength(hs, InHeader.FileOffsetBits, InHeader.EKeyLength); // Load the entries from a continuous array return LoadIndexItems(hs, InHeader, pbEKeyEntries, pbFileData + cbFileData); } -static int LoadIndexFile_V2(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData) +static DWORD LoadIndexFile_V2(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPBYTE pbFileData, DWORD cbFileData) { LPBYTE pbEKeyEntry; LPBYTE pbFileEnd = pbFileData + cbFileData; LPBYTE pbFilePtr = pbFileData + InHeader.HeaderLength + InHeader.HeaderPadding; size_t EKeyEntriesLength; DWORD BlockSize = 0; - int nError = ERROR_NOT_SUPPORTED; + DWORD dwErrCode = ERROR_NOT_SUPPORTED; // Remember the values from the index header - hs->FileOffsetBits = InHeader.FileOffsetBits; - hs->EKeyLength = InHeader.EKeyLength; + SaveFileOffsetBitsAndEKeyLength(hs, InHeader.FileOffsetBits, InHeader.EKeyLength); // Get the pointer to the first block of EKey entries if((pbEKeyEntry = CaptureGuardedBlock2(pbFilePtr, pbFileEnd, InHeader.EntryLength, &BlockSize)) != NULL) @@ -427,7 +446,7 @@ static int LoadIndexFile_V2(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPB while(pbEKeyEntry < pbEndPage) { - CASC_CKEY_ENTRY CKeyEntry; + CASC_EKEY_ENTRY EKeyEntry; // Check the EKey entry protected by 32-bit hash if((pbEKeyEntry = CaptureGuardedBlock3(pbEKeyEntry, pbEndPage, InHeader.EntryLength)) == NULL) @@ -437,14 +456,9 @@ static int LoadIndexFile_V2(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPB //BREAK_ON_XKEY3(pbEKeyEntry, 0xbc, 0xe8, 0x23); // Capture the index entry and verify it. - if(CaptureIndexEntry(InHeader, &CKeyEntry, pbEKeyEntry)) + if(CaptureIndexEntry(InHeader, &EKeyEntry, pbEKeyEntry)) { - // Insert the EKey entry to the array - if(hs->IndexArray.Insert(&CKeyEntry, 1) == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Check whether the CKey entry is an encoding entry - CheckForEncodingManifestCKey(hs, &CKeyEntry); + InsertCKeyEntry(hs, EKeyEntry, CASC_CE_FILE_IS_LOCAL); } // Move to the next entry @@ -454,13 +468,13 @@ static int LoadIndexFile_V2(TCascStorage * hs, CASC_INDEX_HEADER & InHeader, LPB // Move to the next chunk pbStartPage += FILE_INDEX_PAGE_SIZE; } - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } - return nError; + return dwErrCode; } -static int LoadIndexFile(TCascStorage * hs, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) +static DWORD LoadIndexFile(TCascStorage * hs, LPBYTE pbFileData, DWORD cbFileData, DWORD BucketIndex) { CASC_INDEX_HEADER InHeader; @@ -477,76 +491,73 @@ static int LoadIndexFile(TCascStorage * hs, LPBYTE pbFileData, DWORD cbFileData, return ERROR_BAD_FORMAT; } -static int LoadIndexFile(TCascStorage * hs, const TCHAR * szFileName, DWORD BucketIndex) +static DWORD LoadIndexFile(TCascStorage * hs, const TCHAR * szFileName, DWORD BucketIndex) { LPBYTE pbFileData; DWORD cbFileData; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // WoW6 actually reads THE ENTIRE file to memory. Verified on Mac build (x64). pbFileData = LoadFileToMemory(szFileName, &cbFileData); if(pbFileData && cbFileData) { // Parse and load the index file - nError = LoadIndexFile(hs, pbFileData, cbFileData, BucketIndex); + dwErrCode = LoadIndexFile(hs, pbFileData, cbFileData, BucketIndex); CASC_FREE(pbFileData); } else { - nError = GetLastError(); + dwErrCode = GetLastError(); } - return nError; + return dwErrCode; } -static int LoadLocalIndexFiles(TCascStorage * hs) +static DWORD LoadLocalIndexFiles(TCascStorage * hs) { TCHAR * szFileName; DWORD OldIndexArray[CASC_INDEX_COUNT]; DWORD IndexArray[CASC_INDEX_COUNT]; - int nError; + DWORD dwErrCode; // Scan all index files and load contained EKEY entries memset(OldIndexArray, 0, sizeof(OldIndexArray)); memset(IndexArray, 0, sizeof(IndexArray)); - nError = ScanIndexDirectory(hs->szIndexPath, IndexDirectory_OnFileFound, IndexArray, OldIndexArray, hs); - if(nError == ERROR_SUCCESS) + dwErrCode = ScanIndexDirectory(hs->szIndexPath, IndexDirectory_OnFileFound, IndexArray, OldIndexArray, hs); + if(dwErrCode == ERROR_SUCCESS) { - // Initialize the array of index files - if((nError = hs->IndexArray.Create(sizeof(CASC_CKEY_ENTRY), 0x200000)) == ERROR_SUCCESS) + // Load each index file + for(DWORD i = 0; i < CASC_INDEX_COUNT; i++) { - // Load each index file - for(DWORD i = 0; i < CASC_INDEX_COUNT; i++) + // Create the name of the index file + if((szFileName = CreateIndexFileName(hs, i, IndexArray[i])) != NULL) { - // Create the name of the index file - if((szFileName = CreateIndexFileName(hs, i, IndexArray[i])) != NULL) + // Inform the user about what we are doing + if(InvokeProgressCallback(hs, "Loading index files", NULL, i, CASC_INDEX_COUNT)) { - // Inform the user about what we are doing - if(InvokeProgressCallback(hs, "Loading index files", NULL, i, CASC_INDEX_COUNT)) - { - nError = ERROR_CANCELLED; - break; - } - - // Load the index file - if((nError = LoadIndexFile(hs, szFileName, i)) != ERROR_SUCCESS) - break; - CASC_FREE(szFileName); + dwErrCode = ERROR_CANCELLED; + break; } - } - // Remember the number of files that are present locally - hs->LocalFiles = hs->IndexArray.ItemCount(); + // Load the index file + if((dwErrCode = LoadIndexFile(hs, szFileName, i)) != ERROR_SUCCESS) + break; + CASC_FREE(szFileName); + } } + + // Remember the number of files that are present locally + hs->LocalFiles = hs->CKeyArray.ItemCount(); } - return nError; + return dwErrCode; } //----------------------------------------------------------------------------- // Online index files +// https://wowdev.wiki/TACT#CDN_File_Organization -static int CaptureArcIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, DWORD cbIndexFile) +static DWORD CaptureArcIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, DWORD cbIndexFile) { FILE_INDEX_FOOTER<0x08> * pFooter08; BYTE checksum_data[0x40] = { 0 }; @@ -566,10 +577,10 @@ static int CaptureArcIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndex InFooter.Version = pFooter08->Version; InFooter.OffsetBytes = pFooter08->OffsetBytes; InFooter.SizeBytes = pFooter08->SizeBytes; - InFooter.EKeyBytes = pFooter08->EKeySizeBytes; + InFooter.EKeyLength = pFooter08->EKeyLength; InFooter.FooterHashBytes = pFooter08->FooterHashBytes; InFooter.PageLength = pFooter08->PageSizeKB << 10; - InFooter.ItemLength = pFooter08->EKeySizeBytes + pFooter08->OffsetBytes + pFooter08->SizeBytes; + InFooter.ItemLength = pFooter08->EKeyLength + pFooter08->OffsetBytes + pFooter08->SizeBytes; InFooter.FooterLength = sizeof(FILE_INDEX_FOOTER<0x08>); InFooter.ElementCount = ConvertBytesToInteger_4_LE(pFooter08->ElementCount); @@ -585,30 +596,33 @@ static int CaptureArcIndexFooter(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndex return ERROR_BAD_FORMAT; } -static int CaptureArcIndexEntry(CASC_ARCINDEX_FOOTER & InFooter, CASC_ARCINDEX_ENTRY & InEntry, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd) +static DWORD CaptureIndexEntry(CASC_ARCINDEX_FOOTER & InFooter, CASC_EKEY_ENTRY & EKeyEntry, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd, size_t nArchive) { + ULONGLONG StorageOffset = nArchive; + ULONGLONG ArchiveOffset; + // If there enough bytes for one entry/ if ((pbIndexPage + InFooter.ItemLength) > pbIndexPageEnd) return ERROR_BAD_FORMAT; - // Copy the item - memcpy(InEntry.EKey, pbIndexPage, InFooter.EKeyBytes); - pbIndexPage += InFooter.EKeyBytes; + // Capture the EKey (variable length) + pbIndexPage = CaptureEncodedKey(EKeyEntry.EKey, pbIndexPage, InFooter.EKeyLength); // Copy the archive offset - InEntry.EncodedSize = ConvertBytesToInteger_X(pbIndexPage, InFooter.OffsetBytes); - pbIndexPage += InFooter.OffsetBytes; - - // Copy thefile encoded size - InEntry.ArchiveOffset = ConvertBytesToInteger_X(pbIndexPage, InFooter.SizeBytes); - if (InEntry.ArchiveOffset >= 0x10000000) + ArchiveOffset = ConvertBytesToInteger_X(pbIndexPage + InFooter.SizeBytes, InFooter.OffsetBytes); + if (ArchiveOffset >= 0x10000000) return ERROR_BAD_FORMAT; + // Capture the storage offset and encoded size + EKeyEntry.StorageOffset = (StorageOffset << (InFooter.OffsetBytes * 8)) | ArchiveOffset; + EKeyEntry.EncodedSize = ConvertBytesToInteger_X(pbIndexPage, InFooter.SizeBytes); + EKeyEntry.Alignment = 0; + // Is there a valid hash? - return CascIsValidMD5(InEntry.EKey) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + return CascIsValidMD5(EKeyEntry.EKey) ? ERROR_SUCCESS : ERROR_BAD_FORMAT; } -static int VerifyIndexSize(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, size_t cbIndexFile, LPBYTE * PtrIndexEnd) +static DWORD VerifyIndexSize(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, size_t cbIndexFile, LPBYTE * PtrIndexEnd) { size_t nPageCount; @@ -626,21 +640,20 @@ static int VerifyIndexSize(CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexFile, return ERROR_SUCCESS; } -static int LoadArchiveIndexPage(TCascStorage * hs, CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexHash, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd) +static DWORD LoadArchiveIndexPage(TCascStorage * hs, CASC_ARCINDEX_FOOTER & InFooter, LPBYTE pbIndexPage, LPBYTE pbIndexPageEnd, size_t nArchive) { - CASC_ARCINDEX_ENTRY InEntry; - int nError; + CASC_EKEY_ENTRY EKeyEntry; + DWORD dwErrCode; - while (pbIndexPage < pbIndexPageEnd) + while (pbIndexPage <= pbIndexPageEnd) { // Capture the index entry - nError = CaptureArcIndexEntry(InFooter, InEntry, pbIndexPage, pbIndexPageEnd); - if (nError != ERROR_SUCCESS) + dwErrCode = CaptureIndexEntry(InFooter, EKeyEntry, pbIndexPage, pbIndexPageEnd, nArchive); + if (dwErrCode != ERROR_SUCCESS) break; - // Insert the index entry to the array - memcpy(InEntry.IndexHash, pbIndexHash, MD5_HASH_SIZE); - if (hs->ArcIndexArray.Insert(&InEntry, 1) == NULL) + // Insert a new entry to the index array + if((hs->IndexArray.Insert(&EKeyEntry, 1)) == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Move to the next entry @@ -650,28 +663,31 @@ static int LoadArchiveIndexPage(TCascStorage * hs, CASC_ARCINDEX_FOOTER & InFoot return ERROR_SUCCESS; } -static int LoadArchiveIndexFile(TCascStorage * hs, LPBYTE pbIndexHash, LPBYTE pbIndexFile, DWORD cbIndexFile) +static DWORD LoadArchiveIndexFile(TCascStorage * hs, LPBYTE pbIndexFile, DWORD cbIndexFile, size_t nArchive) { CASC_ARCINDEX_FOOTER InFooter; LPBYTE pbIndexEnd = NULL; - int nError; + DWORD dwErrCode; // Validate and capture the footer - nError = CaptureArcIndexFooter(InFooter, pbIndexFile, cbIndexFile); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = CaptureArcIndexFooter(InFooter, pbIndexFile, cbIndexFile); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; + + // Remember the file offset and EKey length + SaveFileOffsetBitsAndEKeyLength(hs, InFooter.OffsetBytes * 8, InFooter.EKeyLength); // Verify the size of the index file - nError = VerifyIndexSize(InFooter, pbIndexFile, cbIndexFile, &pbIndexEnd); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = VerifyIndexSize(InFooter, pbIndexFile, cbIndexFile, &pbIndexEnd); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Parse all pages while (pbIndexFile < pbIndexEnd) { // Load the entire page - nError = LoadArchiveIndexPage(hs, InFooter, pbIndexHash, pbIndexFile, pbIndexFile + InFooter.PageLength); - if (nError != ERROR_SUCCESS) + dwErrCode = LoadArchiveIndexPage(hs, InFooter, pbIndexFile, pbIndexFile + InFooter.PageLength, nArchive); + if (dwErrCode != ERROR_SUCCESS) break; // Move to the next page @@ -681,87 +697,95 @@ static int LoadArchiveIndexFile(TCascStorage * hs, LPBYTE pbIndexHash, LPBYTE pb return ERROR_SUCCESS; } -static int BuildMapOfArcIndices(TCascStorage * hs) +static DWORD BuildMapOfArchiveIndices(TCascStorage * hs) { - PCASC_ARCINDEX_ENTRY pEntry; - size_t nItemCount = hs->ArcIndexArray.ItemCount(); - int nError; + PCASC_EKEY_ENTRY pEKeyEntry; + size_t nItemCount = hs->IndexArray.ItemCount(); + DWORD dwErrCode; // Create the map - nError = hs->ArcIndexMap.Create(nItemCount, MD5_HASH_SIZE, FIELD_OFFSET(CASC_ARCINDEX_ENTRY, EKey)); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = hs->IndexMap.Create(nItemCount, MD5_HASH_SIZE, FIELD_OFFSET(CASC_EKEY_ENTRY, EKey)); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Insert all items for(size_t i = 0; i < nItemCount; i++) { - pEntry = (PCASC_ARCINDEX_ENTRY)hs->ArcIndexArray.ItemAt(i); - if (pEntry != NULL) + pEKeyEntry = (PCASC_EKEY_ENTRY)hs->IndexArray.ItemAt(i); + if (pEKeyEntry != NULL) { - if (!hs->ArcIndexMap.InsertObject(pEntry, pEntry->EKey)) + if (!hs->IndexMap.InsertObject(pEKeyEntry, pEKeyEntry->EKey)) { return ERROR_NOT_ENOUGH_MEMORY; } } } - return nError; + return dwErrCode; } -static int LoadArchiveIndexFiles(TCascStorage * hs) +static DWORD LoadArchiveIndexFiles(TCascStorage * hs) { LPBYTE pbFileData; TCHAR szLocalPath[MAX_PATH]; DWORD cbFileData = 0; size_t nArchiveCount = (hs->ArchivesKey.cbData / MD5_HASH_SIZE); - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Create the array object for the indices - nError = hs->ArcIndexArray.Create(sizeof(CASC_ARCINDEX_ENTRY), 10000); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = hs->IndexArray.Create(sizeof(CASC_EKEY_ENTRY), 0x10000); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Load all the indices for (size_t i = 0; i < nArchiveCount; i++) { + CASC_CDN_DOWNLOAD CdnsInfo = {0}; LPBYTE pbIndexHash = hs->ArchivesKey.pbData + (i * MD5_HASH_SIZE); // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Downloading archive indexes", NULL, (DWORD)(i), (DWORD)(nArchiveCount))) { - nError = ERROR_CANCELLED; + dwErrCode = ERROR_CANCELLED; break; } - // Make sure that we have local copy of the file - nError = DownloadFileFromCDN(hs, _T("data"), pbIndexHash, _T(".index"), szLocalPath, _countof(szLocalPath)); - if (nError == ERROR_SUCCESS) + // Prepare the download structure for "%CDNS_HOST%/%CDNS_PATH%/##/##/EKey" file + CdnsInfo.szCdnsPath = hs->szCdnPath; + CdnsInfo.szPathType = _T("data"); + CdnsInfo.pbEKey = pbIndexHash; + CdnsInfo.szExtension = _T(".index"); + CdnsInfo.szLocalPath = szLocalPath; + CdnsInfo.ccLocalPath = _countof(szLocalPath); + dwErrCode = DownloadFileFromCDN(hs, CdnsInfo); + + // Load and parse the archive index + if (dwErrCode == ERROR_SUCCESS) { // Load the index file to memory pbFileData = LoadFileToMemory(szLocalPath, &cbFileData); if (pbFileData && cbFileData) { - nError = LoadArchiveIndexFile(hs, pbIndexHash, pbFileData, cbFileData); + dwErrCode = LoadArchiveIndexFile(hs, pbFileData, cbFileData, i); CASC_FREE(pbFileData); } } // Break if an error - if (nError != ERROR_SUCCESS) + if (dwErrCode != ERROR_SUCCESS) break; } - // Build map of EKey -> CASC_ARCINDEX_ENTRY - if (nError == ERROR_SUCCESS) - nError = BuildMapOfArcIndices(hs); - - return nError; + // Build map of EKey -> CASC_EKEY_ENTRY + if (dwErrCode == ERROR_SUCCESS) + dwErrCode = BuildMapOfArchiveIndices(hs); + return dwErrCode; } //----------------------------------------------------------------------------- // Public functions -int LoadIndexFiles(TCascStorage * hs) +DWORD LoadIndexFiles(TCascStorage * hs) { if (hs->dwFeatures & CASC_FEATURE_ONLINE) { diff --git a/dep/CascLib/src/CascLib.def b/dep/CascLib/src/CascLib.def index fbed70bf128..0359dfa3a2e 100644 --- a/dep/CascLib/src/CascLib.def +++ b/dep/CascLib/src/CascLib.def @@ -13,13 +13,17 @@ EXPORTS CascOpenOnlineStorage CascGetStorageInfo CascAddEncryptionKey + CascAddStringEncryptionKey CascFindEncryptionKey CascCloseStorage CascOpenFile + CascOpenLocalFile CascGetFileInfo CascGetFileSize + CascGetFileSize64 CascSetFilePointer + CascSetFilePointer64 CascReadFile CascCloseFile @@ -29,4 +33,3 @@ EXPORTS GetLastError=Kernel32.GetLastError SetLastError=Kernel32.SetLastError -
\ No newline at end of file diff --git a/dep/CascLib/src/CascLib.h b/dep/CascLib/src/CascLib.h index 1ed387aee01..90be65a00b2 100644 --- a/dep/CascLib/src/CascLib.h +++ b/dep/CascLib/src/CascLib.h @@ -16,7 +16,7 @@ #define __CASCLIB_H__ #ifdef _MSC_VER -#pragma warning(disable:4668) // 'XXX' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +#pragma warning(disable:4668) // 'XXX' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' #pragma warning(disable:4820) // 'XXX' : '2' bytes padding added after data member 'XXX::yyy' #endif @@ -41,29 +41,29 @@ extern "C" { #ifdef _DEBUG // DEBUG VERSIONS #ifndef _UNICODE #ifdef _DLL - #pragma comment(lib, "CascLibDAD.lib") // Debug Ansi CRT-DLL version + #pragma comment(lib, "CascLibDAD.lib") // Debug Ansi CRT-DLL version #else - #pragma comment(lib, "CascLibDAS.lib") // Debug Ansi CRT-LIB version + #pragma comment(lib, "CascLibDAS.lib") // Debug Ansi CRT-LIB version #endif #else #ifdef _DLL - #pragma comment(lib, "CascLibDUD.lib") // Debug Unicode CRT-DLL version + #pragma comment(lib, "CascLibDUD.lib") // Debug Unicode CRT-DLL version #else - #pragma comment(lib, "CascLibDUS.lib") // Debug Unicode CRT-LIB version + #pragma comment(lib, "CascLibDUS.lib") // Debug Unicode CRT-LIB version #endif #endif #else // RELEASE VERSIONS #ifndef _UNICODE #ifdef _DLL - #pragma comment(lib, "CascLibRAD.lib") // Release Ansi CRT-DLL version + #pragma comment(lib, "CascLibRAD.lib") // Release Ansi CRT-DLL version #else - #pragma comment(lib, "CascLibRAS.lib") // Release Ansi CRT-LIB version + #pragma comment(lib, "CascLibRAS.lib") // Release Ansi CRT-LIB version #endif #else #ifdef _DLL - #pragma comment(lib, "CascLibRUD.lib") // Release Unicode CRT-DLL version + #pragma comment(lib, "CascLibRUD.lib") // Release Unicode CRT-DLL version #else - #pragma comment(lib, "CascLibRUS.lib") // Release Unicode CRT-LIB version + #pragma comment(lib, "CascLibRUS.lib") // Release Unicode CRT-LIB version #endif #endif #endif @@ -72,14 +72,14 @@ extern "C" { //----------------------------------------------------------------------------- // Defines -#define CASCLIB_VERSION 0x0132 // Current version of CascLib (1.50) -#define CASCLIB_VERSION_STRING "1.50" // String version of CascLib version +#define CASCLIB_VERSION 0x0200 // CascLib version - integral (1.50) +#define CASCLIB_VERSION_STRING "2.0" // CascLib version - string // Values for CascOpenFile #define CASC_OPEN_BY_NAME 0x00000000 // Open the file by name. This is the default value #define CASC_OPEN_BY_CKEY 0x00000001 // The name is just the content key; skip ROOT file processing #define CASC_OPEN_BY_EKEY 0x00000002 // The name is just the encoded key; skip ROOT file processing -#define CASC_OPEN_BY_FILEID 0x00000003 // The name is CASC_IDTONAME(FileDataId) +#define CASC_OPEN_BY_FILEID 0x00000003 // The name is CASC_FILE_DATA_ID(FileDataId) #define CASC_OPEN_TYPE_MASK 0x0000000F // The mask which gets open type from the dwFlags #define CASC_OPEN_FLAGS_MASK 0xFFFFFFF0 // The mask which gets open type from the dwFlags #define CASC_STRICT_DATA_CHECK 0x00000010 // Verify all data read from a file @@ -119,16 +119,13 @@ extern "C" { #define MD5_STRING_SIZE 0x20 #endif -#ifndef SHA1_DIGEST_SIZE -#define SHA1_DIGEST_SIZE 0x14 // 160 bits -#endif - // Return value for CascGetFileSize and CascSetFilePointer #define CASC_INVALID_INDEX 0xFFFFFFFF #define CASC_INVALID_SIZE 0xFFFFFFFF #define CASC_INVALID_POS 0xFFFFFFFF #define CASC_INVALID_ID 0xFFFFFFFF #define CASC_INVALID_OFFS64 0xFFFFFFFFFFFFFFFF +#define CASC_INVALID_SIZE64 0xFFFFFFFFFFFFFFFF // Flags for CASC_STORAGE_FEATURES::dwFeatures #define CASC_FEATURE_FILE_NAMES 0x00000001 // File names are supported by the storage @@ -160,7 +157,7 @@ typedef enum _CASC_STORAGE_INFO_CLASS CascStorageFeatures, // Returns the features flag - CascStorageInstalledLocales, + CascStorageInstalledLocales, // Not supported CascStorageProduct, // Gives CASC_STORAGE_PRODUCT CascStorageTags, // Gives CASC_STORAGE_TAGS structure CascStoragePathProduct, // Gives Path:Product into a LPTSTR buffer @@ -173,27 +170,10 @@ typedef enum _CASC_FILE_INFO_CLASS CascFileContentKey, CascFileEncodedKey, CascFileFullInfo, // Gives CASC_FILE_FULL_INFO structure + CascFileSpanInfo, // Gives CASC_FILE_SPAN_INFO structure for each file span CascFileInfoClassMax } CASC_FILE_INFO_CLASS, *PCASC_FILE_INFO_CLASS; -// See https://wowdev.wiki/TACT#Products -typedef enum _CASC_PRODUCT -{ - UnknownProduct, - HeroesOfTheStorm, - Diablo3, - Overwatch, - StarCraft1, - StarCraft2, - WorldOfWarcraft, - WarCraft3, - Destiny2, - CallOfDutyBlackOps4, - Odin, - MaxProductValue - -} CASC_PRODUCT, *PCASC_PRODUCT; - // CascLib may provide a fake name, constructed from file data id, CKey or EKey. // This enum helps to see what name was actually returned // Note that any of these names can be passed to CascOpenFile with no extra flags @@ -221,27 +201,29 @@ typedef struct _CASC_FIND_DATA // Tag mask. Only valid if the storage supports tags, otherwise 0 ULONGLONG TagBitMask; + // Size of the file, as retrieved from CKey entry + ULONGLONG FileSize; + // Plain name of the found file. Pointing inside the 'szFileName' array char * szPlainName; // File data ID. Only valid if the storage supports file data IDs, otherwise CASC_INVALID_ID DWORD dwFileDataId; - // Size of the file, as retrieved from CKey entry or EKey entry - DWORD dwFileSize; - // Locale flags. Only valid if the storage supports locale flags, otherwise CASC_INVALID_ID DWORD dwLocaleFlags; // Content flags. Only valid if the storage supports content flags, otherwise CASC_INVALID_ID DWORD dwContentFlags; - // Hints as for which open method is suitable - DWORD bFileAvailable:1; // If true the file is available locally - DWORD bCanOpenByName:1; - DWORD bCanOpenByDataId:1; - DWORD bCanOpenByCKey:1; - DWORD bCanOpenByEKey:1; + // Span count + DWORD dwSpanCount; + + // If true the file is available locally + DWORD bFileAvailable:1; + + // Name type in 'szFileName'. In case the file name is not known, + // CascLib can put FileDataId-like name or a string representation of CKey/EKey CASC_NAME_TYPE NameType; } CASC_FIND_DATA, *PCASC_FIND_DATA; @@ -264,9 +246,8 @@ typedef struct _CASC_STORAGE_TAGS typedef struct _CASC_STORAGE_PRODUCT { - LPCSTR szProductName; - DWORD dwBuildNumber; - CASC_PRODUCT Product; + char szCodeName[0x1C]; // Code name of the product ("wowt" = "World of Warcraft PTR") + DWORD BuildNumber; // Build number. If zero, then CascLib didn't recognize build number } CASC_STORAGE_PRODUCT, *PCASC_STORAGE_PRODUCT; @@ -279,15 +260,29 @@ typedef struct _CASC_FILE_FULL_INFO ULONGLONG SegmentOffset; // Offset of the file in the segment file ("data.###") ULONGLONG TagBitMask; // Bitmask of tags. Zero if not supported ULONGLONG FileNameHash; // Hash of the file name. Zero if not supported + ULONGLONG ContentSize; // Content size of all spans + ULONGLONG EncodedSize; // Encoded size of all spans DWORD SegmentIndex; // Index of the segment file (aka 0 = "data.000") + DWORD SpanCount; // Number of spans forming the file DWORD FileDataId; // File data ID. CASC_INVALID_ID if not supported. - DWORD ContentSize; // Content size of the file - DWORD EncodedSize; // Encoded size of the file DWORD LocaleFlags; // Locale flags. CASC_INVALID_ID if not supported. DWORD ContentFlags; // Locale flags. CASC_INVALID_ID if not supported } CASC_FILE_FULL_INFO, *PCASC_FILE_FULL_INFO; +typedef struct _CASC_FILE_SPAN_INFO +{ + BYTE CKey[MD5_HASH_SIZE]; // Content key of the file span + BYTE EKey[MD5_HASH_SIZE]; // Encoded key of the file span + ULONGLONG StartOffset; // Starting offset of the file span + ULONGLONG EndOffset; // Ending offset of the file span + DWORD ArchiveIndex; // Index of the archive + DWORD ArchiveOffs; // Offset in the archive + DWORD HeaderSize; // Size of encoded frame headers + DWORD FrameCount; // Number of frames in this span + +} CASC_FILE_SPAN_INFO, *PCASC_FILE_SPAN_INFO; + //----------------------------------------------------------------------------- // Extended version of CascOpenStorage @@ -348,16 +343,21 @@ bool WINAPI CascOpenStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phSt bool WINAPI CascOpenOnlineStorage(LPCTSTR szParams, DWORD dwLocaleMask, HANDLE * phStorage); bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded); bool WINAPI CascAddEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPBYTE Key); +bool WINAPI CascAddStringEncryptionKey(HANDLE hStorage, ULONGLONG KeyName, LPCSTR szKey); LPBYTE WINAPI CascFindEncryptionKey(HANDLE hStorage, ULONGLONG KeyName); bool WINAPI CascCloseStorage(HANDLE hStorage); -bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * phFile); +bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocaleFlags, DWORD dwOpenFlags, HANDLE * PtrFileHandle); +bool WINAPI CascOpenLocalFile(LPCTSTR szFileName, DWORD dwOpenFlags, HANDLE * PtrFileHandle); bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded); -DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh); -DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod); +bool WINAPI CascGetFileSize64(HANDLE hFile, PULONGLONG PtrFileSize); +bool WINAPI CascSetFilePointer64(HANDLE hFile, LONGLONG DistanceToMove, PULONGLONG PtrNewPos, DWORD dwMoveMethod); bool WINAPI CascReadFile(HANDLE hFile, void * lpBuffer, DWORD dwToRead, PDWORD pdwRead); bool WINAPI CascCloseFile(HANDLE hFile); +DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh); +DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * PtrFilePosHigh, DWORD dwMoveMethod); + HANDLE WINAPI CascFindFirstFile(HANDLE hStorage, LPCSTR szMask, PCASC_FIND_DATA pFindData, LPCTSTR szListFile); bool WINAPI CascFindNextFile(HANDLE hFind, PCASC_FIND_DATA pFindData); bool WINAPI CascFindClose(HANDLE hFind); diff --git a/dep/CascLib/src/CascOpenFile.cpp b/dep/CascLib/src/CascOpenFile.cpp index 45d10bc18f1..8ff9739d6e3 100644 --- a/dep/CascLib/src/CascOpenFile.cpp +++ b/dep/CascLib/src/CascOpenFile.cpp @@ -13,8 +13,171 @@ #include "CascCommon.h" //----------------------------------------------------------------------------- +// TCascFile class functions + +TCascFile::TCascFile(TCascStorage * ahs, PCASC_CKEY_ENTRY apCKeyEntry) +{ + // Reference the storage handle + if((hs = ahs) != NULL) + hs->AddRef(); + ClassName = CASC_MAGIC_FILE; + + FilePointer = 0; + pCKeyEntry = apCKeyEntry; + SpanCount = (pCKeyEntry->SpanCount != 0) ? pCKeyEntry->SpanCount : 1; + bVerifyIntegrity = false; + bDownloadFileIf = false; + bCloseFileStream = false; + bFreeCKeyEntries = false; + + // Allocate the array of file spans + if((pFileSpan = CASC_ALLOC_ZERO<CASC_FILE_SPAN>(SpanCount)) != NULL) + { + InitFileSpans(pFileSpan, SpanCount); + InitCacheStrategy(); + } +} + +TCascFile::~TCascFile() +{ + // Free all stuff related to file spans + if (pFileSpan != NULL) + { + PCASC_FILE_SPAN pSpanPtr = pFileSpan; + + for(DWORD i = 0; i < SpanCount; i++, pSpanPtr++) + { + // Close the span file stream if this is a local file + if(bCloseFileStream) + FileStream_Close(pSpanPtr->pStream); + pSpanPtr->pStream = NULL; + + // Free the span frames + CASC_FREE(pSpanPtr->pFrames); + } + + CASC_FREE(pFileSpan); + } + + // Free the CKey entries, if needed + if(pCKeyEntry && bFreeCKeyEntries) + delete [] pCKeyEntry; + pCKeyEntry = NULL; + + // Free the file cache + CASC_FREE(pbFileCache); + + // Close (dereference) the archive handle + if(hs != NULL) + hs = hs->Release(); + ClassName = 0; +} + +DWORD TCascFile::OpenFileSpans(LPCTSTR szSpanList) +{ + TFileStream * pStream; + ULONGLONG FileSize = 0; + DWORD dwErrCode = ERROR_SUCCESS; + + for(DWORD i = 0; i < SpanCount; i++) + { + // Open the file span + pFileSpan[i].pStream = pStream = FileStream_OpenFile(szSpanList, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT); + if(pFileSpan[i].pStream == NULL) + { + dwErrCode = GetLastError(); + break; + } + + // If succeeded, we assign the span to the + FileStream_GetSize(pStream, &FileSize); + if((FileSize >> 0x1E) != 0) + { + dwErrCode = ERROR_NOT_SUPPORTED; + break; + } + + pCKeyEntry[i].EncodedSize = (DWORD)FileSize; + } + + // Free the so-far-opened files + if(dwErrCode != ERROR_SUCCESS) + { + for(DWORD i = 0; i < SpanCount; i++) + { + if(pFileSpan[i].pStream != NULL) + FileStream_Close(pFileSpan[i].pStream); + pFileSpan[i].pStream = NULL; + } + } + + return dwErrCode; +} + +void TCascFile::InitFileSpans(PCASC_FILE_SPAN pSpans, DWORD dwSpanCount) +{ + ULONGLONG FileOffsetBits = 30; + ULONGLONG FileOffsetMask = 0; + ULONGLONG FileOffset = 0; + + // Initialize the file sizes. Note that if any of the spans has invalid size, + // the entire file size will be set to CASC_INVALID_SIZE64. + GetFileSpanInfo(pCKeyEntry, &ContentSize, &EncodedSize); + + // Resolve the file offset bits and file offset mask + if(hs != NULL) + FileOffsetBits = hs->FileOffsetBits; + FileOffsetMask = ((ULONGLONG)1 << FileOffsetBits) - 1; + + // Add all span sizes + for(DWORD i = 0; i < dwSpanCount; i++, pSpans++) + { + // Put the archive index and archive offset + pSpans->ArchiveIndex = (DWORD)(pCKeyEntry[i].StorageOffset >> FileOffsetBits); + pSpans->ArchiveOffs = (DWORD)(pCKeyEntry[i].StorageOffset & FileOffsetMask); + + // Add to the total encoded size + if(ContentSize != CASC_INVALID_SIZE64) + { + pSpans->StartOffset = FileOffset; + FileOffset = FileOffset + pCKeyEntry[i].ContentSize; + pSpans->EndOffset = FileOffset; + } + } +} + +void TCascFile::InitCacheStrategy() +{ + CacheStrategy = CascCacheLastFrame; + FileCacheStart = FileCacheEnd = 0; + pbFileCache = NULL; +} + +//----------------------------------------------------------------------------- // Local functions +static size_t GetSpanFileCount(LPTSTR szSpanList) +{ + LPTSTR szSpanPtr = szSpanList; + size_t nSpanCount = 1; + + while(szSpanPtr[0] != 0) + { + // End of a file? + if(szSpanPtr[0] == ';' && szSpanPtr[1] != 0) + { + szSpanPtr[0] = 0; + nSpanCount++; + } + + szSpanPtr++; + } + + // Place an additional zero to make the list terminated by double EOS + szSpanPtr[1] = 0; + return nSpanCount; +} + PCASC_CKEY_ENTRY FindCKeyEntry_CKey(TCascStorage * hs, LPBYTE pbCKey, PDWORD PtrIndex) { return (PCASC_CKEY_ENTRY)hs->CKeyMap.FindObject(pbCKey, PtrIndex); @@ -28,7 +191,7 @@ PCASC_CKEY_ENTRY FindCKeyEntry_EKey(TCascStorage * hs, LPBYTE pbEKey, PDWORD Ptr bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD dwOpenFlags, HANDLE * PtrFileHandle) { TCascFile * hf = NULL; - int nError = ERROR_FILE_NOT_FOUND; + DWORD dwErrCode = ERROR_FILE_NOT_FOUND; // If the CKey entry is NULL, we consider the file non-existant if(pCKeyEntry != NULL) @@ -39,11 +202,11 @@ bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD d hf->bVerifyIntegrity = (dwOpenFlags & CASC_STRICT_DATA_CHECK) ? true : false; hf->bDownloadFileIf = (hs->dwFeatures & CASC_FEATURE_ONLINE) ? true : false; hf->bOvercomeEncrypted = (dwOpenFlags & CASC_OVERCOME_ENCRYPTED) ? true : false; - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } else { - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } } @@ -51,9 +214,89 @@ bool OpenFileByCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pCKeyEntry, DWORD d PtrFileHandle[0] = (HANDLE)hf; // Handle last error - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); + if(dwErrCode != ERROR_SUCCESS) + SetLastError(dwErrCode); + return (dwErrCode == ERROR_SUCCESS); +} + +bool OpenLocalFile(LPCTSTR szFileName, DWORD dwOpenFlags, HANDLE * PtrFileHandle) +{ + PCASC_CKEY_ENTRY pCKeyEntry; + TCascFile * hf = NULL; + LPTSTR szSpanList; + size_t nSpanCount; + DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + + // Create a copy of the file name. It is actually a file name list, + // separated by comma (for supporting multi-span files) + if((szSpanList = CascNewStr(szFileName, 1)) != NULL) + { + // Calculate the span count + if((nSpanCount = GetSpanFileCount(szSpanList)) != 0 || nSpanCount > 0xFF) + { + // Allocate CKey array for the file. Each entry describes one file span + if((pCKeyEntry = new CASC_CKEY_ENTRY[nSpanCount]) != NULL) + { + // Prepare the span count to the first item + pCKeyEntry->SpanCount = (BYTE)nSpanCount; + + // Prepare the archive offset in each CKey entry + for(size_t i = 0; i < nSpanCount; i++) + pCKeyEntry[i].StorageOffset = 0; + + // Create an instance of the TCascFile + if((hf = new TCascFile(NULL, pCKeyEntry)) != NULL) + { + // Prepare the structure + hf->bVerifyIntegrity = (dwOpenFlags & CASC_STRICT_DATA_CHECK) ? true : false; + hf->bOvercomeEncrypted = (dwOpenFlags & CASC_OVERCOME_ENCRYPTED) ? true : false; + hf->bCloseFileStream = true; + + // Open all local file spans + dwErrCode = hf->OpenFileSpans(szSpanList); + if(dwErrCode != ERROR_SUCCESS) + { + delete hf; + hf = NULL; + } + } + } + } + else + { + dwErrCode = ERROR_INVALID_PARAMETER; + } + + delete [] szSpanList; + } + + // Give the output parameter, no matter what + PtrFileHandle[0] = (HANDLE)hf; + + // Handle last error + if(dwErrCode != ERROR_SUCCESS) + SetLastError(dwErrCode); + return (dwErrCode == ERROR_SUCCESS); +} + +bool SetCacheStrategy(HANDLE hFile, CSTRTG CacheStrategy) +{ + TCascFile * hf; + + // Validate the file handle + if((hf = TCascFile::IsValid(hFile)) != NULL) + { + // The cache must not be initialized yet + if(hf->pbFileCache == NULL) + { + hf->CacheStrategy = CacheStrategy; + return true; + } + } + + // Failed. This should never happen + assert(false); + return false; } //----------------------------------------------------------------------------- @@ -66,7 +309,7 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocal const char * szFileName; DWORD FileDataId = CASC_INVALID_ID; BYTE CKeyEKeyBuffer[MD5_HASH_SIZE]; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // This parameter is not used CASCLIB_UNUSED(dwLocaleFlags); @@ -162,7 +405,7 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocal default: // Unknown open mode - nError = ERROR_INVALID_PARAMETER; + dwErrCode = ERROR_INVALID_PARAMETER; break; } @@ -170,6 +413,18 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const void * pvFileName, DWORD dwLocal return OpenFileByCKeyEntry(hs, pCKeyEntry, dwOpenFlags, PtrFileHandle); } +bool WINAPI CascOpenLocalFile(LPCTSTR szFileName, DWORD dwOpenFlags, HANDLE * PtrFileHandle) +{ + // Verify parameters + if(szFileName == NULL || szFileName[0] == 0 || PtrFileHandle == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + return OpenLocalFile(szFileName, dwOpenFlags, PtrFileHandle); +} + bool WINAPI CascCloseFile(HANDLE hFile) { TCascFile * hf; diff --git a/dep/CascLib/src/CascOpenStorage.cpp b/dep/CascLib/src/CascOpenStorage.cpp index e10db730d41..abc17e1e9ce 100644 --- a/dep/CascLib/src/CascOpenStorage.cpp +++ b/dep/CascLib/src/CascOpenStorage.cpp @@ -17,33 +17,29 @@ //----------------------------------------------------------------------------- // Local defines -// Limit for "orphaned" items - those that are in index files, but are not in ENCODING manifest -#define CASC_MAX_ORPHANED_ITEMS 0x100 +// Limit for "additional" items in CKey table +#define CASC_MAX_EXTRA_ITEMS 0x40 //----------------------------------------------------------------------------- -// TCascStorage service functions +// TCascStorage class functions TCascStorage::TCascStorage() { // Prepare the base storage parameters - szClassName = "TCascStorage"; + ClassName = CASC_MAGIC_STORAGE; pRootHandler = NULL; - dwDefaultLocale = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB; dwRefCount = 1; szRootPath = szDataPath = szIndexPath = szBuildFile = szCdnServers = szCdnPath = szCodeName = NULL; - szProductName = NULL; szIndexFormat = NULL; szRegion = NULL; memset(DataFiles, 0, sizeof(DataFiles)); - Product = UnknownProduct; dwBuildNumber = 0; dwFeatures = 0; - bAllowOrphans = false; BuildFileType = CascBuildNone; - LocalFiles = TotalFiles = EKeyEntries = OrphanItems = SkippedItems = EKeyLength = FileOffsetBits = 0; + LocalFiles = TotalFiles = EKeyEntries = EKeyLength = FileOffsetBits = 0; } TCascStorage::~TCascStorage() @@ -79,32 +75,24 @@ TCascStorage::~TCascStorage() FreeCascBlob(&PatchArchivesKey); FreeCascBlob(&PatchArchivesGroup); FreeCascBlob(&BuildFiles); - szClassName = NULL; + ClassName = 0; } TCascStorage * TCascStorage::AddRef() { - dwRefCount++; + CascInterlockedIncrement(&dwRefCount); return this; } TCascStorage * TCascStorage::Release() { - if (dwRefCount == 1) + if(CascInterlockedDecrement(&dwRefCount) == 0) { delete this; return NULL; } - dwRefCount--; - return NULL; -} - -TCascStorage * TCascStorage::IsValid(HANDLE hStorage) -{ - TCascStorage * hs = (TCascStorage *)hStorage; - - return (hs != NULL && hs->szClassName != NULL && !strcmp(hs->szClassName, "TCascStorage")) ? hs : NULL; + return this; } //----------------------------------------------------------------------------- @@ -139,38 +127,205 @@ static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir) return szIndexPath; } -static int CreateCKeyMaps(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader) +// Inserts an entry from the text build file +static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_CKEY_ENTRY & CKeyEntry) { - size_t nEstimatedEntries = (EnHeader.CKeyPageCount * EnHeader.CKeyPageSize) / sizeof(FILE_CKEY_ENTRY); - size_t nIxEntries = hs->IndexArray.ItemCount(); - int nError; + PCASC_CKEY_ENTRY pCKeyEntry = NULL; - // Orphaned items: These are present in INDEX files (by EKey), but missing in the ENCODING manifest. - // Probably a bug in generator of "2018 - New CASC\00001", but we want to open the storage nontheless. - if(nEstimatedEntries < 0x100) + // Skip entries without any key + if(CKeyEntry.Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY)) { - nEstimatedEntries = nEstimatedEntries + nIxEntries; - hs->bAllowOrphans = true; + // Check if there is an existing entry + if((pCKeyEntry = FindCKeyEntry_CKey(hs, CKeyEntry.CKey)) == NULL) + { + // Insert a new entry to the array. DO NOT ALLOW enlarge array here + pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); + if(pCKeyEntry == NULL) + return NULL; + + // Fill in the item + memcpy(pCKeyEntry, &CKeyEntry, sizeof(CASC_CKEY_ENTRY)); + + // If we have CKey present, insert it to the CKey map + if(CKeyEntry.Flags & CASC_CE_HAS_CKEY) + hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); + + // If we have EKey present, insert it to the EKey map + if(CKeyEntry.Flags & CASC_CE_HAS_EKEY) + hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); + } + else + { + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + pCKeyEntry->ContentSize = CKeyEntry.ContentSize; + if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) + pCKeyEntry->EncodedSize = CKeyEntry.EncodedSize; + } } - // Allow some room for extra entries - nEstimatedEntries += CASC_MAX_ORPHANED_ITEMS; + return pCKeyEntry; +} + +// Inserts an entry from ENCODING +static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, PFILE_CKEY_ENTRY pFileEntry) +{ + PCASC_CKEY_ENTRY pCKeyEntry; + + // Check whether the entry is already there + if((pCKeyEntry = FindCKeyEntry_EKey(hs, pFileEntry->EKey)) == NULL) + { + // Insert a new entry to the array. DO NOT ALLOW enlarge array here + pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); + if(pCKeyEntry == NULL) + return NULL; + + CopyMemory16(pCKeyEntry->CKey, pFileEntry->CKey); + CopyMemory16(pCKeyEntry->EKey, pFileEntry->EKey); + pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64; + pCKeyEntry->TagBitMask = 0; + pCKeyEntry->ContentSize = ConvertBytesToInteger_4(pFileEntry->ContentSize); + pCKeyEntry->EncodedSize = CASC_INVALID_SIZE; + pCKeyEntry->Flags = CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING; + pCKeyEntry->RefCount = 0; + pCKeyEntry->SpanCount = 1; + pCKeyEntry->Priority = 0; + + // Insert the item into both maps + hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); + hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); + } + else + { + // Supply both CKey and EKey. Rewrite EKey regardless, because ENCODING manifest contains a full one + CopyMemory16(pCKeyEntry->CKey, pFileEntry->CKey); + CopyMemory16(pCKeyEntry->EKey, pFileEntry->EKey); + + // Supply the content size + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + pCKeyEntry->ContentSize = ConvertBytesToInteger_4(pFileEntry->ContentSize); + pCKeyEntry->Flags |= CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING; + pCKeyEntry->Flags &= ~CASC_CE_HAS_EKEY_PARTIAL; + + // Insert the item into CKey map + hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); + } + + return pCKeyEntry; +} + +// Inserts an entry from DOWNLOAD +static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, CASC_DOWNLOAD_ENTRY & DlEntry) +{ + PCASC_CKEY_ENTRY pCKeyEntry; + + // Check whether the entry is already there + if((pCKeyEntry = FindCKeyEntry_EKey(hs, DlEntry.EKey)) == NULL) + { + // Insert dummy CKey entry to the array. DO NOT allow to enlarge the array + pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false); + if(pCKeyEntry == NULL) + return NULL; + + // Copy the entry + ZeroMemory16(pCKeyEntry->CKey); + CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey); + pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64; + pCKeyEntry->TagBitMask = 0; + pCKeyEntry->ContentSize = CASC_INVALID_SIZE; + pCKeyEntry->EncodedSize = (DWORD)DlEntry.EncodedSize; + pCKeyEntry->Flags = CASC_CE_HAS_EKEY | CASC_CE_IN_DOWNLOAD; + pCKeyEntry->RefCount = 0; + pCKeyEntry->SpanCount = 1; + + // Insert the entry to the map. Only insert it to the EKey map, as there is no CKey present + hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); + } + else + { + // Copy the EKey if we only have partial one + if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY_PARTIAL) + CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey); + + // Supply the encoded size, if unknown yet + if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) + pCKeyEntry->EncodedSize = (DWORD)DlEntry.EncodedSize; + pCKeyEntry->Flags = (pCKeyEntry->Flags & ~CASC_CE_HAS_EKEY_PARTIAL) | CASC_CE_IN_DOWNLOAD; + } + + // Supply the rest + pCKeyEntry->Priority = DlEntry.Priority; + return pCKeyEntry; +} + +static DWORD CopyBuildFileItemsToCKeyArray(TCascStorage * hs) +{ + // Insert the well-known files +// InsertCKeyEntry(hs, hs->EncodingCKey); + InsertCKeyEntry(hs, hs->DownloadCKey); + InsertCKeyEntry(hs, hs->InstallCKey); + InsertCKeyEntry(hs, hs->PatchFile); + InsertCKeyEntry(hs, hs->RootFile); + InsertCKeyEntry(hs, hs->SizeFile); + InsertCKeyEntry(hs, hs->VfsRoot); + + // Insert all VFS roots + for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++) + { + PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); + InsertCKeyEntry(hs, *pCKeyEntry); + } + + return ERROR_SUCCESS; +} + +// Estimate the total number of files, so we won't have to re-allocate the array +static size_t GetEstimatedNumberOfFiles(TCascStorage * hs) +{ + // If we know the size of DOWNLOAD at this point, we estimate number of files from it. + // Size of one entry in DOWNLOAD is at least 26 bytes. This is the most reliable method. + // However, for some online storages ("agent"), this is a very small value + if(hs->DownloadCKey.ContentSize != CASC_INVALID_SIZE) + return (hs->DownloadCKey.ContentSize / 26) + CASC_MAX_EXTRA_ITEMS; + + // If we know the size of ENCODING at this point, we estimate number of files from it. + // Size of one entry in ENCODING is at least 38 bytes. This method fails on storages + // with TVFS file system, as ENCODING only contains a small subset of file. + // Fortunately, all known TVFS-based storages have "download-size" present + if(hs->EncodingCKey.ContentSize != CASC_INVALID_SIZE) + return (hs->EncodingCKey.ContentSize / 26) + CASC_MAX_EXTRA_ITEMS; + + // By default, it's gonna be half a million, which is the maximum observed number of files + // for all older storages (HOTS before 39445, WoW before 19116) + return 500000; +} + +static DWORD InitCKeyArray(TCascStorage * hs) +{ + size_t nNumberOfFiles = GetEstimatedNumberOfFiles(hs); + DWORD dwErrCode; + + // + // Allocate array and map of CKey entries + // // Create the array of CKey items - nError = hs->CKeyArray.Create(sizeof(CASC_CKEY_ENTRY), nEstimatedEntries); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = hs->CKeyArray.Create(sizeof(CASC_CKEY_ENTRY), nNumberOfFiles); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Create the map CKey -> CASC_CKEY_ENTRY - nError = hs->CKeyMap.Create(nEstimatedEntries, EnHeader.CKeyLength, FIELD_OFFSET(CASC_CKEY_ENTRY, CKey)); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = hs->CKeyMap.Create(nNumberOfFiles, MD5_HASH_SIZE, FIELD_OFFSET(CASC_CKEY_ENTRY, CKey)); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - // Create the map EKey -> CASC_CKEY_ENTRY - nError = hs->EKeyMap.Create(nEstimatedEntries, hs->EKeyLength, FIELD_OFFSET(CASC_CKEY_ENTRY, EKey)); - if(nError != ERROR_SUCCESS) - return nError; + // Create the map CKey -> CASC_CKEY_ENTRY. Note that TVFS root references files + // using 9-byte EKey, so cut the search EKey length to 9 bytes + dwErrCode = hs->EKeyMap.Create(nNumberOfFiles, CASC_EKEY_SIZE, FIELD_OFFSET(CASC_CKEY_ENTRY, EKey)); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + // Insert the entry of ENCODING file. This is vital for its opening and loading + InsertCKeyEntry(hs, hs->EncodingCKey); return ERROR_SUCCESS; } @@ -200,7 +355,6 @@ int CaptureEncodingHeader(CASC_ENCODING_HEADER & EnHeader, LPBYTE pbFileData, si static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHeader, LPBYTE pbPageBegin, LPBYTE pbEndOfPage) { - PCASC_CKEY_ENTRY pCKeyEntry; PFILE_CKEY_ENTRY pFileEntry; LPBYTE pbFileEntry = pbPageBegin; @@ -219,26 +373,10 @@ static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHead // Example of a file entry with multiple EKeys: // Overwatch build 24919, CKey: 0e 90 94 fa d2 cb 85 ac d0 7c ea 09 f9 c5 ba 00 // BREAKIF(pFileEntry->EKeyCount > 1); +// BREAK_ON_XKEY3(pFileEntry->EKey, 0x09, 0xF3, 0xCD); - // Insert the CKey entry into the array - pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1); - if(pCKeyEntry != NULL) - { - // Supply both CKey and EKey. Rewrite EKey regardless, because ENCODING manifest contains a full one - CopyMemory16(pCKeyEntry->CKey, pFileEntry->CKey); - CopyMemory16(pCKeyEntry->EKey, pFileEntry->EKey); - pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64; - pCKeyEntry->TagBitMask = 0; - pCKeyEntry->EncodedSize = CASC_INVALID_SIZE; - pCKeyEntry->ContentSize = ConvertBytesToInteger_4(pFileEntry->ContentSize); - pCKeyEntry->RefCount = 0; - pCKeyEntry->Priority = 0; - pCKeyEntry->Flags = (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_IN_ENCODING); - - // Insert the item into both maps - hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); - hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); - } + // Insert the entry to the central CKey table + InsertCKeyEntry(hs, pFileEntry); // Move to the next encoding entry pbFileEntry = pbFileEntry + 2 + 4 + EnHeader.CKeyLength + (pFileEntry->EKeyCount * EnHeader.EKeyLength); @@ -246,245 +384,88 @@ static int LoadEncodingCKeyPage(TCascStorage * hs, CASC_ENCODING_HEADER & EnHead return ERROR_SUCCESS; } -static PCASC_CKEY_ENTRY InsertCKeyEntry(TCascStorage * hs, PCASC_CKEY_ENTRY pSourceEntry, bool bAllowOrphans, bool * pbAllocatedNewEntry) -{ - PCASC_CKEY_ENTRY pCKeyEntry = NULL; - bool bAllocatedNewEntry = false; - - if(pSourceEntry->Flags & CASC_CE_HAS_EKEY) - { - // If there is that item already, reuse it - pCKeyEntry = FindCKeyEntry_EKey(hs, pSourceEntry->EKey); - if(pCKeyEntry == NULL) - { - // Increment number of orphaned index entries - hs->OrphanItems++; - - // Insert the orphan item only of they are allowed and if we won't overflow the array - if(bAllowOrphans && (hs->CKeyArray.ItemCount() + 1) < hs->CKeyArray.ItemCountMax()) - { - // Insert a new entry to the array - pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1); - if(pCKeyEntry != NULL) - { - // Copy CKey, EKey and some flags - if(pSourceEntry->Flags & CASC_CE_HAS_CKEY) - CopyMemory16(pCKeyEntry->CKey, pSourceEntry->CKey); - - if(pSourceEntry->Flags & CASC_CE_HAS_EKEY) - CopyMemory16(pCKeyEntry->EKey, pSourceEntry->EKey); - - pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64; - pCKeyEntry->TagBitMask = 0; - pCKeyEntry->RefCount = 0; - pCKeyEntry->Priority = 0; - - pCKeyEntry->EncodedSize = CASC_INVALID_SIZE; - pCKeyEntry->ContentSize = CASC_INVALID_SIZE; - pCKeyEntry->Flags = (pSourceEntry->Flags & (CASC_CE_HAS_CKEY | CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL)); - bAllocatedNewEntry = true; - } - } - else - { - hs->SkippedItems++; - } - } - } - - if(pbAllocatedNewEntry != NULL) - pbAllocatedNewEntry[0] = bAllocatedNewEntry; - return pCKeyEntry; -} - -static PCASC_CKEY_ENTRY CopyBuildFileItemToCKeyArray(TCascStorage * hs, PCASC_CKEY_ENTRY pSourceEntry) -{ - PCASC_CKEY_ENTRY pCKeyEntry = NULL; - bool bAllocatedNewEntry = false; - - pCKeyEntry = InsertCKeyEntry(hs, pSourceEntry, true, &bAllocatedNewEntry); - if(pCKeyEntry != NULL) - { - // Fill the values that might be known - if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) - pCKeyEntry->EncodedSize = pSourceEntry->EncodedSize; - if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) - pCKeyEntry->ContentSize = pSourceEntry->ContentSize; - - // If this is a new entry, we need to insert it to the maps - if(bAllocatedNewEntry) - { - if(pCKeyEntry->Flags & CASC_CE_HAS_CKEY) - hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); - if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY) - hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); - } - } - - return pCKeyEntry; -} - -static int CopyBuildFileItemsToCKeyArray(TCascStorage * hs) -{ - // Insert the well-known files - CopyBuildFileItemToCKeyArray(hs, &hs->EncodingCKey); - CopyBuildFileItemToCKeyArray(hs, &hs->DownloadCKey); - CopyBuildFileItemToCKeyArray(hs, &hs->InstallCKey); - CopyBuildFileItemToCKeyArray(hs, &hs->PatchFile); - CopyBuildFileItemToCKeyArray(hs, &hs->RootFile); - CopyBuildFileItemToCKeyArray(hs, &hs->SizeFile); - CopyBuildFileItemToCKeyArray(hs, &hs->VfsRoot); - - // Insert all VFS roots - for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++) - { - PCASC_CKEY_ENTRY pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); - CopyBuildFileItemToCKeyArray(hs, pCKeyEntry); - } - - return ERROR_SUCCESS; -} - -static int CopyIndexItemsToCKeyArray(TCascStorage * hs) -{ - PCASC_CKEY_ENTRY pIndexEntry; - PCASC_CKEY_ENTRY pCKeyEntry; - size_t nItemCount = hs->IndexArray.ItemCount(); - bool bAllocatedNewEntry = false; - - // Iterate over all index items - for(size_t i = 0; i < nItemCount; i++) - { - // Get the n-th index entry - pIndexEntry = (PCASC_CKEY_ENTRY)hs->IndexArray.ItemAt(i); - - // Sometimes, there are multiple items with the same EKey in the index files - // Example: "2018 - New CASC\00001", EKey 37 89 16 5b 2d cc 71 c1 25 00 00 00 00 00 00 00 - // Positions: 0x2D, 0x2E, 0x2F - //BREAK_ON_XKEY3(pIndexEntry->EKey, 0x37, 0x89, 0x16); - - // Copy the index entry to the central storage - if((pCKeyEntry = InsertCKeyEntry(hs, pIndexEntry, hs->bAllowOrphans, &bAllocatedNewEntry)) != NULL) - { - // Make sure that the CKey is zeroed when not present - if((pCKeyEntry->Flags & CASC_CE_HAS_CKEY) == 0) - ZeroMemory16(pCKeyEntry->CKey); - - // Only copy the storage offset and sizes if not available yet - if(pCKeyEntry->StorageOffset == CASC_INVALID_OFFS64) - { - pCKeyEntry->StorageOffset = pIndexEntry->StorageOffset; - pCKeyEntry->EncodedSize = pIndexEntry->EncodedSize; - } - - if(bAllocatedNewEntry) - { - if(pCKeyEntry->Flags & CASC_CE_HAS_CKEY) - hs->CKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->CKey); - if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY) - hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey); - } - - // Mark the file as available locally - pCKeyEntry->Flags |= CASC_CE_FILE_IS_LOCAL; - } - } - - // We free the index array at this point - hs->IndexArray.Free(); - return ERROR_SUCCESS; -} - static int LoadEncodingManifest(TCascStorage * hs) { + PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->EncodingCKey.CKey); LPBYTE pbEncodingFile; DWORD cbEncodingFile = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading ENCODING manifest", NULL, 0, 0)) return ERROR_CANCELLED; // Load the entire encoding file to memory - pbEncodingFile = LoadInternalFileToMemory(hs, &hs->EncodingCKey, &cbEncodingFile); + pbEncodingFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbEncodingFile); if(pbEncodingFile != NULL && cbEncodingFile != 0) { CASC_ENCODING_HEADER EnHeader; // Capture the header of the ENCODING file - nError = CaptureEncodingHeader(EnHeader, pbEncodingFile, cbEncodingFile); - if(nError == ERROR_SUCCESS) + dwErrCode = CaptureEncodingHeader(EnHeader, pbEncodingFile, cbEncodingFile); + if(dwErrCode == ERROR_SUCCESS) { // Get the CKey page header and the first page PFILE_CKEY_PAGE pPageHeader = (PFILE_CKEY_PAGE)(pbEncodingFile + sizeof(FILE_ENCODING_HEADER) + EnHeader.ESpecBlockSize); LPBYTE pbCKeyPage = (LPBYTE)(pPageHeader + EnHeader.CKeyPageCount); - // Since ENCODING contains the full list of all files (even those not downloaded), - // we can now make a fair estimate about how large maps shall we create. - // So, we can build the maps CKey and EKey map. - if((nError = CreateCKeyMaps(hs, EnHeader)) == ERROR_SUCCESS) + // Go through all CKey pages and verify them + for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++) { - // Go through all CKey pages and verify them - for(DWORD i = 0; i < EnHeader.CKeyPageCount; i++) + // Check if there is enough space in the buffer + if((pbCKeyPage + EnHeader.CKeyPageSize) > (pbEncodingFile + cbEncodingFile)) { - PFILE_CKEY_ENTRY pCKeyEntry = (PFILE_CKEY_ENTRY)pbCKeyPage; - - // Check if there is enough space in the buffer - if((pbCKeyPage + EnHeader.CKeyPageSize) > (pbEncodingFile + cbEncodingFile)) - { - nError = ERROR_FILE_CORRUPT; - break; - } + dwErrCode = ERROR_FILE_CORRUPT; + break; + } - // Check the hash of the entire segment - // Note that verifying takes considerable time of the storage loading -// if(!VerifyDataBlockHash(pbCKeyPage, EnHeader.CKeyPageSize, pEncodingSegment->SegmentHash)) -// { -// nError = ERROR_FILE_CORRUPT; -// break; -// } + // Check the hash of the entire segment + // Note that verifying takes considerable time of the storage loading +// if(!VerifyDataBlockHash(pbCKeyPage, EnHeader.CKeyPageSize, pEncodingSegment->SegmentHash)) +// { +// dwErrCode = ERROR_FILE_CORRUPT; +// break; +// } - // Check if the CKey matches with the expected first value - if(memcmp(pCKeyEntry->CKey, pPageHeader[i].FirstKey, CASC_CKEY_SIZE)) - { - nError = ERROR_FILE_CORRUPT; - break; - } + // Check if the CKey matches with the expected first value + if(memcmp(((PFILE_CKEY_ENTRY)pbCKeyPage)->CKey, pPageHeader[i].FirstKey, MD5_HASH_SIZE)) + { + dwErrCode = ERROR_FILE_CORRUPT; + break; + } - // Load the entire page of CKey entries. - // This operation will never fail, because all memory is already pre-allocated - nError = LoadEncodingCKeyPage(hs, EnHeader, pbCKeyPage, pbCKeyPage + EnHeader.CKeyPageSize); - if(nError != ERROR_SUCCESS) - break; + // Load the entire page of CKey entries. + // This operation will never fail, because all memory is already pre-allocated + dwErrCode = LoadEncodingCKeyPage(hs, EnHeader, pbCKeyPage, pbCKeyPage + EnHeader.CKeyPageSize); + if(dwErrCode != ERROR_SUCCESS) + break; - // Move to the next CKey page - pbCKeyPage += EnHeader.CKeyPageSize; - } + // Move to the next CKey page + pbCKeyPage += EnHeader.CKeyPageSize; } } // All CKey->EKey entries from the text build files need to be copied to the CKey array - // This also includes the ENCODING file itself, which is vital for later loading - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { - nError = CopyBuildFileItemsToCKeyArray(hs); + dwErrCode = CopyBuildFileItemsToCKeyArray(hs); } // Now supply all the entries from the index files - if(nError == ERROR_SUCCESS) - { - nError = CopyIndexItemsToCKeyArray(hs); - } + //if(dwErrCode == ERROR_SUCCESS) + //{ + // dwErrCode = CopyIndexItemsToCKeyArray(hs); + //} // Free the loaded ENCODING file CASC_FREE(pbEncodingFile); } else { - nError = GetLastError(); + dwErrCode = GetLastError(); } - return nError; + return dwErrCode; } size_t GetTagBitmapLength(LPBYTE pbFilePtr, LPBYTE pbFileEnd, DWORD EntryCount) @@ -615,7 +596,7 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead LPBYTE pbTag = pbTags; size_t nMaxNameLength = 0; size_t nTagEntryLengh = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Does the storage support tags? if(DlHeader.TagCount != 0) @@ -624,7 +605,7 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead hs->dwFeatures |= CASC_FEATURE_TAGS; // Allocate space for the tag array - TagArray = CASC_ALLOC(CASC_TAG_ENTRY1, DlHeader.TagCount); + TagArray = CASC_ALLOC<CASC_TAG_ENTRY1>(DlHeader.TagCount); if(TagArray != NULL) { // Get the longest tag name @@ -640,8 +621,8 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead nTagEntryLengh = ALIGN_TO_SIZE(nTagEntryLengh, 8); // Load the tags into array in the storage structure - nError = hs->TagsArray.Create(nTagEntryLengh, DlHeader.TagCount); - if(nError == ERROR_SUCCESS) + dwErrCode = hs->TagsArray.Create(nTagEntryLengh, DlHeader.TagCount); + if(dwErrCode == ERROR_SUCCESS) { // Convert the array of CASC_DOWNLOAD_TAG1 to array of CASC_DOWNLOAD_TAG2 for(DWORD i = 0; i < DlHeader.TagCount; i++) @@ -653,7 +634,7 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead pTargetTag = (PCASC_TAG_ENTRY2)hs->TagsArray.Insert(1); if(pTargetTag == NULL) { - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; break; } @@ -667,7 +648,7 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead } else { - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } } @@ -676,20 +657,21 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead { CASC_DOWNLOAD_ENTRY DlEntry; PCASC_CKEY_ENTRY pCKeyEntry; + ULONGLONG TagBit = 1; size_t BitMaskOffset = (i / 8); + size_t TagItemCount = hs->TagsArray.ItemCount(); BYTE BitMaskBit = 0x80 >> (i % 8); // Capture the download entry if(CaptureDownloadEntry(DlHeader, DlEntry, pbEntry, pbFileEnd) != ERROR_SUCCESS) break; - // Make sure we have the entry in CKey table - pCKeyEntry = FindCKeyEntry_EKey(hs, DlEntry.EKey); - if(pCKeyEntry != NULL) - { - ULONGLONG TagBit = 1; - size_t TagItemCount = hs->TagsArray.ItemCount(); + // COD4: zone/base.xpak + //BREAK_ON_XKEY3(DlEntry.EKey, 0xa5, 0x00, 0x16); + // Insert the entry to the central CKey table + if((pCKeyEntry = InsertCKeyEntry(hs, DlEntry)) != NULL) + { // Supply the tag bits for(size_t j = 0; j < TagItemCount; j++) { @@ -700,17 +682,6 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead // Move to the next bit TagBit <<= 1; } - - // If the EKey has partial EKey only, fix that - if(pCKeyEntry->Flags & CASC_CE_HAS_EKEY_PARTIAL) - { - CopyMemory16(pCKeyEntry->EKey, DlEntry.EKey); - pCKeyEntry->Flags &= ~CASC_CE_HAS_EKEY_PARTIAL; - } - - // Supply the priority - pCKeyEntry->Priority = DlEntry.Priority; - pCKeyEntry->Flags |= CASC_CE_IN_DOWNLOAD; } // Move to the next entry @@ -722,7 +693,7 @@ static int LoadDownloadManifest(TCascStorage * hs, CASC_DOWNLOAD_HEADER & DlHead // Remember the total file count hs->TotalFiles = hs->CKeyArray.ItemCount(); - return nError; + return dwErrCode; } static int LoadDownloadManifest(TCascStorage * hs) @@ -730,7 +701,7 @@ static int LoadDownloadManifest(TCascStorage * hs) PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->DownloadCKey.CKey); LPBYTE pbDownloadFile = NULL; DWORD cbDownloadFile = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading DOWNLOAD manifest", NULL, 0, 0)) @@ -743,11 +714,11 @@ static int LoadDownloadManifest(TCascStorage * hs) CASC_DOWNLOAD_HEADER DlHeader; // Capture the header of the DOWNLOAD file - nError = CaptureDownloadHeader(DlHeader, pbDownloadFile, cbDownloadFile); - if(nError == ERROR_SUCCESS) + dwErrCode = CaptureDownloadHeader(DlHeader, pbDownloadFile, cbDownloadFile); + if(dwErrCode == ERROR_SUCCESS) { // Parse the entire download manifest - nError = LoadDownloadManifest(hs, DlHeader, pbDownloadFile, pbDownloadFile + cbDownloadFile); + dwErrCode = LoadDownloadManifest(hs, DlHeader, pbDownloadFile, pbDownloadFile + cbDownloadFile); } // Free the loaded manifest @@ -755,7 +726,7 @@ static int LoadDownloadManifest(TCascStorage * hs) } // If the DOWNLOAD manifest is not present, we won't abort the downloading process. - return nError; + return dwErrCode; } //----------------------------------------------------------------------------- @@ -767,7 +738,7 @@ static int LoadInstallManifest(TCascStorage * hs) PCASC_CKEY_ENTRY pCKeyEntry = FindCKeyEntry_CKey(hs, hs->InstallCKey.CKey); LPBYTE pbInstallFile = NULL; DWORD cbInstallFile = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Inform the user about what we are doing if(InvokeProgressCallback(hs, "Loading INSTALL manifest", NULL, 0, 0)) @@ -777,18 +748,18 @@ static int LoadInstallManifest(TCascStorage * hs) pbInstallFile = LoadInternalFileToMemory(hs, pCKeyEntry, &cbInstallFile); if (pbInstallFile != NULL && cbInstallFile != 0) { - nError = RootHandler_CreateInstall(hs, pbInstallFile, cbInstallFile); + dwErrCode = RootHandler_CreateInstall(hs, pbInstallFile, cbInstallFile); CASC_FREE(pbInstallFile); } else { - nError = GetLastError(); + dwErrCode = GetLastError(); } - return nError; + return dwErrCode; } -static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC_CKEY_ENTRY & FakeCKeyEntry) +static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC_CKEY_ENTRY & FakeCKeyEntry, DWORD dwFlags = 0) { PCASC_CKEY_ENTRY pCKeyEntry = NULL; @@ -799,9 +770,24 @@ static bool InsertWellKnownFile(TCascStorage * hs, const char * szFileName, CASC pCKeyEntry = FindCKeyEntry_CKey(hs, FakeCKeyEntry.CKey); if(pCKeyEntry != NULL) { - // Insert the key to the root handler, unless it's already referenced by a name - if(pCKeyEntry->RefCount == 0) - hs->pRootHandler->Insert(szFileName, pCKeyEntry); + // Insert the key to the root handler. Note that the file can already be referenced + // ("index" vs "vfs-root" in Warcraft III storages) + hs->pRootHandler->Insert(szFileName, pCKeyEntry); + pCKeyEntry->Flags |= (CASC_CE_IN_BUILD | dwFlags); + return true; + } + } + + // Special case: the PATCH file is usually not in any indices. + // It's also never locally available + if((dwFlags & CASC_CE_FILE_PATCH) && (hs->dwFeatures & CASC_FEATURE_ONLINE)) + { + // Get or insert the PATCH entry + pCKeyEntry = InsertCKeyEntry(hs, FakeCKeyEntry); + if(pCKeyEntry != NULL) + { + hs->pRootHandler->Insert(szFileName, pCKeyEntry); + pCKeyEntry->Flags |= (CASC_CE_IN_BUILD | dwFlags); return true; } } @@ -815,16 +801,14 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask) PDWORD FileSignature; LPBYTE pbRootFile = NULL; DWORD cbRootFile = 0; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Sanity checks assert(hs->CKeyMap.IsInitialized() == true); assert(hs->pRootHandler == NULL); - // Locale: The default parameter is 0 - in that case, - // we assign the default locale, loaded from the .build.info file - if(dwLocaleMask == 0) - dwLocaleMask = hs->dwDefaultLocale; + // Locale: The default parameter is 0 - in that case, we load all locales + dwLocaleMask = (dwLocaleMask != 0) ? dwLocaleMask : 0xFFFFFFFF; // Prioritize the VFS root over legacy ROOT file pCKeyEntry = (hs->VfsRoot.ContentSize != CASC_INVALID_SIZE) ? &hs->VfsRoot : &hs->RootFile; @@ -846,19 +830,19 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask) switch(FileSignature[0]) { case CASC_MNDX_ROOT_SIGNATURE: - nError = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile); + dwErrCode = RootHandler_CreateMNDX(hs, pbRootFile, cbRootFile); break; case CASC_DIABLO3_ROOT_SIGNATURE: - nError = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile); + dwErrCode = RootHandler_CreateDiablo3(hs, pbRootFile, cbRootFile); break; case CASC_TVFS_ROOT_SIGNATURE: - nError = RootHandler_CreateTVFS(hs, pbRootFile, cbRootFile); + dwErrCode = RootHandler_CreateTVFS(hs, pbRootFile, cbRootFile); break; case CASC_WOW82_ROOT_SIGNATURE: - nError = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); + dwErrCode = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); break; default: @@ -868,13 +852,13 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask) // If the format was not recognized, they need to return ERROR_BAD_FORMAT // - nError = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile); - if(nError == ERROR_BAD_FORMAT) + dwErrCode = RootHandler_CreateOverwatch(hs, pbRootFile, cbRootFile); + if(dwErrCode == ERROR_BAD_FORMAT) { - nError = RootHandler_CreateStarcraft1(hs, pbRootFile, cbRootFile); - if(nError == ERROR_BAD_FORMAT) + dwErrCode = RootHandler_CreateStarcraft1(hs, pbRootFile, cbRootFile); + if(dwErrCode == ERROR_BAD_FORMAT) { - nError = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); + dwErrCode = RootHandler_CreateWoW(hs, pbRootFile, cbRootFile, dwLocaleMask); } } break; @@ -886,10 +870,10 @@ static int LoadBuildManifest(TCascStorage * hs, DWORD dwLocaleMask) } else { - nError = GetLastError(); + dwErrCode = GetLastError(); } - return nError; + return dwErrCode; } static DWORD GetStorageTotalFileCount(TCascStorage * hs) @@ -902,7 +886,7 @@ static DWORD GetStorageTotalFileCount(TCascStorage * hs) { if((pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.ItemAt(i)) != NULL) { - if((pCKeyEntry->Flags & CASC_CE_FOLDER_ENTRY) == 0) + if(pCKeyEntry->IsFile()) { // If there is zero or one file name reference, we count the item as one file. // If there is more than 1 name reference, we count the file as many times as number of references @@ -925,9 +909,13 @@ static bool GetStorageProduct(TCascStorage * hs, void * pvStorageInfo, size_t cb pProductInfo = (PCASC_STORAGE_PRODUCT)ProbeOutputBuffer(pvStorageInfo, cbStorageInfo, sizeof(CASC_STORAGE_PRODUCT), pcbLengthNeeded); if(pProductInfo != NULL) { - pProductInfo->szProductName = hs->szProductName; - pProductInfo->dwBuildNumber = hs->dwBuildNumber; - pProductInfo->Product = hs->Product; + // Clear the entire structure + memset(pProductInfo, 0, sizeof(CASC_STORAGE_PRODUCT)); + + // Copy the product code name and build number + if(hs->szCodeName != NULL) + CascStrCopy(pProductInfo->szCodeName, _countof(pProductInfo->szCodeName), hs->szCodeName); + pProductInfo->BuildNumber = hs->dwBuildNumber; } return (pProductInfo != NULL); @@ -1083,21 +1071,13 @@ static DWORD InitializeLocalDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_AR static DWORD InitializeOnlineDirectories(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) { - LPCTSTR szLocalCache = pArgs->szLocalPath; - LPCTSTR szCodeName = pArgs->szCodeName; - // Create the root path - hs->szRootPath = CombinePath(szLocalCache, szCodeName); + hs->szRootPath = CascNewStr(pArgs->szLocalPath); if (hs->szRootPath != NULL) { - // Create the name of the build file - hs->szBuildFile = CombinePath(hs->szRootPath, _T("versions")); - if(hs->szBuildFile != NULL) - { - hs->BuildFileType = CascVersionsDb; - hs->dwFeatures |= CASC_FEATURE_ONLINE; - return ERROR_SUCCESS; - } + hs->BuildFileType = CascVersionsDb; + hs->dwFeatures |= CASC_FEATURE_ONLINE; + return ERROR_SUCCESS; } return ERROR_NOT_ENOUGH_MEMORY; @@ -1131,7 +1111,7 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) // For online storages, we need to load CDN servers if ((dwErrCode == ERROR_SUCCESS) && (hs->dwFeatures & CASC_FEATURE_ONLINE)) { - dwErrCode = LoadCdnsInfo(hs); + dwErrCode = LoadCdnsFile(hs); } // Now, load the main storage file ".build.info" (or ".build.db" in old storages) @@ -1145,6 +1125,8 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) if (dwErrCode == ERROR_SUCCESS) { dwErrCode = LoadCdnConfigFile(hs); + if(dwErrCode != ERROR_SUCCESS && (hs->dwFeatures & CASC_FEATURE_ONLINE) == 0) + dwErrCode = ERROR_SUCCESS; } // Proceed with loading the CDN build file @@ -1153,6 +1135,12 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) dwErrCode = LoadCdnBuildFile(hs); } + // Create the central file array + if(dwErrCode == ERROR_SUCCESS) + { + dwErrCode = InitCKeyArray(hs); + } + // Load the index files. Store information from the index files to the CKeyArray. if(dwErrCode == ERROR_SUCCESS) { @@ -1190,7 +1178,7 @@ static DWORD LoadCascStorage(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs) InsertWellKnownFile(hs, "ENCODING", hs->EncodingCKey); InsertWellKnownFile(hs, "DOWNLOAD", hs->DownloadCKey); InsertWellKnownFile(hs, "INSTALL", hs->InstallCKey); - InsertWellKnownFile(hs, "PATCH", hs->PatchFile); + InsertWellKnownFile(hs, "PATCH", hs->PatchFile, CASC_CE_FILE_PATCH); InsertWellKnownFile(hs, "ROOT", hs->RootFile); InsertWellKnownFile(hs, "SIZE", hs->SizeFile); @@ -1267,7 +1255,6 @@ static LPTSTR ParseOpenParams(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs) bool WINAPI CascOpenStorageEx(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs, bool bOnlineStorage, HANDLE * phStorage) { CASC_OPEN_STORAGE_ARGS LocalArgs = {sizeof(CASC_OPEN_STORAGE_ARGS)}; - DWORD (*PfnInitDirs)(TCascStorage * hs, PCASC_OPEN_STORAGE_ARGS pArgs); TCascStorage * hs; LPTSTR szParamsCopy = NULL; DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; @@ -1297,8 +1284,7 @@ bool WINAPI CascOpenStorageEx(LPCTSTR szParams, PCASC_OPEN_STORAGE_ARGS pArgs, b if((hs = new TCascStorage()) != NULL) { // Setup the directories - PfnInitDirs = (bOnlineStorage) ? InitializeOnlineDirectories : InitializeLocalDirectories; - dwErrCode = PfnInitDirs(hs, pArgs); + dwErrCode = (bOnlineStorage) ? InitializeOnlineDirectories(hs, pArgs) : InitializeLocalDirectories(hs, pArgs); if(dwErrCode == ERROR_SUCCESS) { // Perform the entire storage loading diff --git a/dep/CascLib/src/CascPort.h b/dep/CascLib/src/CascPort.h index 8172a5dde35..21cd991a09c 100644 --- a/dep/CascLib/src/CascPort.h +++ b/dep/CascLib/src/CascPort.h @@ -87,6 +87,7 @@ #include <errno.h> #include <stddef.h> #include <string.h> + #include <ctype.h> #include <cassert> // Support for PowerPC on Max OS X @@ -161,6 +162,8 @@ typedef int LONG; typedef unsigned int DWORD; typedef long long LONGLONG; + typedef signed long long LONGLONG; + typedef signed long long *PLONGLONG; typedef unsigned long long ULONGLONG; typedef unsigned long long *PULONGLONG; typedef void * HANDLE; @@ -304,6 +307,27 @@ #endif //----------------------------------------------------------------------------- +// Interlocked operations + +inline DWORD CascInterlockedIncrement(PDWORD PtrValue) +{ +#ifdef PLATFORM_WINDOWS + return (DWORD)InterlockedIncrement((LONG *)(PtrValue)); +#else + return ++PtrValue[0]; +#endif +} + +inline DWORD CascInterlockedDecrement(PDWORD PtrValue) +{ +#ifdef PLATFORM_WINDOWS + return (DWORD)InterlockedIncrement((LONG *)(PtrValue)); +#else + return --PtrValue[0]; +#endif +} + +//----------------------------------------------------------------------------- // Forbidden functions, do not use #ifdef __CASCLIB_SELF__ diff --git a/dep/CascLib/src/CascReadFile.cpp b/dep/CascLib/src/CascReadFile.cpp index 2f444d0d80f..aa2a718198c 100644 --- a/dep/CascLib/src/CascReadFile.cpp +++ b/dep/CascLib/src/CascReadFile.cpp @@ -15,62 +15,97 @@ //----------------------------------------------------------------------------- // Local functions -static int EnsureDataStreamIsOpen(TCascFile * hf) +static DWORD GetStreamEncodedSize(TFileStream * pStream) +{ + ULONGLONG FileSize = 0; + + FileStream_GetSize(pStream, &FileSize); + assert((FileSize >> 32) == 0); + + return (DWORD)(FileSize); +} + +static DWORD OpenDataStream(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, bool bDownloadFileIf) { TCascStorage * hs = hf->hs; TFileStream * pStream = NULL; - ULONGLONG EncodedSize = 0; - TCHAR * szDataFile; TCHAR szCachePath[MAX_PATH]; + TCHAR szDataFile[MAX_PATH]; TCHAR szPlainName[0x80]; - int nError; + DWORD dwErrCode; // If the file is available locally, we rely on data files. // If not, we download the file and open the stream - if(hf->pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL) + if(pCKeyEntry->Flags & CASC_CE_FILE_IS_LOCAL) { + DWORD dwArchiveIndex = pFileSpan->ArchiveIndex; + // If the file is not open yet, do it - if(hs->DataFiles[hf->ArchiveIndex] == NULL) + if(hs->DataFiles[dwArchiveIndex] == NULL) { // Prepare the name of the data file - CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), hf->ArchiveIndex); - szDataFile = CombinePath(hs->szIndexPath, szPlainName); + CascStrPrintf(szPlainName, _countof(szPlainName), _T("data.%03u"), dwArchiveIndex); + CombinePath(szDataFile, _countof(szDataFile), PATH_SEP_CHAR, hs->szIndexPath, szPlainName, NULL); - // Open the data file - if(szDataFile != NULL) - { - // Open the data stream with read+write sharing to prevent Battle.net agent - // detecting a corruption and redownloading the entire package - pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_FLAG_WRITE_SHARE | STREAM_PROVIDER_FLAT | STREAM_FLAG_FILL_MISSING | BASE_PROVIDER_FILE); - hs->DataFiles[hf->ArchiveIndex] = pStream; - CASC_FREE(szDataFile); - } + // Open the data stream with read+write sharing to prevent Battle.net agent + // detecting a corruption and redownloading the entire package + pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_FLAG_WRITE_SHARE | STREAM_PROVIDER_FLAT | STREAM_FLAG_FILL_MISSING | BASE_PROVIDER_FILE); + hs->DataFiles[dwArchiveIndex] = pStream; } // Return error or success - hf->pStream = hs->DataFiles[hf->ArchiveIndex]; - return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; + pFileSpan->pStream = hs->DataFiles[dwArchiveIndex]; + return (pFileSpan->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND; } else { - if(hf->bDownloadFileIf) + if(bDownloadFileIf) { - // Create the local folder path and download the file from CDN - nError = DownloadFileFromCDN(hf->hs, _T("data"), hf->pCKeyEntry->EKey, NULL, szCachePath, _countof(szCachePath)); - if(nError == ERROR_SUCCESS) + CASC_CDN_DOWNLOAD CdnsInfo = {0}; + LPCTSTR szPathType = (pCKeyEntry->Flags & CASC_CE_FILE_PATCH) ? _T("patch") : _T("data"); + + // Prepare the download structure for "%CDNS_HOST%/%CDNS_PATH%/##/##/EKey" file + CdnsInfo.szCdnsPath = hs->szCdnPath; + CdnsInfo.szPathType = szPathType; + CdnsInfo.pbEKey = pCKeyEntry->EKey; + CdnsInfo.szLocalPath = szCachePath; + CdnsInfo.ccLocalPath = _countof(szCachePath); + + // Download the file from CDN + dwErrCode = DownloadFileFromCDN(hs, CdnsInfo); + if(dwErrCode == ERROR_SUCCESS) { - hf->pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT); - if(hf->pStream != NULL) + pStream = FileStream_OpenFile(szCachePath, BASE_PROVIDER_FILE | STREAM_PROVIDER_FLAT); + if(pStream != NULL) { - // Supply the file size, if unknown yet - if(hf->EncodedSize == CASC_INVALID_SIZE) + // Initialize information about the position and size of the file in archive + // On loose files, their position is zero and encoded size is length of the file + if(CdnsInfo.pbArchiveKey != NULL) { - FileStream_GetSize(hf->pStream, &EncodedSize); - hf->pCKeyEntry->EncodedSize = (DWORD)EncodedSize; - hf->EncodedSize = (DWORD)EncodedSize; + // Archive position + pFileSpan->ArchiveIndex = CdnsInfo.ArchiveIndex; + pFileSpan->ArchiveOffs = (DWORD)CdnsInfo.ArchiveOffs; + + // Encoded size + if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) + pCKeyEntry->EncodedSize = CdnsInfo.EncodedSize; + assert(pCKeyEntry->EncodedSize == CdnsInfo.EncodedSize); + } + else + { + // Archive position + pFileSpan->ArchiveIndex = 0; + pFileSpan->ArchiveOffs = 0; + + // Encoded size + if(pCKeyEntry->EncodedSize == CASC_INVALID_SIZE) + pCKeyEntry->EncodedSize = GetStreamEncodedSize(pStream); + assert(pCKeyEntry->EncodedSize == GetStreamEncodedSize(pStream)); } - hf->bLocalFileStream = true; + // We need to close the file stream after we're done + pFileSpan->pStream = pStream; + hf->bCloseFileStream = true; return ERROR_SUCCESS; } } @@ -125,7 +160,7 @@ static void VerifyHeaderSpan(PBLTE_ENCODED_HEADER pBlteHeader, ULONGLONG HeaderO } #endif -static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize) +static DWORD ParseBlteHeader(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG HeaderOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t * pcbHeaderSize) { PBLTE_ENCODED_HEADER pEncodedHeader = (PBLTE_ENCODED_HEADER)pbEncodedBuffer; PBLTE_HEADER pBlteHeader = (PBLTE_HEADER)pbEncodedBuffer; @@ -143,12 +178,12 @@ static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEnco // There must be at least some bytes if (cbEncodedBuffer < FIELD_OFFSET(BLTE_ENCODED_HEADER, MustBe0F)) return ERROR_BAD_FORMAT; - if (pEncodedHeader->EncodedSize != hf->EncodedSize) + if (pEncodedHeader->EncodedSize != pCKeyEntry->EncodedSize) return ERROR_BAD_FORMAT; #ifdef _DEBUG // Not really needed, it's here just for explanation of what the values mean - //assert(memcmp(hf->pCKeyEntry->EKey, pEncodedHeader->EKey.Value, MD5_HASH_SIZE) == 0); + //assert(memcmp(pCKeyEntry->EKey, pEncodedHeader->EKey.Value, MD5_HASH_SIZE) == 0); VerifyHeaderSpan(pEncodedHeader, HeaderOffset); #endif // Capture the EKey @@ -183,11 +218,11 @@ static int ParseBlteHeader(TCascFile * hf, ULONGLONG HeaderOffset, LPBYTE pbEnco } // Give the frame count - hf->FrameCount = FrameCount; + pFileSpan->FrameCount = FrameCount; return ERROR_SUCCESS; } -static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize) +static LPBYTE ReadMissingHeaderData(PCASC_FILE_SPAN pFileSpan, ULONGLONG DataFileOffset, LPBYTE pbEncodedBuffer, size_t cbEncodedBuffer, size_t cbTotalHeaderSize) { LPBYTE pbNewBuffer; @@ -197,7 +232,7 @@ static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LP { // Load the missing data DataFileOffset += cbEncodedBuffer; - if (FileStream_Read(hf->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer))) + if (FileStream_Read(pFileSpan->pStream, &DataFileOffset, pbNewBuffer + cbEncodedBuffer, (DWORD)(cbTotalHeaderSize - cbEncodedBuffer))) { return pbNewBuffer; } @@ -208,138 +243,200 @@ static LPBYTE ReadMissingHeaderData(TCascFile * hf, ULONGLONG DataFileOffset, LP return NULL; } -static int LoadFileFrames(TCascFile * hf, ULONGLONG DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize) +static LPBYTE CaptureBlteFileFrame(CASC_FILE_FRAME & Frame, LPBYTE pbFramePtr, LPBYTE pbFrameEnd) +{ + PBLTE_FRAME pFileFrame = (PBLTE_FRAME)pbFramePtr; + + // Check whether we have enough data ready + if((pbFramePtr + sizeof(BLTE_FRAME)) > pbFrameEnd) + return NULL; + + Frame.FrameHash = pFileFrame->FrameHash; + Frame.ContentSize = ConvertBytesToInteger_4(pFileFrame->ContentSize); + Frame.EncodedSize = ConvertBytesToInteger_4(pFileFrame->EncodedSize); + return pbFramePtr + sizeof(BLTE_FRAME); +} + +static DWORD LoadSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry, DWORD DataFileOffset, LPBYTE pbFramePtr, LPBYTE pbFrameEnd, size_t cbHeaderSize) { - PBLTE_FRAME pFileFrame; + PCASC_FILE_FRAME pFrames = NULL; DWORD ContentSize = 0; - DWORD FileOffset = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; - assert(hf != NULL); - assert(hf->pStream != NULL); - assert(hf->pFrames == NULL); + assert(pFileSpan != NULL); + assert(pFileSpan->pStream != NULL); + assert(pFileSpan->pFrames == NULL); - if (hf->FrameCount != 0) + if (pFileSpan->FrameCount != 0) { // Move the raw archive offset - DataFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME)); + DataFileOffset += (pFileSpan->FrameCount * sizeof(BLTE_FRAME)); // Allocate array of file frames - hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount); - if (hf->pFrames != NULL) + pFrames = CASC_ALLOC<CASC_FILE_FRAME>(pFileSpan->FrameCount); + if (pFrames != NULL) { // Copy the frames to the file structure - for (DWORD i = 0; i < hf->FrameCount; i++, pbFramePtr += sizeof(BLTE_FRAME)) + for (DWORD i = 0; i < pFileSpan->FrameCount; i++) { - // Capture the file frame - if ((pbFramePtr + sizeof(BLTE_FRAME)) > pbFrameEnd) - return ERROR_BAD_FORMAT; - pFileFrame = (PBLTE_FRAME)pbFramePtr; - - // Convert the file frame to the native format - hf->pFrames[i].DataFileOffset = (DWORD)DataFileOffset; - hf->pFrames[i].FileOffset = CASC_INVALID_POS; - hf->pFrames[i].EncodedSize = ConvertBytesToInteger_4(pFileFrame->EncodedSize); - hf->pFrames[i].ContentSize = ConvertBytesToInteger_4(pFileFrame->ContentSize); - hf->pFrames[i].FrameHash = pFileFrame->FrameHash; - - DataFileOffset += hf->pFrames[i].EncodedSize; - ContentSize += hf->pFrames[i].ContentSize; - FileOffset += hf->pFrames[i].ContentSize; + CASC_FILE_FRAME & Frame = pFrames[i]; + + // Capture the single BLTE frame + pbFramePtr = CaptureBlteFileFrame(Frame, pbFramePtr, pbFrameEnd); + if(pbFramePtr == NULL) + { + dwErrCode = ERROR_BAD_FORMAT; + break; + } + + // Fill-in the file range of the frame + Frame.StartOffset = pFileSpan->StartOffset + ContentSize; + Frame.EndOffset = Frame.StartOffset + Frame.ContentSize; + ContentSize += Frame.ContentSize; + + // Fill-in the archive range of the frame + assert((DataFileOffset + Frame.EncodedSize) > DataFileOffset); + Frame.DataFileOffset = DataFileOffset; + DataFileOffset += Frame.EncodedSize; } // Save the content size of the file - if(hf->pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) { - hf->pCKeyEntry->ContentSize = ContentSize; - hf->ContentSize = ContentSize; + pCKeyEntry->ContentSize = ContentSize; } } + else + { + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + } } else { - // The content size in the file structure must be valid at this point, - // otherwise we don't know the frame content size - if (hf->ContentSize == CASC_INVALID_SIZE) + // Allocate single "dummy" frame + pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1); + if (pFrames != NULL) { - assert(false); - return ERROR_CAN_NOT_COMPLETE; + // Fill the single frame + memset(&pFrames->FrameHash, 0, sizeof(CONTENT_KEY)); + pFrames->StartOffset = pFileSpan->StartOffset; + pFrames->EndOffset = pFileSpan->EndOffset; + pFrames->DataFileOffset = DataFileOffset; + pFrames->EncodedSize = (DWORD)(pCKeyEntry->EncodedSize - cbHeaderSize); + pFrames->ContentSize = pCKeyEntry->ContentSize; + + // Save the number of file frames + pFileSpan->FrameCount = 1; } - - // Save the number of file frames - hf->FrameCount = 1; - - // Allocate single "dummy" frame - hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, 1); - if (hf->pFrames != NULL) + else { - memset(&hf->pFrames->FrameHash, 0, sizeof(CONTENT_KEY)); - hf->pFrames->DataFileOffset = (DWORD)DataFileOffset; - hf->pFrames->FileOffset = CASC_INVALID_POS; - hf->pFrames->EncodedSize = (DWORD)(hf->EncodedSize - cbHeaderSize); - hf->pFrames->ContentSize = hf->ContentSize; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } } - if (hf->pFrames == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - return nError; + // Free the frame array on error + if(dwErrCode != ERROR_SUCCESS) + { + pFileSpan->FrameCount = 0; + CASC_FREE(pFrames); + } + + pFileSpan->pFrames = pFrames; + return dwErrCode; } -static int LoadEncodedHeaderAndFileFrames(TCascFile * hf) +static DWORD LoadSpanFramesForPlainFile(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry) +{ + PCASC_FILE_FRAME pFrames; + + // Allocate single "dummy" frame + pFrames = CASC_ALLOC<CASC_FILE_FRAME>(1); + if (pFrames != NULL) + { + // Setup the size + pFileSpan->EndOffset = pFileSpan->StartOffset + pCKeyEntry->ContentSize; + pCKeyEntry->Flags |= CASC_CE_PLAIN_DATA; + + // Fill the single frame + memset(&pFrames->FrameHash, 0, sizeof(CONTENT_KEY)); + pFrames->StartOffset = pFileSpan->StartOffset; + pFrames->EndOffset = pFrames->StartOffset + pCKeyEntry->ContentSize; + pFrames->DataFileOffset = 0; + pFrames->EncodedSize = pCKeyEntry->EncodedSize; + pFrames->ContentSize = pCKeyEntry->ContentSize; + + // Save the number of file frames + pFileSpan->FrameCount = 1; + pFileSpan->pFrames = pFrames; + return ERROR_SUCCESS; + } + + return ERROR_NOT_ENOUGH_MEMORY; +} + +static DWORD LoadEncodedHeaderAndSpanFrames(PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry) { LPBYTE pbEncodedBuffer; size_t cbEncodedBuffer = MAX_ENCODED_HEADER; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Should only be called when the file frames are NOT loaded - assert(hf->pFrames == NULL); - assert(hf->FrameCount == 0); + assert(pFileSpan->pFrames == NULL); + assert(pFileSpan->FrameCount == 0); // Allocate the initial buffer for the encoded headers - pbEncodedBuffer = CASC_ALLOC(BYTE, MAX_ENCODED_HEADER); + pbEncodedBuffer = CASC_ALLOC<BYTE>(MAX_ENCODED_HEADER); if (pbEncodedBuffer != NULL) { - ULONGLONG ReadOffset = hf->ArchiveOffset; + ULONGLONG ReadOffset = pFileSpan->ArchiveOffs; size_t cbTotalHeaderSize; size_t cbHeaderSize = 0; // At this point, we expect encoded size to be known - assert(hf->EncodedSize != CASC_INVALID_SIZE); + assert(pCKeyEntry->EncodedSize != CASC_INVALID_SIZE); // Do not read more than encoded size - cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, hf->EncodedSize); + cbEncodedBuffer = CASCLIB_MIN(cbEncodedBuffer, pCKeyEntry->EncodedSize); // Load the entire (eventual) header area. This is faster than doing // two read operations in a row. Read as much as possible. If the file is cut, // the FileStream will pad it with zeros - if (FileStream_Read(hf->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer)) + if (FileStream_Read(pFileSpan->pStream, &ReadOffset, pbEncodedBuffer, (DWORD)cbEncodedBuffer)) { // Parse the BLTE header - nError = ParseBlteHeader(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize); - if (nError == ERROR_SUCCESS) + dwErrCode = ParseBlteHeader(pFileSpan, pCKeyEntry, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, &cbHeaderSize); + if (dwErrCode == ERROR_SUCCESS) { - // If the headers are larger than the initial read size, - // We read the missing data - cbTotalHeaderSize = cbHeaderSize + (hf->FrameCount * sizeof(BLTE_FRAME)); + // If the headers are larger than the initial read size, we read the missing data + pFileSpan->HeaderSize = (DWORD)(cbTotalHeaderSize = cbHeaderSize + (pFileSpan->FrameCount * sizeof(BLTE_FRAME))); if (cbTotalHeaderSize > cbEncodedBuffer) { - pbEncodedBuffer = ReadMissingHeaderData(hf, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize); + pbEncodedBuffer = ReadMissingHeaderData(pFileSpan, ReadOffset, pbEncodedBuffer, cbEncodedBuffer, cbTotalHeaderSize); if (pbEncodedBuffer == NULL) - nError = GetLastError(); + dwErrCode = GetLastError(); cbEncodedBuffer = cbTotalHeaderSize; } // Load the array of frame headers - if (nError == ERROR_SUCCESS) + if (dwErrCode == ERROR_SUCCESS) + { + assert((DWORD)(ReadOffset + cbHeaderSize) > (DWORD)ReadOffset); + dwErrCode = LoadSpanFrames(pFileSpan, pCKeyEntry, (DWORD)(ReadOffset + cbHeaderSize), pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize); + } + } + else + { + // Special treatment for plain files ("PATCH"): If the content size and encoded size + // are equal, we will create a single fake frame + if(pCKeyEntry->EncodedSize == pCKeyEntry->ContentSize) { - nError = LoadFileFrames(hf, ReadOffset + cbHeaderSize, pbEncodedBuffer + cbHeaderSize, pbEncodedBuffer + cbEncodedBuffer, cbHeaderSize); + dwErrCode = LoadSpanFramesForPlainFile(pFileSpan, pCKeyEntry); } } } else { - nError = ERROR_FILE_CORRUPT; + dwErrCode = ERROR_FILE_CORRUPT; } // Free the frame buffer @@ -347,67 +444,137 @@ static int LoadEncodedHeaderAndFileFrames(TCascFile * hf) } else { - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } - return nError; + return dwErrCode; } -static int EnsureFileFramesLoaded(TCascFile * hf) +static DWORD LoadSpanFrames(TCascFile * hf, PCASC_FILE_SPAN pFileSpan, PCASC_CKEY_ENTRY pCKeyEntry) { - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; - // If the encoded frames are not loaded, do it now - if(hf->pFrames == NULL) - { - // We need the data file to be open - nError = EnsureDataStreamIsOpen(hf); - if(nError != ERROR_SUCCESS) - return nError; + // Sanity check + assert(pFileSpan->pFrames == NULL); - // Make sure we have header area loaded - nError = LoadEncodedHeaderAndFileFrames(hf); + // Make sure that the data stream is open for that span + if(pFileSpan->pStream == NULL) + { + dwErrCode = OpenDataStream(hf, pFileSpan, pCKeyEntry, hf->bDownloadFileIf); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; } - return nError; + // Make sure we have header area loaded + return LoadEncodedHeaderAndSpanFrames(pFileSpan, pCKeyEntry); } -static int LoadEncodedFrame(TFileStream * pStream, PCASC_FILE_FRAME pFrame, LPBYTE pbEncodedFrame, bool bVerifyIntegrity) +// Loads all file spans to memory +static DWORD LoadFileSpanFrames(TCascFile * hf) { - ULONGLONG FileOffset = pFrame->DataFileOffset; - int nError = ERROR_SUCCESS; + PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry; + PCASC_FILE_SPAN pFileSpan = hf->pFileSpan; + DWORD dwErrCode = ERROR_SUCCESS; - // Load the encoded frame to memory - if(FileStream_Read(pStream, &FileOffset, pbEncodedFrame, pFrame->EncodedSize)) + // If the ContentSize/EncodedSize is still unknown, we need to get it from the file frames + if(hf->ContentSize == CASC_INVALID_SIZE64 || hf->EncodedSize == CASC_INVALID_SIZE64) { - if (bVerifyIntegrity) + // Set initially to zero + hf->ContentSize = 0; + hf->EncodedSize = 0; + + // Load file frames for all spans + for(DWORD i = 0; i < hf->SpanCount; i++, pCKeyEntry++, pFileSpan++) { - if (!CascVerifyDataBlockHash(pbEncodedFrame, pFrame->EncodedSize, pFrame->FrameHash.Value)) - nError = ERROR_FILE_CORRUPT; + // Init the range of the file span + pFileSpan->StartOffset = hf->ContentSize; + pFileSpan->EndOffset = hf->ContentSize; + + // Load the frames of the file span + dwErrCode = LoadSpanFrames(hf, pFileSpan, pCKeyEntry); + if(dwErrCode != ERROR_SUCCESS) + break; + + hf->ContentSize += pCKeyEntry->ContentSize; + hf->EncodedSize += pCKeyEntry->EncodedSize; + pFileSpan->EndOffset = hf->ContentSize; } } else { - nError = GetLastError(); + // Load file frames for all spans + for(DWORD i = 0; i < hf->SpanCount; i++, pCKeyEntry++, pFileSpan++) + { + // Load the frames of the file span + dwErrCode = LoadSpanFrames(hf, pFileSpan, pCKeyEntry); + if(dwErrCode != ERROR_SUCCESS) + break; + } } - return nError; + return dwErrCode; } -static int ProcessFileFrame( - TCascStorage * hs, - LPBYTE pbOutBuffer, - DWORD cbOutBuffer, - LPBYTE pbInBuffer, - DWORD cbInBuffer, - DWORD dwFrameIndex) +static DWORD EnsureFileSpanFramesLoaded(TCascFile * hf) { + DWORD dwErrCode; + + if(hf->ContentSize == CASC_INVALID_SIZE64 || hf->pFileSpan->pFrames == NULL) + { + // Load all frames of all file spans + dwErrCode = LoadFileSpanFrames(hf); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + + // Now the content size must be known + if(hf->ContentSize == CASC_INVALID_SIZE64) + return ERROR_CAN_NOT_COMPLETE; + } + + return ERROR_SUCCESS; +} + +static DWORD DecodeFileFrame( + TCascFile * hf, + PCASC_CKEY_ENTRY pCKeyEntry, + PCASC_FILE_FRAME pFrame, + LPBYTE pbEncoded, + LPBYTE pbDecoded, + DWORD FrameIndex) +{ + TCascStorage * hs = hf->hs; LPBYTE pbWorkBuffer = NULL; - DWORD cbOutBufferExpected = 0; + DWORD cbDecodedExpected = 0; DWORD cbWorkBuffer = 0; DWORD dwStepCount = 0; + DWORD dwErrCode = ERROR_SUCCESS; + DWORD cbEncoded = pFrame->EncodedSize; + DWORD cbDecoded = pFrame->ContentSize; bool bWorkComplete = false; - int nError = ERROR_SUCCESS; + + //if(pFrame->EncodedSize == 0xda001) + //{ + // FILE * fp = fopen("E:\\frame-da001-002.dat", "wb"); + // fwrite(pbEncoded, 1, pFrame->EncodedSize, fp); + // fclose(fp); + //} + + // If this is a file span with plain data, just copy the data + if(pCKeyEntry->Flags & CASC_CE_PLAIN_DATA) + { + assert(pCKeyEntry->ContentSize == pCKeyEntry->EncodedSize); + assert(pCKeyEntry->ContentSize == pFrame->ContentSize); + assert(pFrame->ContentSize == pFrame->EncodedSize); + memcpy(pbDecoded, pbEncoded, pCKeyEntry->ContentSize); + return ERROR_SUCCESS; + } + + // Shall we verify the frame integrity? + if(hf->bVerifyIntegrity) + { + if(!CascVerifyDataBlockHash(pbEncoded, pFrame->EncodedSize, pFrame->FrameHash.Value)) + return ERROR_FILE_CORRUPT; + } // Perform the loop while(bWorkComplete == false) @@ -416,7 +583,7 @@ static int ProcessFileFrame( assert(dwStepCount < 2); // Perform the operation specific by the first byte - switch(pbInBuffer[0]) + switch(pbEncoded[0]) { case 'E': // Encrypted files @@ -425,14 +592,14 @@ static int ProcessFileFrame( // Allocate temporary buffer to decrypt into // Example storage: "2016 - WoW/23420", File: "4ee6bc9c6564227f1748abd0b088e950" - pbWorkBuffer = CASC_ALLOC(BYTE, cbInBuffer - 1); - cbWorkBuffer = cbInBuffer - 1; + pbWorkBuffer = CASC_ALLOC<BYTE>(cbEncoded - 1); + cbWorkBuffer = cbEncoded - 1; if(pbWorkBuffer == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Decrypt the stream to the work buffer - nError = CascDecrypt(hs, pbWorkBuffer, &cbWorkBuffer, pbInBuffer + 1, cbInBuffer - 1, dwFrameIndex); - if(nError != ERROR_SUCCESS) + dwErrCode = CascDecrypt(hs, pbWorkBuffer, &cbWorkBuffer, pbEncoded + 1, cbEncoded - 1, FrameIndex); + if(dwErrCode != ERROR_SUCCESS) { bWorkComplete = true; break; @@ -440,32 +607,32 @@ static int ProcessFileFrame( // When encrypted, there is always one more step after this. // Setup the work buffer as input buffer for the next operation - pbInBuffer = pbWorkBuffer; - cbInBuffer = cbWorkBuffer; + pbEncoded = pbWorkBuffer; + cbEncoded = cbWorkBuffer; break; case 'Z': // ZLIB compressed files // If we decompressed less than expected, we simply fill the rest with zeros // Example: INSTALL file from the TACT CASC storage - cbOutBufferExpected = cbOutBuffer; - nError = CascDecompress(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1); + cbDecodedExpected = cbDecoded; + dwErrCode = CascDecompress(pbDecoded, &cbDecoded, pbEncoded + 1, cbEncoded - 1); // We exactly know what the output buffer size will be. // If the uncompressed data is smaller, fill the rest with zeros - if(cbOutBuffer < cbOutBufferExpected) - memset(pbOutBuffer + cbOutBuffer, 0, (cbOutBufferExpected - cbOutBuffer)); + if(cbDecoded < cbDecodedExpected) + memset(pbDecoded + cbDecoded, 0, (cbDecodedExpected - cbDecoded)); bWorkComplete = true; break; case 'N': // Normal stored files - nError = CascDirectCopy(pbOutBuffer, &cbOutBuffer, pbInBuffer + 1, cbInBuffer - 1); + dwErrCode = CascDirectCopy(pbDecoded, &cbDecoded, pbEncoded + 1, cbEncoded - 1); bWorkComplete = true; break; - case 'F': // Recursive frames - not supported - default: // Unrecognized - if we unpacked something, we consider it done - nError = ERROR_NOT_SUPPORTED; + case 'F': // Recursive frames (not supported) + default: // Unrecognized. Could be a plain file data + dwErrCode = ERROR_NOT_SUPPORTED; bWorkComplete = true; assert(false); break; @@ -475,9 +642,18 @@ static int ProcessFileFrame( dwStepCount++; } + // Some people find it handy to extract data from partially encrypted file, + // even at the cost of producing corrupt files. + // We overcome missing decryption key by zeroing the encrypted portions + if(dwErrCode == ERROR_FILE_ENCRYPTED && hf->bOvercomeEncrypted) + { + memset(pbDecoded, 0, cbDecoded); + dwErrCode = ERROR_SUCCESS; + } + // Free the temporary buffer CASC_FREE(pbWorkBuffer); - return nError; + return dwErrCode; } static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded) @@ -485,6 +661,15 @@ static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo PCASC_FILE_FULL_INFO pFileInfo; PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry; TCascStorage * hs = hf->hs; + DWORD dwErrCode; + + // Make sure that the file spans are loaded + dwErrCode = EnsureFileSpanFramesLoaded(hf); + if(dwErrCode != ERROR_SUCCESS) + { + SetLastError(dwErrCode); + return false; + } // Verify whether we have enough space in the buffer pFileInfo = (PCASC_FILE_FULL_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_FULL_INFO), pcbLengthNeeded); @@ -498,14 +683,15 @@ static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo pFileInfo->ContentFlags = CASC_INVALID_ID; // Supply information not depending on root - CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->ArchiveIndex); + CascStrPrintf(pFileInfo->DataFileName, _countof(pFileInfo->DataFileName), "data.%03u", hf->pFileSpan->ArchiveIndex); pFileInfo->StorageOffset = pCKeyEntry->StorageOffset; - pFileInfo->SegmentOffset = hf->ArchiveOffset; + pFileInfo->SegmentOffset = hf->pFileSpan->ArchiveOffs; pFileInfo->FileNameHash = 0; pFileInfo->TagBitMask = pCKeyEntry->TagBitMask; - pFileInfo->SegmentIndex = hf->ArchiveIndex; pFileInfo->ContentSize = hf->ContentSize; pFileInfo->EncodedSize = hf->EncodedSize; + pFileInfo->SegmentIndex = hf->pFileSpan->ArchiveIndex; + pFileInfo->SpanCount = hf->SpanCount; // Supply the root-specific information hs->pRootHandler->GetInfo(pCKeyEntry, pFileInfo); @@ -514,6 +700,257 @@ static bool GetFileFullInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo return (pFileInfo != NULL); } +static bool GetFileSpanInfo(TCascFile * hf, void * pvFileInfo, size_t cbFileInfo, size_t * pcbLengthNeeded) +{ + PCASC_FILE_SPAN_INFO pFileInfo; + PCASC_FILE_SPAN pFileSpan = hf->pFileSpan; + PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry; + DWORD dwErrCode = ERROR_SUCCESS; + + // Make sure that the file spans are loaded + dwErrCode = EnsureFileSpanFramesLoaded(hf); + if(dwErrCode != ERROR_SUCCESS) + { + SetLastError(dwErrCode); + return false; + } + + // Verify whether we have enough space in the buffer + pFileInfo = (PCASC_FILE_SPAN_INFO)ProbeOutputBuffer(pvFileInfo, cbFileInfo, sizeof(CASC_FILE_SPAN_INFO) * hf->SpanCount, pcbLengthNeeded); + if(pFileInfo != NULL) + { + // Copy all file spans + for(DWORD i = 0; i < hf->SpanCount; i++, pFileInfo++, pFileSpan++, pCKeyEntry++) + { + CopyMemory16(pFileInfo->CKey, pCKeyEntry->CKey); + CopyMemory16(pFileInfo->EKey, pCKeyEntry->EKey); + pFileInfo->StartOffset = pFileSpan->StartOffset; + pFileInfo->EndOffset = pFileSpan->EndOffset; + pFileInfo->ArchiveIndex = pFileSpan->ArchiveIndex; + pFileInfo->ArchiveOffs = pFileSpan->ArchiveOffs; + pFileInfo->HeaderSize = pFileSpan->HeaderSize; + pFileInfo->FrameCount = pFileSpan->FrameCount; + } + } + + return (pFileInfo != NULL); +} + + +// Reads the file data from cache. Returns the number of bytes read +static DWORD ReadFile_Cache(TCascFile * hf, LPBYTE pbBuffer, ULONGLONG StartOffset, ULONGLONG EndOffset) +{ + // Is there a file cache at all? + if(hf->pbFileCache != NULL && hf->FileCacheStart <= StartOffset && StartOffset < hf->FileCacheEnd) + { + LPBYTE pbStartBlock = hf->pbFileCache + (size_t)(StartOffset - hf->FileCacheStart); + + // Can we handle the entire request from the cache? + if(EndOffset <= hf->FileCacheEnd) + { + DWORD dwBytesToCopy = (DWORD)(EndOffset - StartOffset); + + memcpy(pbBuffer, pbStartBlock, dwBytesToCopy); + return dwBytesToCopy; + } + + // We copy as much bytes as available. The rest is handled by normal read + else + { + DWORD dwBytesToCopy = (DWORD)(hf->FileCacheEnd - StartOffset); + + memcpy(pbBuffer, pbStartBlock, dwBytesToCopy); + return dwBytesToCopy; + } + } + + // Can't handle the request from the cache + return 0; +} + +// No cache at all. The entire file will be read directly to the user buffer +static DWORD ReadFile_WholeFile(TCascFile * hf, LPBYTE pbBuffer) +{ + PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry; + PCASC_FILE_SPAN pFileSpan = hf->pFileSpan; + LPBYTE pbSaveBuffer = pbBuffer; + LPBYTE pbEncoded; + LPBYTE pbEncodedPtr; + DWORD dwErrCode; + + for(DWORD SpanIndex = 0; SpanIndex < hf->SpanCount; SpanIndex++, pCKeyEntry++, pFileSpan++) + { + ULONGLONG ByteOffset = pFileSpan->ArchiveOffs + pFileSpan->HeaderSize; + DWORD EncodedSize = pCKeyEntry->EncodedSize - pFileSpan->HeaderSize; + + // Allocate the buffer for the entire encoded span + pbEncodedPtr = pbEncoded = CASC_ALLOC<BYTE>(EncodedSize); + if(pbEncoded == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; + } + + // Load the encoded buffer + if(FileStream_Read(pFileSpan->pStream, &ByteOffset, pbEncoded, EncodedSize)) + { + PCASC_FILE_FRAME pFileFrame = pFileSpan->pFrames; + + for(DWORD FrameIndex = 0; FrameIndex < pFileSpan->FrameCount; FrameIndex++, pFileFrame++) + { + // Decode the file frame + dwErrCode = DecodeFileFrame(hf, pCKeyEntry, pFileFrame, pbEncodedPtr, pbBuffer, FrameIndex); + if(dwErrCode != ERROR_SUCCESS) + break; + + // Move pointers + pbEncodedPtr += pFileFrame->EncodedSize; + pbBuffer += pFileFrame->ContentSize; + } + } + + CASC_FREE(pbEncoded); + } + + // Give the amount of bytes read + return (DWORD)(pbBuffer - pbSaveBuffer); +} + +static DWORD ReadFile_FrameCached(TCascFile * hf, LPBYTE pbBuffer, ULONGLONG StartOffset, ULONGLONG EndOffset) +{ + PCASC_CKEY_ENTRY pCKeyEntry = hf->pCKeyEntry; + PCASC_FILE_SPAN pFileSpan = hf->pFileSpan; + PCASC_FILE_FRAME pFileFrame = NULL; + ULONGLONG ByteOffset; + LPBYTE pbSaveBuffer = pbBuffer; + LPBYTE pbEncoded = NULL; + LPBYTE pbDecoded = NULL; + DWORD dwBytesRead = 0; + DWORD dwErrCode = ERROR_SUCCESS; + bool bNeedFreeDecoded = true; + + // Parse all file spans + for(DWORD SpanIndex = 0; SpanIndex < hf->SpanCount; SpanIndex++, pCKeyEntry++, pFileSpan++) + { + if(pFileSpan->StartOffset <= StartOffset && StartOffset < pFileSpan->EndOffset) + { + for(DWORD FrameIndex = 0; FrameIndex < pFileSpan->FrameCount; FrameIndex++) + { + // Get the current file frame + pFileFrame = pFileSpan->pFrames + FrameIndex; + + // Check the frame byte range + if(pFileFrame->StartOffset <= StartOffset && StartOffset < pFileFrame->EndOffset) + { + // Check bytes read overflow + if((dwBytesRead + pFileFrame->ContentSize) < dwBytesRead) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return 0; + } + + // Pick the buffer for decoded data. If we are going to read the entire frame, + // there is a little chance that the caller will read the same file range again + // So we can as well just unpack the entire frame into the output buffer + if(pFileFrame->StartOffset < StartOffset || EndOffset < pFileFrame->EndOffset) + { + if((pbDecoded = CASC_ALLOC<BYTE>(pFileFrame->ContentSize)) == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; + } + bNeedFreeDecoded = true; + } + else + { + bNeedFreeDecoded = false; + pbDecoded = pbBuffer; + } + + // Allocate the encoded frame + if((pbEncoded = CASC_ALLOC<BYTE>(pFileFrame->EncodedSize)) == NULL) + { + CASC_FREE(pbDecoded); + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return 0; + } + + // Load the frame to the encoded buffer + ByteOffset = pFileFrame->DataFileOffset; + if(FileStream_Read(pFileSpan->pStream, &ByteOffset, pbEncoded, pFileFrame->EncodedSize)) + { + ULONGLONG EndOfCopy = CASCLIB_MIN(pFileFrame->EndOffset, EndOffset); + DWORD dwBytesToCopy = (DWORD)(EndOfCopy - StartOffset); + + // Decode the frame + dwErrCode = DecodeFileFrame(hf, pCKeyEntry, pFileFrame, pbEncoded, pbDecoded, FrameIndex); + if(dwErrCode == ERROR_SUCCESS) + { + // Copy the data + if(pbDecoded != pbBuffer) + memcpy(pbBuffer, pbDecoded + (DWORD)(StartOffset - pFileFrame->StartOffset), dwBytesToCopy); + StartOffset += dwBytesToCopy; + pbBuffer += dwBytesToCopy; + } + } + + // Free the encoded buffer + CASC_FREE(pbEncoded); + + // If we are at the end of the read area, break all loops + if(dwErrCode != ERROR_SUCCESS || StartOffset >= EndOffset) + goto __WorkComplete; + if(bNeedFreeDecoded) + CASC_FREE(pbDecoded); + } + } + } + } + + __WorkComplete: + + if(dwErrCode == ERROR_SUCCESS) + { + // If there is some data left in the frame, we set it as cache + if(pFileFrame != NULL && pbDecoded != NULL && EndOffset < pFileFrame->EndOffset) + { + CASC_FREE(hf->pbFileCache); + + hf->FileCacheStart = pFileFrame->StartOffset; + hf->FileCacheEnd = pFileFrame->EndOffset; + hf->pbFileCache = pbDecoded; + pbDecoded = NULL; + } + } + + // Final free of the decoded buffer, if needeed + if(bNeedFreeDecoded) + CASC_FREE(pbDecoded); + pbDecoded = NULL; + + // Return the number of bytes read. Always set LastError. + SetLastError(dwErrCode); + return (DWORD)(pbBuffer - pbSaveBuffer); +} + +// No cache at all. The entire file will be read directly to the user buffer +static DWORD ReadFile_NonCached(TCascFile * hf, LPBYTE pbBuffer, ULONGLONG StartOffset, ULONGLONG EndOffset) +{ + // Reading the whole file? + if(StartOffset == 0 && EndOffset == hf->ContentSize) + { + return ReadFile_WholeFile(hf, pbBuffer); + } + + // Reading just a part of the file? + else + { + assert(false); + } + + return 0; +} + //----------------------------------------------------------------------------- // Public functions @@ -565,6 +1002,9 @@ bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * case CascFileFullInfo: return GetFileFullInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded); + case CascFileSpanInfo: + return GetFileSpanInfo(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded); + default: SetLastError(ERROR_INVALID_PARAMETER); return false; @@ -598,55 +1038,64 @@ bool WINAPI CascGetFileInfo(HANDLE hFile, CASC_FILE_INFO_CLASS InfoClass, void * // WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x000007d0 0x00000397 n/a // -DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh) +bool WINAPI CascGetFileSize64(HANDLE hFile, PULONGLONG PtrFileSize) { TCascFile * hf; - int nError; - - CASCLIB_UNUSED(pdwFileSizeHigh); + DWORD dwErrCode; // Validate the file handle if((hf = TCascFile::IsValid(hFile)) == NULL) { SetLastError(ERROR_INVALID_HANDLE); - return CASC_INVALID_SIZE; + return false; } - // Someone may have provided file content size. - // If yes, do not load the frames, as it's not necessary. - if(hf->ContentSize == CASC_INVALID_SIZE) + // Validate the file pointer + if(PtrFileSize == NULL) { - // Make sure that the file header area is loaded - nError = EnsureFileFramesLoaded(hf); - if(nError != ERROR_SUCCESS) - { - SetLastError(nError); - return CASC_INVALID_SIZE; - } + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } - // The content size should be loaded from the frames - assert(hf->ContentSize != CASC_INVALID_SIZE); + // ENCODING on older storages: Content size is not present in the BUILD file + // For that reason, we need to query the content size from the file frames + dwErrCode = EnsureFileSpanFramesLoaded(hf); + if(dwErrCode != ERROR_SUCCESS) + { + SetLastError(dwErrCode); + return false; } // Give the file size to the caller - if(pdwFileSizeHigh != NULL) - *pdwFileSizeHigh = 0; - return hf->ContentSize; + PtrFileSize[0] = hf->ContentSize; + return true; } -DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHigh, DWORD dwMoveMethod) +DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD PtrFileSizeHigh) +{ + ULONGLONG FileSize = 0; + + // Retrieve the 64-bit file size + if(!CascGetFileSize64(hFile, &FileSize)) + return CASC_INVALID_SIZE; + + // Give the file size to the caller + if(PtrFileSizeHigh != NULL) + PtrFileSizeHigh[0] = (DWORD)(FileSize >> 32); + return (DWORD)(FileSize); +} + +bool WINAPI CascSetFilePointer64(HANDLE hFile, LONGLONG DistanceToMove, PULONGLONG PtrNewPos, DWORD dwMoveMethod) { - TCascFile * hf; ULONGLONG FilePosition; - ULONGLONG MoveOffset; - DWORD dwFilePosHi; + TCascFile * hf; // If the hFile is not a valid file handle, return an error. hf = TCascFile::IsValid(hFile); if(hf == NULL) { SetLastError(ERROR_INVALID_HANDLE); - return CASC_INVALID_POS; + return false; } // Get the relative point where to move from @@ -666,42 +1115,73 @@ DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * plFilePosHig default: SetLastError(ERROR_INVALID_PARAMETER); - return CASC_INVALID_POS; + return false; } - // Now get the move offset. Note that both values form - // a signed 64-bit value (a file pointer can be moved backwards) - if(plFilePosHigh != NULL) - dwFilePosHi = *plFilePosHigh; - else - dwFilePosHi = (lFilePos & 0x80000000) ? 0xFFFFFFFF : 0; - MoveOffset = MAKE_OFFSET64(dwFilePosHi, lFilePos); - // Now calculate the new file pointer - // Do not allow the file pointer to overflow - FilePosition = ((FilePosition + MoveOffset) >= FilePosition) ? (FilePosition + MoveOffset) : 0; + if(DistanceToMove >= 0) + { + // Do not allow the file pointer to overflow 64-bit range + if((FilePosition + DistanceToMove) < FilePosition) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } - // CASC files can't be bigger than 4 GB. - // We don't allow to go past 4 GB - if(FilePosition >> 32) + // Do not allow the file pointer to overflow the file size + if((FilePosition = FilePosition + DistanceToMove) > hf->ContentSize) + FilePosition = hf->ContentSize; + hf->FilePointer = FilePosition; + } + else { - SetLastError(ERROR_INVALID_PARAMETER); - return CASC_INVALID_POS; + // Do not allow the file pointer to underflow 64-bit range + if((FilePosition + DistanceToMove) > FilePosition) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + // Do not allow the file pointer to move to negative values + if((FilePosition = FilePosition + DistanceToMove) < 0) + FilePosition = 0; + hf->FilePointer = FilePosition; } - // Change the file position - hf->FilePointer = (DWORD)FilePosition; + // Give the result size to the caller + if(PtrNewPos != NULL) + PtrNewPos[0] = hf->FilePointer; + return true; +} + +DWORD WINAPI CascSetFilePointer(HANDLE hFile, LONG lFilePos, LONG * PtrFilePosHigh, DWORD dwMoveMethod) +{ + ULONGLONG NewPos = 0; + LONGLONG DistanceToMove; + + // Assemble the 64-bit distance to move + DistanceToMove = (PtrFilePosHigh != NULL) ? MAKE_OFFSET64(PtrFilePosHigh[0], lFilePos) : (LONGLONG)(LONG)lFilePos; + + // Set the file offset + if(!CascSetFilePointer64(hFile, DistanceToMove, &NewPos, dwMoveMethod)) + return CASC_INVALID_POS; - // Return the new file position - if(plFilePosHigh != NULL) - *plFilePosHigh = 0; - return hf->FilePointer; + // Give the result to the caller + if(PtrFilePosHigh != NULL) + PtrFilePosHigh[0] = (LONG)(NewPos >> 32); + return (DWORD)(NewPos); } -bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD pdwBytesRead) +bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDWORD PtrBytesRead) { + ULONGLONG SaveFilePointer; + ULONGLONG StartOffset; + ULONGLONG EndOffset; TCascFile * hf; - int nError = ERROR_SUCCESS; + LPBYTE pbBuffer = (LPBYTE)pvBuffer; + DWORD dwBytesRead1 = 0; // From cache + DWORD dwBytesRead2 = 0; // From file + DWORD dwErrCode; // The buffer must be valid if(pvBuffer == NULL) @@ -717,129 +1197,78 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW return false; } - // If the file frames are not loaded yet, do it now - if(nError == ERROR_SUCCESS) + // If we don't have file frames loaded, we need to do it now. + // Need to do it before file range check, as the file size may be unknown at this point + dwErrCode = EnsureFileSpanFramesLoaded(hf); + if(dwErrCode != ERROR_SUCCESS) { - nError = EnsureFileFramesLoaded(hf); + SetLastError(dwErrCode); + return false; } // If the file position is at or beyond end of file, do nothing - if(nError == ERROR_SUCCESS) + SaveFilePointer = StartOffset = hf->FilePointer; + if(StartOffset >= hf->ContentSize) { - // Check the starting position - if(hf->FilePointer >= hf->ContentSize) - { - *pdwBytesRead = 0; - return true; - } - - // Check the ending position - if((hf->FilePointer + dwBytesToRead) > hf->ContentSize) - { - dwBytesToRead = hf->ContentSize - hf->FilePointer; - } + PtrBytesRead[0] = 0; + return true; } - // Allocate cache buffer for the entire file. This is the fastest approach - // (without reallocations). However, this may consume quite a lot of memory - // (Storage: "2016 - Starcraft II/45364", file: "3d815f40c0413701aa2bd214070d0062" - // needs 0x239a09b3 bytes of memory (~600 MB) - if(nError == ERROR_SUCCESS) + // If the read area goes beyond end of the file, cut the number of bytes to read + EndOffset = StartOffset + dwBytesToRead; + if(EndOffset > hf->ContentSize) { - if(hf->pbFileCache == NULL) - { - // Allocate buffer - hf->pbFileCache = CASC_ALLOC(BYTE, hf->ContentSize); - hf->cbFileCache = hf->ContentSize; - if(hf->pbFileCache == NULL) - nError = ERROR_NOT_ENOUGH_MEMORY; - } + EndOffset = hf->ContentSize; } - // Load all frames that are not loaded yet - if(nError == ERROR_SUCCESS) + // Can we handle the request (at least partially) from the cache? + if((dwBytesRead1 = ReadFile_Cache(hf, pbBuffer, StartOffset, EndOffset)) != 0) { - PCASC_FILE_FRAME pFrame = hf->pFrames; - DWORD StartFrameOffset = 0; - DWORD StartReadOffset = hf->FilePointer; - DWORD EndReadOffset = hf->FilePointer + dwBytesToRead; + // Move pointers + StartOffset = StartOffset + dwBytesRead1; + pbBuffer += dwBytesRead1; - for(DWORD i = 0; (i < hf->FrameCount) && (nError == ERROR_SUCCESS); i++, pFrame++) + // Has the read request been fully satisfied? + if(StartOffset == EndOffset) { - LPBYTE pbDecodedFrame = hf->pbFileCache + StartFrameOffset; - LPBYTE pbEncodedFrame; - DWORD EndFrameOffset = StartFrameOffset + pFrame->ContentSize; - - // Does that frame belong to the range? - if(StartReadOffset < EndFrameOffset && EndReadOffset > StartFrameOffset) - { - // Is the frame already loaded? - if (pFrame->FileOffset == CASC_INVALID_POS) - { - // Allocate space for the encoded frame - pbEncodedFrame = CASC_ALLOC(BYTE, pFrame->EncodedSize); - if (pbEncodedFrame != NULL) - { - // Load the encoded frame data - nError = LoadEncodedFrame(hf->pStream, pFrame, pbEncodedFrame, hf->bVerifyIntegrity); - if (nError == ERROR_SUCCESS) - { - // Decode the frame - nError = ProcessFileFrame(hf->hs, - pbDecodedFrame, - pFrame->ContentSize, - pbEncodedFrame, - pFrame->EncodedSize, - (DWORD)(pFrame - hf->pFrames)); - - // Some people find it handy to extract data from partially encrypted file, - // even at the cost producing files that are corrupt. - // We overcome missing decryption key by zeroing the encrypted portions - if(nError == ERROR_FILE_ENCRYPTED && hf->bOvercomeEncrypted) - { - memset(pbDecodedFrame, 0, pFrame->ContentSize); - nError = ERROR_SUCCESS; - } - - if (nError == ERROR_SUCCESS) - { - // Mark the frame as loaded - pFrame->FileOffset = StartFrameOffset; - } - } - - // Free the frame buffer - CASC_FREE(pbEncodedFrame); - } - else - { - nError = ERROR_NOT_ENOUGH_MEMORY; - } - } - } - - // If the frame start is past the read offset, stop the loop - if ((StartFrameOffset + pFrame->ContentSize) >= EndReadOffset) - break; - StartFrameOffset += pFrame->ContentSize; + if(PtrBytesRead != NULL) + PtrBytesRead[0] = dwBytesRead1; + hf->FilePointer = EndOffset; + return true; } } - // Now all frames have been loaded into the cache; copy the entire block to the output buffer - if(nError == ERROR_SUCCESS) + // Perform the cache-strategy-specific read + switch(hf->CacheStrategy) { - // Copy the entire data - memcpy(pvBuffer, hf->pbFileCache + hf->FilePointer, dwBytesToRead); - hf->FilePointer += dwBytesToRead; + // No caching at all. The entire file will be read directly to the user buffer + // Used for loading internal files, where we need to read the whole file + case CascCacheNothing: + dwBytesRead2 = ReadFile_NonCached(hf, pbBuffer, StartOffset, EndOffset); + break; - // Give the number of bytes read - if(pdwBytesRead != NULL) - *pdwBytesRead = dwBytesToRead; + // Read as many frames as we can. The last loaded frame, if not read entirely, + // will stay in the cache - We expect the next read to continue from that offset. + case CascCacheLastFrame: + dwBytesRead2 = ReadFile_FrameCached(hf, pbBuffer, StartOffset, EndOffset); + break; + } + + // If the second-stage-read failed, we invalidate the entire operation and return 0 bytes read + if(dwBytesRead2 != 0) + { + // Give the result to the caller + if(PtrBytesRead != NULL) + PtrBytesRead[0] = (dwBytesRead1 + dwBytesRead2); + hf->FilePointer = StartOffset + dwBytesRead2; return true; } else { - SetLastError(nError); + // Give the result to the caller + if(PtrBytesRead != NULL) + PtrBytesRead[0] = 0; + hf->FilePointer = SaveFilePointer; return false; } } diff --git a/dep/CascLib/src/CascRootFile_Diablo3.cpp b/dep/CascLib/src/CascRootFile_Diablo3.cpp index bbe369c7646..c29f13f74a9 100644 --- a/dep/CascLib/src/CascRootFile_Diablo3.cpp +++ b/dep/CascLib/src/CascRootFile_Diablo3.cpp @@ -195,33 +195,6 @@ struct TDiabloRoot : public TFileTreeRoot FreeLoadingStuff(); } - void AppendBackslashToTotalPath(PATH_BUFFER & PathBuffer) - { - if(PathBuffer.szPtr < PathBuffer.szEnd) - { - PathBuffer.szPtr[0] = '\\'; - PathBuffer.szPtr++; - } - } - - void AppendPathToTotalPath(PATH_BUFFER & PathBuffer, const char * szFileName, const char * szFileEnd) - { - char * szPathPtr = PathBuffer.szPtr; - size_t nLength = (szFileEnd - szFileName); - - // Append the name - if((szPathPtr + nLength) < PathBuffer.szEnd) - { - memcpy(szPathPtr, szFileName, nLength); - szPathPtr += nLength; - } - - // Append zero terminator - if(szPathPtr < PathBuffer.szEnd) - szPathPtr[0] = 0; - PathBuffer.szPtr = szPathPtr; - } - PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex) { if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL) @@ -404,52 +377,28 @@ struct TDiabloRoot : public TFileTreeRoot } bool CreateAssetFileName( - PATH_BUFFER & PathBuffer, + CASC_PATH<char> & PathBuffer, DWORD FileIndex, DWORD SubIndex) { PDIABLO3_CORE_TOC_ENTRY pTocEntry; PDIABLO3_ASSET_INFO pAssetInfo; - const char * szPackageName = NULL; - const char * szPlainName; - const char * szFormat; - char * szPathEnd = PathBuffer.szEnd; - char * szPathPtr = PathBuffer.szPtr; - size_t nLength = 0; + LPCSTR szPackageName = NULL; + LPCSTR szPlainName; + LPCSTR szFormat; + char szBuffer[MAX_PATH]; // Find and check the entry pTocEntry = pFileIndices + FileIndex; if(pTocEntry->FileIndex == FileIndex) { // Retrieve the asset information + szPlainName = (LPCSTR)(pbCoreTocData + pTocEntry->NameOffset); pAssetInfo = GetAssetInfo(pTocEntry->AssetIndex); - - // Either use the asset info for getting the folder name or supply "Asset##" - if(pAssetInfo != NULL) - { - CascStrCopy(szPathPtr, (szPathEnd - szPathPtr), pAssetInfo->szDirectoryName); - szPathPtr += strlen(szPathPtr); - } - else - { - szPathPtr[0] = 'A'; - szPathPtr[1] = 's'; - szPathPtr[2] = 's'; - szPathPtr[3] = 'e'; - szPathPtr[4] = 't'; - szPathPtr[5] = (char)('0' + (pTocEntry->AssetIndex / 10)); - szPathPtr[6] = (char)('0' + (pTocEntry->AssetIndex % 10)); - szPathPtr += 7; - } - - // Put the backslash - if(szPathPtr < PathBuffer.szEnd) - *szPathPtr++ = '\\'; // Construct the file name, up to the extension. Don't include the '.' - szPlainName = (const char *)(pbCoreTocData + pTocEntry->NameOffset); szFormat = (SubIndex != CASC_INVALID_INDEX) ? "%s\\%04u" : "%s"; - nLength = CascStrPrintf(szPathPtr, (szPathEnd - szPathPtr), szFormat, szPlainName, SubIndex); + CascStrPrintf(szBuffer, _countof(szBuffer), szFormat, szPlainName, SubIndex); // Try to fixup the file extension from the package name. // File extensions are not predictable because for subitems, @@ -466,27 +415,41 @@ struct TDiabloRoot : public TFileTreeRoot // We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible // - if(PackagesMap.IsInitialized() && pAssetInfo != NULL) + if(pAssetInfo != NULL) { // Retrieve the asset name - szPackageName = FindPackageName(pAssetInfo->szDirectoryName, szPathPtr); + szPackageName = FindPackageName(pAssetInfo->szDirectoryName, szBuffer); if(szPackageName != NULL) { - CascStrCopy(PathBuffer.szPtr, (PathBuffer.szEnd - PathBuffer.szPtr), szPackageName); + PathBuffer.AppendString(szPackageName, false); return true; } + + // Append the directory name + PathBuffer.AppendString(pAssetInfo->szDirectoryName, false); + } + else + { + // Append generic name "Asset##" and continue + PathBuffer.AppendString("Asset", false); + PathBuffer.AppendChar((char)('0' + (pTocEntry->AssetIndex / 10))); + PathBuffer.AppendChar((char)('0' + (pTocEntry->AssetIndex % 10))); } - // Use the extension from the AssetInfo, if we have any + // Append the content of the buffer + PathBuffer.AppendString(szBuffer, true); + + // If we have an extension, use it. Otherwise, supply "a##" if(pAssetInfo != NULL && pAssetInfo->szExtension != NULL) { - szPathPtr[nLength++] = '.'; - CascStrCopy(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), pAssetInfo->szExtension); - return true; + PathBuffer.AppendChar('.'); + PathBuffer.AppendString(pAssetInfo->szExtension, false); + } + else + { + CascStrPrintf(szBuffer, _countof(szBuffer), ".a%02u", pTocEntry->AssetIndex); + PathBuffer.AppendString(szBuffer, false); } - - // Otherwise, supply "a##" - CascStrPrintf(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), ".a%02u", pTocEntry->AssetIndex); return true; } @@ -494,13 +457,14 @@ struct TDiabloRoot : public TFileTreeRoot } // Parse the asset entries - int ParseAssetEntries( + DWORD ParseAssetEntries( TCascStorage * hs, DIABLO3_DIRECTORY & Directory, - PATH_BUFFER & PathBuffer) + CASC_PATH<char> & PathBuffer) { PDIABLO3_ASSET_ENTRY pEntry = (PDIABLO3_ASSET_ENTRY)Directory.pbAssetEntries; PCASC_CKEY_ENTRY pCKeyEntry; + size_t nSavePos = PathBuffer.Save(); DWORD dwEntries = Directory.dwAssetEntries; // Do nothing if there is no entries @@ -516,8 +480,11 @@ struct TDiabloRoot : public TFileTreeRoot if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, CASC_INVALID_INDEX)) { // Insert the entry to the file tree - FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + FileTree.InsertByName(pCKeyEntry, PathBuffer); } + + // Restore the path buffer position + PathBuffer.Restore(nSavePos); } } } @@ -525,13 +492,14 @@ struct TDiabloRoot : public TFileTreeRoot return ERROR_SUCCESS; } - int ParseAssetAndIdxEntries( + DWORD ParseAssetAndIdxEntries( TCascStorage * hs, DIABLO3_DIRECTORY & Directory, - PATH_BUFFER & PathBuffer) + CASC_PATH<char> & PathBuffer) { PDIABLO3_ASSETIDX_ENTRY pEntry = (PDIABLO3_ASSETIDX_ENTRY)Directory.pbAssetIdxEntries; PCASC_CKEY_ENTRY pCKeyEntry; + size_t nSavePos = PathBuffer.Save(); DWORD dwEntries = Directory.dwAssetIdxEntries; // Do nothing if there is no entries @@ -548,8 +516,11 @@ struct TDiabloRoot : public TFileTreeRoot { // Insert the entry to the file tree // fprintf(fp, "%08u %04u %s\n", pEntry->FileIndex, pEntry->SubIndex, PathBuffer.szBegin); - FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + FileTree.InsertByName(pCKeyEntry, PathBuffer); } + + // Restore the path buffer position + PathBuffer.Restore(nSavePos); } } } @@ -558,16 +529,16 @@ struct TDiabloRoot : public TFileTreeRoot } // Parse the named entries of all folders - int ParseDirectory_Phase1( + DWORD ParseDirectory_Phase1( TCascStorage * hs, DIABLO3_DIRECTORY & Directory, - PATH_BUFFER & PathBuffer, + CASC_PATH<char> & PathBuffer, bool bIsRootDirectory) { DIABLO3_NAMED_ENTRY NamedEntry; - char * szSavePtr = PathBuffer.szPtr; size_t nFolderIndex = 0; - int nError = ERROR_SUCCESS; + size_t nSavePos = PathBuffer.Save(); + DWORD dwErrCode = ERROR_SUCCESS; // Do nothing if there is no named headers if(Directory.pbNamedEntries && Directory.dwNamedEntries) @@ -587,33 +558,32 @@ struct TDiabloRoot : public TFileTreeRoot return ERROR_BAD_FORMAT; // Append the path fragment to the total path - AppendPathToTotalPath(PathBuffer, NamedEntry.szFileName, NamedEntry.szFileEnd); + PathBuffer.AppendStringN(NamedEntry.szFileName, (NamedEntry.szFileEnd - NamedEntry.szFileName), true); // Check whether the file exists in the storage pCKeyEntry = FindCKeyEntry_CKey(hs, NamedEntry.pCKey->Value); if(pCKeyEntry != NULL) { // Create file node belonging to this folder - pFileNode = FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + pFileNode = FileTree.InsertByName(pCKeyEntry, PathBuffer); dwNodeIndex = (DWORD)FileTree.IndexOf(pFileNode); // If we are parsing root folder, we also need to load the data of the sub-folder file if(bIsRootDirectory) { // Mark the node as directory - AppendBackslashToTotalPath(PathBuffer); pCKeyEntry->Flags |= CASC_CE_FOLDER_ENTRY; pFileNode->Flags |= CFN_FLAG_FOLDER; // Load the sub-directory file - nError = LoadDirectoryFile(hs, RootFolders[nFolderIndex], pCKeyEntry); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = LoadDirectoryFile(hs, RootFolders[nFolderIndex], pCKeyEntry); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Parse the sub-directory file - nError = ParseDirectory_Phase1(hs, RootFolders[nFolderIndex], PathBuffer, false); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = ParseDirectory_Phase1(hs, RootFolders[nFolderIndex], PathBuffer, false); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Also save the item pointer and increment the folder index RootFolders[nFolderIndex].dwNodeIndex = dwNodeIndex; @@ -621,20 +591,19 @@ struct TDiabloRoot : public TFileTreeRoot } // Restore the path pointer - PathBuffer.szPtr = szSavePtr; - szSavePtr[0] = 0; + PathBuffer.Restore(nSavePos); } } } - return nError; + return dwErrCode; } // Parse the nameless entries of all folders int ParseDirectory_Phase2(TCascStorage * hs) { - PATH_BUFFER PathBuffer; - char szPathBuffer[MAX_PATH]; + CASC_PATH<char> PathBuffer; + char szBuffer[MAX_PATH]; // Parse each root subdirectory for(size_t i = 0; i < DIABLO3_MAX_ROOT_FOLDERS; i++) @@ -642,16 +611,11 @@ struct TDiabloRoot : public TFileTreeRoot // Is this root folder loaded? if(RootFolders[i].pbDirectoryData != NULL) { - PathBuffer.szBegin = szPathBuffer; - PathBuffer.szPtr = szPathBuffer; - PathBuffer.szEnd = szPathBuffer + MAX_PATH - 1; - szPathBuffer[0] = 0; - // Retrieve the parent name if(RootFolders[i].dwNodeIndex != 0) { - FileTree.PathAt(szPathBuffer, MAX_PATH, RootFolders[i].dwNodeIndex); - PathBuffer.szPtr = PathBuffer.szBegin + strlen(szPathBuffer); + FileTree.PathAt(szBuffer, _countof(szBuffer), RootFolders[i].dwNodeIndex); + PathBuffer.SetPathRoot(szBuffer); } // Array of DIABLO3_ASSET_ENTRY entries. @@ -671,12 +635,12 @@ struct TDiabloRoot : public TFileTreeRoot // Creates an array of DIABLO3_CORE_TOC_ENTRY entries indexed by FileIndex // Used as lookup table when we have FileIndex and need Asset+PlainName - int CreateMapOfFileIndices(TCascStorage * hs, const char * szFileName) + DWORD CreateMapOfFileIndices(TCascStorage * hs, const char * szFileName) { PDIABLO3_CORE_TOC_HEADER pTocHeader = NULL; LPBYTE pbCoreTocPtr = pbCoreTocFile; DWORD dwMaxFileIndex = 0; - int nError = ERROR_CAN_NOT_COMPLETE; + DWORD dwErrCode = ERROR_CAN_NOT_COMPLETE; // Load the entire file to memory pbCoreTocFile = pbCoreTocPtr = LoadFileToMemory(hs, szFileName, &cbCoreTocFile); @@ -693,7 +657,7 @@ struct TDiabloRoot : public TFileTreeRoot return ERROR_SUCCESS; // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs - pFileIndices = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwMaxFileIndex + 1); + pFileIndices = CASC_ALLOC<DIABLO3_CORE_TOC_ENTRY>(dwMaxFileIndex + 1); if(pFileIndices != NULL) { // Initialize all entries to invalid @@ -720,10 +684,10 @@ struct TDiabloRoot : public TFileTreeRoot // Save the file to the root handler pbCoreTocData = pbCoreTocPtr; nFileIndices = dwMaxFileIndex; - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } } - return nError; + return dwErrCode; } // Packages.dat contains a list of full file names (without locale prefix). @@ -740,19 +704,7 @@ struct TDiabloRoot : public TFileTreeRoot { LPBYTE pbPackagesPtr = pbPackagesDat; LPBYTE pbPackagesEnd = pbPackagesDat + cbPackagesDat; -/* - LPBYTE pbPackagesPtr = pbPackagesDat + 8; - FILE * fp = fopen("E:\\Packages.dat", "wt"); - if(fp != NULL) - { - while(pbPackagesPtr < pbPackagesEnd) - { - fprintf(fp, "%s\n", pbPackagesPtr); - pbPackagesPtr = pbPackagesPtr + strlen((char *)pbPackagesPtr) + 1; - } - fclose(fp); - } -*/ + // Get the header. There is just Signature + NumberOfNames if((pbPackagesPtr = CaptureInteger32(pbPackagesPtr, pbPackagesEnd, &Signature)) == NULL) return ERROR_BAD_FORMAT; @@ -783,27 +735,20 @@ struct TDiabloRoot : public TFileTreeRoot return ERROR_SUCCESS; } - int Load(TCascStorage * hs, DIABLO3_DIRECTORY & RootDirectory) + DWORD Load(TCascStorage * hs, DIABLO3_DIRECTORY & RootDirectory) { - PATH_BUFFER PathBuffer; - char szPathBuffer[MAX_PATH]; - int nError; - - // Initialize path buffer and go parse the directory - PathBuffer.szBegin = szPathBuffer; - PathBuffer.szPtr = szPathBuffer; - PathBuffer.szEnd = szPathBuffer + MAX_PATH; - szPathBuffer[0] = 0; + CASC_PATH<char> PathBuffer; + DWORD dwErrCode; // Always parse the named entries first. They always point to a file. // These are entries with arbitrary names, and they do not belong to an asset - nError = ParseDirectory_Phase1(hs, RootDirectory, PathBuffer, true); - if(nError == ERROR_SUCCESS) + dwErrCode = ParseDirectory_Phase1(hs, RootDirectory, PathBuffer, true); + if(dwErrCode == ERROR_SUCCESS) { // The asset entries in the ROOT file don't contain file names, but indices. // To convert a file index to a file name, we need to load and parse the "Base\\CoreTOC.dat" file. - nError = CreateMapOfFileIndices(hs, "Base\\CoreTOC.dat"); - if(nError == ERROR_SUCCESS) + dwErrCode = CreateMapOfFileIndices(hs, "Base\\CoreTOC.dat"); + if(dwErrCode == ERROR_SUCCESS) { // The file "Base\Data_D3\PC\Misc\Packages.dat" contains the file names // (without level-0 and level-1 directory). @@ -818,7 +763,7 @@ struct TDiabloRoot : public TFileTreeRoot FreeLoadingStuff(); } - return nError; + return dwErrCode; } void FreeLoadingStuff() @@ -860,11 +805,11 @@ struct TDiabloRoot : public TFileTreeRoot //----------------------------------------------------------------------------- // Public functions -int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +DWORD RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { TDiabloRoot * pRootHandler = NULL; DIABLO3_DIRECTORY RootDirectory; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Verify the header of the ROOT file if(TDiabloRoot::CaptureDirectoryData(RootDirectory, pbRootFile, cbRootFile) != NULL) @@ -874,8 +819,8 @@ int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRoot if(pRootHandler != NULL) { // Load the root directory. If load failed, we free the object - nError = pRootHandler->Load(hs, RootDirectory); - if(nError != ERROR_SUCCESS) + dwErrCode = pRootHandler->Load(hs, RootDirectory); + if(dwErrCode != ERROR_SUCCESS) { delete pRootHandler; pRootHandler = NULL; @@ -885,5 +830,5 @@ int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRoot // Assign the root directory (or NULL) and return error hs->pRootHandler = pRootHandler; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascRootFile_Install.cpp b/dep/CascLib/src/CascRootFile_Install.cpp index a0b00f73168..86d621e2e47 100644 --- a/dep/CascLib/src/CascRootFile_Install.cpp +++ b/dep/CascLib/src/CascRootFile_Install.cpp @@ -25,30 +25,7 @@ struct TRootHandler_Install : public TFileTreeRoot dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY); } - static int CaptureInstallHeader(CASC_INSTALL_HEADER & InHeader, LPBYTE pbFileData, size_t cbFileData) - { - PFILE_INSTALL_HEADER pFileHeader = (PFILE_INSTALL_HEADER)pbFileData; - - // Check the signature ('DL') and version - if (cbFileData < sizeof(FILE_INSTALL_HEADER) || pFileHeader->Magic != FILE_MAGIC_INSTALL || pFileHeader->Version != 1) - return ERROR_BAD_FORMAT; - - // Note that we don't support CKey sizes greater than 0x10 in the INSTALL file - if (pFileHeader->EKeyLength > MD5_HASH_SIZE) - return ERROR_BAD_FORMAT; - - // Capture the header version 1 - memset(&InHeader, 0, sizeof(CASC_INSTALL_HEADER)); - InHeader.Magic = pFileHeader->Magic; - InHeader.Version = pFileHeader->Version; - InHeader.EKeyLength = pFileHeader->EKeyLength; - InHeader.TagCount = ConvertBytesToInteger_2(pFileHeader->TagCount); - InHeader.EntryCount = ConvertBytesToInteger_4(pFileHeader->EntryCount); - InHeader.HeaderLength = sizeof(FILE_INSTALL_HEADER); - return ERROR_SUCCESS; - } - - int Load(TCascStorage * hs, CASC_INSTALL_HEADER InHeader, LPBYTE pbInstallFile, LPBYTE pbInstallEnd) + DWORD Load(TCascStorage * hs, CASC_INSTALL_HEADER InHeader, LPBYTE pbInstallFile, LPBYTE pbInstallEnd) { PCASC_CKEY_ENTRY pCKeyEntry; const char * szString; @@ -90,32 +67,48 @@ struct TRootHandler_Install : public TFileTreeRoot //----------------------------------------------------------------------------- // Public functions -// -// Starcraft ROOT file is a text file with the following format: -// HD2/portraits/NBluCrit/NLCFID01.webm|c2795b120592355d45eba9cdc37f691e -// locales/enUS/Assets/campaign/EXPZerg/Zerg08/staredit/wav/zovtra01.ogg|316b0274bf2dabaa8db60c3ff1270c85 -// locales/zhCN/Assets/sound/terran/ghost/tghdth01.wav|6637ed776bd22089e083b8b0b2c0374c -// +DWORD CaptureInstallHeader(CASC_INSTALL_HEADER & InHeader, LPBYTE pbFileData, size_t cbFileData) +{ + PFILE_INSTALL_HEADER pFileHeader = (PFILE_INSTALL_HEADER)pbFileData; + + // Check the signature ('DL') and version + if (cbFileData < sizeof(FILE_INSTALL_HEADER) || pFileHeader->Magic != FILE_MAGIC_INSTALL || pFileHeader->Version != 1) + return ERROR_BAD_FORMAT; + + // Note that we don't support CKey sizes greater than 0x10 in the INSTALL file + if (pFileHeader->EKeyLength > MD5_HASH_SIZE) + return ERROR_BAD_FORMAT; + + // Capture the header version 1 + memset(&InHeader, 0, sizeof(CASC_INSTALL_HEADER)); + InHeader.Magic = pFileHeader->Magic; + InHeader.Version = pFileHeader->Version; + InHeader.EKeyLength = pFileHeader->EKeyLength; + InHeader.TagCount = ConvertBytesToInteger_2(pFileHeader->TagCount); + InHeader.EntryCount = ConvertBytesToInteger_4(pFileHeader->EntryCount); + InHeader.HeaderLength = sizeof(FILE_INSTALL_HEADER); + return ERROR_SUCCESS; +} -int RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbInstallFile, DWORD cbInstallFile) +DWORD RootHandler_CreateInstall(TCascStorage * hs, LPBYTE pbInstallFile, DWORD cbInstallFile) { CASC_INSTALL_HEADER InHeader; TRootHandler_Install * pRootHandler = NULL; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Capture the header of the DOWNLOAD file - nError = TRootHandler_Install::CaptureInstallHeader(InHeader, pbInstallFile, cbInstallFile); - if (nError == ERROR_SUCCESS) + dwErrCode = CaptureInstallHeader(InHeader, pbInstallFile, cbInstallFile); + if (dwErrCode == ERROR_SUCCESS) { // Allocate the root handler object pRootHandler = new TRootHandler_Install(); if (pRootHandler != NULL) { // Parse the entire install manifest - nError = pRootHandler->Load(hs, InHeader, pbInstallFile, pbInstallFile + cbInstallFile); + dwErrCode = pRootHandler->Load(hs, InHeader, pbInstallFile, pbInstallFile + cbInstallFile); hs->pRootHandler = pRootHandler; } } - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascRootFile_MNDX.cpp b/dep/CascLib/src/CascRootFile_MNDX.cpp index 9dca8d30eb4..c049d813967 100644 --- a/dep/CascLib/src/CascRootFile_MNDX.cpp +++ b/dep/CascLib/src/CascRootFile_MNDX.cpp @@ -368,14 +368,14 @@ class TByteStream // HOTS: 1957160 <DWORD> template <typename T> - int GetValue(T & Value) + DWORD GetValue(T & Value) { T * Pointer; - int nError; + DWORD dwErrCode; - nError = GetBytes(sizeof(T), (LPBYTE *)(&Pointer)); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = GetBytes(sizeof(T), (LPBYTE *)(&Pointer)); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; Value = Pointer[0]; return ERROR_SUCCESS; @@ -383,15 +383,15 @@ class TByteStream // Retrieves the item count in the array template <typename T> - int GetArrayItemCount(DWORD & ArraySize, DWORD & ItemCount) + DWORD GetArrayItemCount(DWORD & ArraySize, DWORD & ItemCount) { ULONGLONG ByteCount; - int nError; + DWORD dwErrCode; // The first 8 bytes is the byte size of the array - nError = GetValue<ULONGLONG>(ByteCount); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = GetValue<ULONGLONG>(ByteCount); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Extract the number of bytes if(ByteCount > 0xFFFFFFFF || (ByteCount % sizeof(T)) != 0) @@ -408,9 +408,9 @@ class TByteStream // HOTS: 1957230: <BYTE> // HOTS: 1957280: <HASH_ENTRY> template <typename T> - int GetArray(T ** Pointer, size_t ItemCount) + DWORD GetArray(T ** Pointer, size_t ItemCount) { - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Verify parameters if(Pointer == NULL && ItemCount != 0) @@ -421,15 +421,15 @@ class TByteStream // Allocate bytes for the array if (Pointer != NULL) { - Pointer[0] = CASC_ALLOC(T, ItemCount); + Pointer[0] = CASC_ALLOC<T>(ItemCount); if (Pointer[0] == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Get the pointer to the array - nError = CopyBytes(Pointer[0], sizeof(T) * ItemCount); + dwErrCode = CopyBytes(Pointer[0], sizeof(T) * ItemCount); } - return nError; + return dwErrCode; } LPBYTE pbByteData; @@ -488,7 +488,7 @@ class TGenericArray T * NewArray; // Allocate new data buffer - NewArray = CASC_ALLOC(T, NewMaxItemCount); + NewArray = CASC_ALLOC<T>(NewMaxItemCount); if(NewArray != NULL) { // Copy the old items to the buffer @@ -555,24 +555,24 @@ class TGenericArray // HOTS: 1957700 <HASH_ENTRY> // HOTS: 195A220 <char> // HOTS: 1958580 <TBitStream, DWORD> - int LoadFromStream(TByteStream & InStream) + DWORD LoadFromStream(TByteStream & InStream) { DWORD NumberOfBytes; - int nError; + DWORD dwErrCode; // Get and verify the number of items - nError = InStream.GetArrayItemCount<T>(NumberOfBytes, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetArrayItemCount<T>(NumberOfBytes, ItemCount); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Get the pointer to the array - nError = InStream.GetArray<T>(&ItemArray, ItemCount); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetArray<T>(&ItemArray, ItemCount); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.SkipBytes((0 - (DWORD)NumberOfBytes) & 0x07); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; return SetArrayValid(); } @@ -619,28 +619,28 @@ class TBitEntryArray : public TGenericArray<DWORD> return dwResult & EntryBitMask; } - int LoadBitsFromStream(TByteStream & InStream) + DWORD LoadBitsFromStream(TByteStream & InStream) { ULONGLONG Value64 = 0; - int nError; + DWORD dwErrCode; - nError = LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = InStream.GetValue<DWORD>(BitsPerEntry); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetValue<DWORD>(BitsPerEntry); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; if(BitsPerEntry > 0x20) return ERROR_BAD_FORMAT; - nError = InStream.GetValue<DWORD>(EntryBitMask); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetValue<DWORD>(EntryBitMask); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = InStream.GetValue<ULONGLONG>(Value64); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetValue<ULONGLONG>(Value64); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; if(Value64 > 0xFFFFFFFF) return ERROR_BAD_FORMAT; TotalEntries = (DWORD)Value64; @@ -687,39 +687,39 @@ class TSparseArray } // HOTS: 1958630 - int LoadFromStream(TByteStream & InStream) + DWORD LoadFromStream(TByteStream & InStream) { DWORD total_count = 0; DWORD valid_count = 0; - int nError; - - nError = ItemBits.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; - - nError = InStream.GetValue<DWORD>(total_count); - if(nError != ERROR_SUCCESS) - return nError; - nError = InStream.GetValue<DWORD>(valid_count); - if(nError != ERROR_SUCCESS) - return nError; + DWORD dwErrCode; + + dwErrCode = ItemBits.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + + dwErrCode = InStream.GetValue<DWORD>(total_count); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + dwErrCode = InStream.GetValue<DWORD>(valid_count); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; if(valid_count > total_count) return ERROR_FILE_CORRUPT; TotalItemCount = total_count; ValidItemCount = valid_count; - nError = BaseVals.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = BaseVals.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = IndexToItem0.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = IndexToItem0.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = IndexToItem1.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = IndexToItem1.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; return ERROR_SUCCESS; } @@ -1521,13 +1521,13 @@ class TPathFragmentTable } // HOTS: 0195A820 - int LoadFromStream(TByteStream & InStream) + DWORD LoadFromStream(TByteStream & InStream) { - int nError; + DWORD dwErrCode; - nError = PathFragments.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = PathFragments.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; return PathMarks.LoadFromStream(InStream); } @@ -1606,10 +1606,10 @@ class TStruct10 } // HOTS: 19572E0 - int sub_19572E0(DWORD dwBitMask) + DWORD sub_19572E0(DWORD dwBitMask) { DWORD dwSubMask; - int nError; + DWORD dwErrCode; if(dwBitMask & 0xFFF00000) return ERROR_INVALID_PARAMETER; @@ -1618,9 +1618,9 @@ class TStruct10 if(dwSubMask) field_0 = dwSubMask; - nError = sub_1956FD0(dwBitMask); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = sub_1956FD0(dwBitMask); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; dwSubMask = dwBitMask & 0xF000; if(dwSubMask == 0 || dwSubMask == 0x1000) @@ -1718,32 +1718,32 @@ class TFileNameDatabase } // HOTS: 1956DA0 - int Load(LPBYTE pbMarData, size_t cbMarData) + DWORD Load(LPBYTE pbMarData, size_t cbMarData) { TByteStream ByteStream; DWORD dwSignature; - int nError; + DWORD dwErrCode; if(pbMarData == NULL && cbMarData != 0) return ERROR_INVALID_PARAMETER; - nError = ByteStream.SetByteBuffer(pbMarData, cbMarData); - if(nError == ERROR_SUCCESS) + dwErrCode = ByteStream.SetByteBuffer(pbMarData, cbMarData); + if(dwErrCode == ERROR_SUCCESS) { // Get pointer to MAR signature - nError = ByteStream.GetValue<DWORD>(dwSignature); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = ByteStream.GetValue<DWORD>(dwSignature); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Verify the signature if(dwSignature != MNDX_MAR_SIGNATURE) return ERROR_BAD_FORMAT; // HOTS: 1956E11 - nError = LoadFromStream(ByteStream); + dwErrCode = LoadFromStream(ByteStream); } - return nError; + return dwErrCode; } // HOTS: 19584B0 @@ -2398,36 +2398,36 @@ class TFileNameDatabase } // HOTS: 1959790 - int LoadFromStream(TByteStream & InStream) + DWORD LoadFromStream(TByteStream & InStream) { DWORD dwBitMask; - int nError; + DWORD dwErrCode; - nError = CollisionTable.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = CollisionTable.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = FileNameIndexes.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = FileNameIndexes.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = CollisionHiBitsIndexes.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = CollisionHiBitsIndexes.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // HOTS: 019597CD - nError = LoBitsTable.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = LoBitsTable.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = HiBitsTable.LoadBitsFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = HiBitsTable.LoadBitsFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // HOTS: 019597F5 - nError = PathFragmentTable.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = PathFragmentTable.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // HOTS: 0195980A if(CollisionHiBitsIndexes.ValidItemCount != 0 && PathFragmentTable.PathFragments.ItemCount == 0) @@ -2438,29 +2438,29 @@ class TFileNameDatabase if (pNewDB == NULL) return ERROR_NOT_ENOUGH_MEMORY; - nError = SetChildDatabase(pNewDB); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = SetChildDatabase(pNewDB); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = pChildDB->LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = pChildDB->LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; } // HOTS: 0195986B - nError = HashTable.LoadFromStream(InStream); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = HashTable.LoadFromStream(InStream); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; HashTableMask = HashTable.ItemCount - 1; - nError = InStream.GetValue<DWORD>(field_214); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetValue<DWORD>(field_214); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; - nError = InStream.GetValue<DWORD>(dwBitMask); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = InStream.GetValue<DWORD>(dwBitMask); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; return Struct10.sub_1957800(dwBitMask); } @@ -2508,7 +2508,7 @@ class TMndxMarFile int LoadRootData(FILE_MAR_INFO & MarInfo, LPBYTE pbRootFile, LPBYTE pbRootEnd) { // Allocate the MAR data - pbMarData = CASC_ALLOC(BYTE, MarInfo.MarDataSize); + pbMarData = CASC_ALLOC<BYTE>(MarInfo.MarDataSize); cbMarData = MarInfo.MarDataSize; if(pbMarData == NULL) return ERROR_NOT_ENOUGH_MEMORY; @@ -2526,29 +2526,29 @@ class TMndxMarFile } // HOTS: 1956C60 - int SearchFile(TMndxSearch * pSearch) + DWORD SearchFile(TMndxSearch * pSearch) { - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; if(pDatabase == NULL) return ERROR_INVALID_PARAMETER; if(!pDatabase->FindFileInDatabase(pSearch)) - nError = ERROR_FILE_NOT_FOUND; + dwErrCode = ERROR_FILE_NOT_FOUND; - return nError; + return dwErrCode; } // HOTS: 1956CE0 - int DoSearch(TMndxSearch * pSearch, bool * pbFindResult) + DWORD DoSearch(TMndxSearch * pSearch, bool * pbFindResult) { - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; if(pDatabase == NULL) return ERROR_INVALID_PARAMETER; *pbFindResult = pDatabase->DoSearch(pSearch); - return nError; + return dwErrCode; } // HOTS: 1956D20 @@ -2638,23 +2638,23 @@ struct TMndxHandler return pbRootPtr + sizeof(FILE_MNDX_HEADER); } - int LoadPackageNames() + DWORD LoadPackageNames() { TMndxMarFile * pMarFile = MndxInfo.MarFiles[MAR_PACKAGE_NAMES]; TMndxSearch Search; PMNDX_PACKAGE pPackage; size_t nPackageCount = 0x40; bool bFindResult = false; - int nError; + DWORD dwErrCode; // Prepare the file name search in the top level directory Search.SetSearchMask("", 0); // Allocate initial name list structure pMarFile->GetFileNameCount(&nPackageCount); - nError = Packages.Create<MNDX_PACKAGE>(nPackageCount); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = Packages.Create<MNDX_PACKAGE>(nPackageCount); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Reset the package array Packages.Reset(); @@ -2671,7 +2671,7 @@ struct TMndxHandler assert(pPackage->szFileName == NULL); // Allocate space for the file name - pPackage->szFileName = CASC_ALLOC(char, Search.cchFoundPath + 1); + pPackage->szFileName = CASC_ALLOC<char>(Search.cchFoundPath + 1); if (pPackage->szFileName == NULL) return ERROR_NOT_ENOUGH_MEMORY; @@ -2687,13 +2687,13 @@ struct TMndxHandler return ERROR_SUCCESS; } - int Load(const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd) + DWORD Load(const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd) { TMndxMarFile * pMarFile; FILE_MAR_INFO MarInfo; size_t nFilePointer = 0; DWORD i; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Copy the header into the MNDX info MndxInfo.HeaderVersion = MndxHeader.HeaderVersion; @@ -2728,13 +2728,13 @@ struct TMndxHandler pMarFile = new TMndxMarFile(); if(pMarFile == NULL) { - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; break; } // Create the database from the MAR data - nError = pMarFile->LoadRootData(MarInfo, pbRootFile, pbRootEnd); - if(nError != ERROR_SUCCESS) + dwErrCode = pMarFile->LoadRootData(MarInfo, pbRootFile, pbRootEnd); + if(dwErrCode != ERROR_SUCCESS) break; // Assign the MAR file to the MNDX info structure @@ -2743,23 +2743,23 @@ struct TMndxHandler // All three MAR files must be loaded // HOTS: 00E9503B - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { if(MndxInfo.MarFiles[MAR_PACKAGE_NAMES] == NULL || MndxInfo.MarFiles[MAR_STRIPPED_NAMES] == NULL || MndxInfo.MarFiles[MAR_FULL_NAMES] == NULL) - nError = ERROR_BAD_FORMAT; + dwErrCode = ERROR_BAD_FORMAT; if(MndxInfo.CKeyEntrySize != sizeof(MNDX_CKEY_ENTRY)) - nError = ERROR_BAD_FORMAT; + dwErrCode = ERROR_BAD_FORMAT; } // Load the array of Ckey entries. All present files are in the array, // the same names (differentiated by package ID) are groupped together - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { size_t CKeyEntriesSize; size_t FileNameCount = 0; pMarFile = MndxInfo.MarFiles[MAR_STRIPPED_NAMES]; - nError = ERROR_FILE_CORRUPT; + dwErrCode = ERROR_FILE_CORRUPT; // Capture the array of CKey entries if(pMarFile->GetFileNameCount(&FileNameCount) == ERROR_SUCCESS && FileNameCount == MndxInfo.FileNameCount) @@ -2768,16 +2768,16 @@ struct TMndxHandler if ((pbRootFile + MndxInfo.CKeyEntriesOffset + CKeyEntriesSize) <= pbRootEnd) { pCKeyEntries = (PMNDX_CKEY_ENTRY)(pbRootFile + MndxInfo.CKeyEntriesOffset); - nError = ERROR_SUCCESS; + dwErrCode = ERROR_SUCCESS; } } } // Pick the CKey entries that are the first with a given name - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { assert(MndxInfo.FileNameCount <= MndxInfo.CKeyEntriesCount); - FileNameIndexToCKeyIndex = CASC_ALLOC(PMNDX_CKEY_ENTRY, MndxInfo.FileNameCount + 1); + FileNameIndexToCKeyIndex = CASC_ALLOC<PMNDX_CKEY_ENTRY>(MndxInfo.FileNameCount + 1); if(FileNameIndexToCKeyIndex != NULL) { PMNDX_CKEY_ENTRY pRootEntry = pCKeyEntries; @@ -2800,22 +2800,22 @@ struct TMndxHandler // Verify the final number of file names if ((nFileNameIndex - 1) != MndxInfo.FileNameCount) - nError = ERROR_BAD_FORMAT; + dwErrCode = ERROR_BAD_FORMAT; } else - nError = ERROR_NOT_ENOUGH_MEMORY; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; } // Load the package names from the 0-th MAR file - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { - nError = LoadPackageNames(); + dwErrCode = LoadPackageNames(); } - return nError; + return dwErrCode; } - int LoadFileNames(TCascStorage * hs, CASC_FILE_TREE & FileTree) + DWORD LoadFileNames(TCascStorage * hs, CASC_FILE_TREE & FileTree) { PCASC_CKEY_ENTRY pCKeyEntry; PMNDX_CKEY_ENTRY pRootEntry; @@ -2825,13 +2825,13 @@ struct TMndxHandler TMndxSearch Search; char szFileName[MAX_PATH]; bool bFindResult = false; - int nError; + DWORD dwErrCode; // Setup the search mask Search.SetSearchMask("", 0); // Keep searching ad long as we found something - while ((nError = pMarFile->DoSearch(&Search, &bFindResult)) == ERROR_SUCCESS && bFindResult) + while ((dwErrCode = pMarFile->DoSearch(&Search, &bFindResult)) == ERROR_SUCCESS && bFindResult) { // Sanity check assert(Search.cchFoundPath < MAX_PATH); @@ -2874,7 +2874,7 @@ struct TMndxHandler } } - return nError; + return dwErrCode; } // @@ -2931,32 +2931,32 @@ struct TRootHandler_MNDX : public TFileTreeRoot dwFeatures |= CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY; } - int Load(TCascStorage * hs, const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd) + DWORD Load(TCascStorage * hs, const FILE_MNDX_HEADER & MndxHeader, LPBYTE pbRootFile, LPBYTE pbRootEnd) { TMndxHandler Handler; - int nError; + DWORD dwErrCode; // Load and parse the entire MNDX structure - nError = Handler.Load(MndxHeader, pbRootFile, pbRootEnd); - if (nError == ERROR_SUCCESS) + dwErrCode = Handler.Load(MndxHeader, pbRootFile, pbRootEnd); + if (dwErrCode == ERROR_SUCCESS) { // Search all file names and insert them into the file tree - nError = Handler.LoadFileNames(hs, FileTree); + dwErrCode = Handler.LoadFileNames(hs, FileTree); } - return nError; + return dwErrCode; } }; //----------------------------------------------------------------------------- // Public functions - MNDX info -int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +DWORD RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { TRootHandler_MNDX * pRootHandler = NULL; FILE_MNDX_HEADER MndxHeader; LPBYTE pbRootEnd = pbRootFile + cbRootFile; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Verify the header of the ROOT file if(TMndxHandler::CaptureRootHeader(MndxHeader, pbRootFile, pbRootEnd) != NULL) @@ -2966,8 +2966,8 @@ int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFil if(pRootHandler != NULL) { // Load the root directory. If load failed, we free the object - nError = pRootHandler->Load(hs, MndxHeader, pbRootFile, pbRootEnd); - if(nError != ERROR_SUCCESS) + dwErrCode = pRootHandler->Load(hs, MndxHeader, pbRootFile, pbRootEnd); + if(dwErrCode != ERROR_SUCCESS) { delete pRootHandler; pRootHandler = NULL; @@ -2977,5 +2977,5 @@ int RootHandler_CreateMNDX(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFil // Assign the root directory (or NULL) and return error hs->pRootHandler = pRootHandler; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascRootFile_OW.cpp b/dep/CascLib/src/CascRootFile_OW.cpp index 24f5af81b34..251a67284cb 100644 --- a/dep/CascLib/src/CascRootFile_OW.cpp +++ b/dep/CascLib/src/CascRootFile_OW.cpp @@ -264,7 +264,7 @@ struct TApmFile LPBYTE CaptureArrayOfEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd) { // Allocate array of entries - pApmEntries = CASC_ALLOC(APM_ENTRY, EntryCount); + pApmEntries = CASC_ALLOC<APM_ENTRY>(EntryCount); if(pApmEntries != NULL) { // The newest format @@ -310,12 +310,9 @@ struct TApmFile LPBYTE CapturePackageEntries(LPBYTE pbArrayOfEntries, LPBYTE pbApmEnd) { // Allocate array of entries - pApmPackages = CASC_ALLOC(APM_PACKAGE_ENTRY, PackageCount); + pApmPackages = CASC_ALLOC_ZERO<APM_PACKAGE_ENTRY>(PackageCount); if(pApmPackages != NULL) { - // Zero the entire array - memset(pApmPackages, 0, PackageCount * sizeof(APM_PACKAGE_ENTRY)); - // The newest format if(BuildNumber > 45104 && BuildNumber != 45214) { @@ -426,12 +423,12 @@ struct TRootHandler_OW : public TFileTreeRoot return false; } - int LoadApmFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName) + DWORD LoadApmFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName) { TApmFile ApmFile; LPBYTE pbApmData; DWORD cbApmData = 0; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; pbApmData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbApmData); if(pbApmData != NULL) @@ -456,16 +453,15 @@ struct TRootHandler_OW : public TFileTreeRoot CASC_FREE(pbApmData); } - return nError; + return dwErrCode; } - static int LoadCmfFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName) + static DWORD LoadCmfFile(TCascStorage * hs, CONTENT_KEY & CKey, const char * szFileName) { TCmfFile CmfFile; LPBYTE pbCmfData; DWORD cbCmfData = 0; - - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; pbCmfData = LoadInternalFileToMemory(hs, CKey.Value, CASC_OPEN_BY_CKEY, &cbCmfData); if(pbCmfData != NULL) @@ -484,7 +480,7 @@ struct TRootHandler_OW : public TFileTreeRoot CASC_FREE(pbCmfData); } - return nError; + return dwErrCode; } */ int Load(TCascStorage * hs, CASC_CSV & Csv, size_t nFileNameIndex, size_t nCKeyIndex) @@ -525,7 +521,7 @@ struct TRootHandler_OW : public TFileTreeRoot } /* // Load all CMF+APM files - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { for(size_t i = 0; i < nApmFiles; i++) { @@ -548,8 +544,8 @@ struct TRootHandler_OW : public TFileTreeRoot break; // Create the map of CMF entries - nError = LoadCmfFile(hs, pFileNode2->CKey, szCmfFile); - if(nError != ERROR_SUCCESS) + dwErrCode = LoadCmfFile(hs, pFileNode2->CKey, szCmfFile); + if(dwErrCode != ERROR_SUCCESS) break; } @@ -563,16 +559,16 @@ struct TRootHandler_OW : public TFileTreeRoot // Public functions // TODO: There is way more files in the Overwatch CASC storage than present in the ROOT file. -int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +DWORD RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { TRootHandler_OW * pRootHandler = NULL; CASC_CSV Csv(0, true); size_t Indices[2]; - int nError; + DWORD dwErrCode; // Load the ROOT file - nError = Csv.Load(pbRootFile, cbRootFile); - if(nError == ERROR_SUCCESS) + dwErrCode = Csv.Load(pbRootFile, cbRootFile); + if(dwErrCode == ERROR_SUCCESS) { // Retrieve the indices of the file name and MD5 columns Indices[0] = Csv.GetColumnIndex("FILENAME"); @@ -585,8 +581,8 @@ int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRo if (pRootHandler != NULL) { // Load the root directory. If load failed, we free the object - nError = pRootHandler->Load(hs, Csv, Indices[0], Indices[1]); - if (nError != ERROR_SUCCESS) + dwErrCode = pRootHandler->Load(hs, Csv, Indices[0], Indices[1]); + if (dwErrCode != ERROR_SUCCESS) { delete pRootHandler; pRootHandler = NULL; @@ -595,11 +591,11 @@ int RootHandler_CreateOverwatch(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRo } else { - nError = ERROR_BAD_FORMAT; + dwErrCode = ERROR_BAD_FORMAT; } } // Assign the root directory (or NULL) and return error hs->pRootHandler = pRootHandler; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascRootFile_TVFS.cpp b/dep/CascLib/src/CascRootFile_TVFS.cpp index 38d32120805..25ccf450397 100644 --- a/dep/CascLib/src/CascRootFile_TVFS.cpp +++ b/dep/CascLib/src/CascRootFile_TVFS.cpp @@ -98,19 +98,6 @@ typedef struct _TVFS_PATH_TABLE_ENTRY DWORD NodeValue; // Node value } TVFS_PATH_TABLE_ENTRY, *PTVFS_PATH_TABLE_ENTRY; -// In-memory layout of VFS span entry -typedef struct _TVFS_SPAN_ENTRY -{ - LPBYTE pbCftFileTable; - LPBYTE pbCftFileEntry; - LPBYTE pbCftFileEnd; - - DWORD dwFileOffset; // Offset into the referenced file - DWORD dwSpanSize; // Size of the span - DWORD dwCftOffset; // Offset relative to the beginning of the container file table - -} TVFS_SPAN_ENTRY, *PTVFS_SPAN_ENTRY; - //----------------------------------------------------------------------------- // Handler definition for TVFS root file @@ -123,10 +110,9 @@ struct TRootHandler_TVFS : public TFileTreeRoot { // TVFS supports file names, but DOESN'T support CKeys. dwFeatures |= CASC_FEATURE_FILE_NAMES; - dwNestLevel = 0; } - // Returns size of "container file table offset" fiels in the VFS. + // Returns size of "container file table offset" field in the VFS. // - If the container file table is larger than 0xffffff bytes, it's 4 bytes // - If the container file table is larger than 0xffff bytes, it's 3 bytes // - If the container file table is larger than 0xff bytes, it's 2 bytes @@ -142,50 +128,24 @@ struct TRootHandler_TVFS : public TFileTreeRoot return 1; } - bool PathBuffer_AddChar(PATH_BUFFER & PathBuffer, char chOneChar) - { - if(PathBuffer.szPtr >= PathBuffer.szEnd) - return false; - - *PathBuffer.szPtr++ = chOneChar; - *PathBuffer.szPtr = 0; - return true; - } - - bool PathBuffer_AddString(PATH_BUFFER & PathBuffer, LPBYTE pbNamePtr, LPBYTE pbNameEnd) - { - size_t nLength = (pbNameEnd - pbNamePtr); - - // Check whether we have enough space - if ((PathBuffer.szPtr + nLength) > PathBuffer.szEnd) - return false; - - // Copy the node name - memcpy(PathBuffer.szPtr, pbNamePtr, nLength); - PathBuffer.szPtr += nLength; - return true; - } - - bool PathBuffer_AppendNode(PATH_BUFFER & PathBuffer, TVFS_PATH_TABLE_ENTRY & PathEntry) + bool PathBuffer_AppendNode(CASC_PATH<char> & PathBuffer, TVFS_PATH_TABLE_ENTRY & PathEntry) { // Append the prefix separator, if needed if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE) - PathBuffer_AddChar(PathBuffer, '/'); + PathBuffer.AppendChar('/'); // Append the name fragment, if any if (PathEntry.pbNameEnd > PathEntry.pbNamePtr) - PathBuffer_AddString(PathBuffer, PathEntry.pbNamePtr, PathEntry.pbNameEnd); + PathBuffer.AppendStringN((const char *)PathEntry.pbNamePtr, (PathEntry.pbNameEnd - PathEntry.pbNamePtr), false); // Append the postfix separator, if needed if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST) - PathBuffer_AddChar(PathBuffer, '/'); + PathBuffer.AppendChar('/'); - // Always end the buffer with zero - PathBuffer.szPtr[0] = 0; return true; } - static int CaptureDirectoryHeader(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd) + static DWORD CaptureDirectoryHeader(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd) { // Fill the header structure with zeros memset(&DirHeader, 0, sizeof(TVFS_DIRECTORY_HEADER)); @@ -262,32 +222,50 @@ struct TRootHandler_TVFS : public TFileTreeRoot return (1 <= SpanCount && SpanCount <= 224) ? pbVfsFileEntry : NULL; } - LPBYTE CaptureVfsSpanEntry(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbVfsSpanEntry, TVFS_SPAN_ENTRY & SpanEntry) + LPBYTE CaptureVfsSpanEntries(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbVfsSpanEntry, PCASC_CKEY_ENTRY PtrSpanEntry, size_t SpanCount) { + LPBYTE pbCftFileTable; + LPBYTE pbCftFileEntry; + LPBYTE pbCftFileEnd; LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset; LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize; size_t ItemSize = sizeof(DWORD) + sizeof(DWORD) + DirHeader.CftOffsSize; - // Check the range being captured - if(pbVfsSpanEntry < pbVfsFileTable || (pbVfsSpanEntry + ItemSize) > pbVfsFileEnd) + // Check whether all spans are included in the valid range + if(pbVfsSpanEntry < pbVfsFileTable || (pbVfsSpanEntry + (ItemSize * SpanCount)) > pbVfsFileEnd) return NULL; - // - // Structure of the span entry: - // (4bytes): Offset into the referenced file (big endian) - // (4bytes): Size of the span (big endian) - // (?bytes): Offset into Container File Table. Length depends on container file table size - // - - SpanEntry.dwFileOffset = ConvertBytesToInteger_4(pbVfsSpanEntry); - SpanEntry.dwSpanSize = ConvertBytesToInteger_4(pbVfsSpanEntry + sizeof(DWORD)); - SpanEntry.dwCftOffset = ConvertBytesToInteger_X(pbVfsSpanEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize); - - // Resolve the Container File Table entries - SpanEntry.pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset; - SpanEntry.pbCftFileEntry = SpanEntry.pbCftFileTable + SpanEntry.dwCftOffset; - SpanEntry.pbCftFileEnd = SpanEntry.pbCftFileTable + DirHeader.CftTableSize; - return pbVfsSpanEntry + ItemSize; + // Convert all spans + for(size_t i = 0; i < SpanCount; i++) + { + DWORD dwCftOffset = ConvertBytesToInteger_X(pbVfsSpanEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize); + + // + // Structure of the span entry: + // (4bytes): Offset into the referenced file (big endian) + // (4bytes): Size of the span (big endian) + // (?bytes): Offset into Container File Table. Length depends on container file table size + // + + // Resolve the Container File Table entry + pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset; + pbCftFileEntry = pbCftFileTable + dwCftOffset; + pbCftFileEnd = pbCftFileTable + DirHeader.CftTableSize; + + // Capture the EKey and the file size + if((pbCftFileEntry + DirHeader.EKeySize + sizeof(DWORD)) > pbCftFileEnd) + return NULL; + + // Copy the EKey and content size + CaptureEncodedKey(PtrSpanEntry->EKey, pbCftFileEntry, DirHeader.EKeySize); + PtrSpanEntry->ContentSize = ConvertBytesToInteger_4(pbVfsSpanEntry + sizeof(DWORD)); + + // Move to the next entry + pbVfsSpanEntry += ItemSize; + PtrSpanEntry++; + } + + return pbVfsSpanEntry; } // @@ -365,7 +343,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot return pbPathTablePtr; } - bool IsVfsFileEKey(TCascStorage * hs, ENCODED_KEY & EKey, size_t EKeyLength) + bool IsVfsFileEKey(TCascStorage * hs, LPBYTE EKey, size_t EKeyLength) { PCASC_CKEY_ENTRY pCKeyEntry; size_t ItemCount = hs->VfsRootList.ItemCount(); @@ -376,7 +354,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i); if (pCKeyEntry != NULL) { - if (!memcmp(pCKeyEntry->EKey, EKey.Value, EKeyLength)) + if (!memcmp(pCKeyEntry->EKey, EKey, EKeyLength)) return true; } } @@ -387,27 +365,27 @@ struct TRootHandler_TVFS : public TFileTreeRoot // This function verifies whether a file is actually a sub-directory. // If yes, it contains just another "TVFS" virtual file system, just like the ROOT file. - int IsVfsSubDirectory(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, TVFS_DIRECTORY_HEADER & SubHeader, ENCODED_KEY & EKey, DWORD dwFileSize) + DWORD IsVfsSubDirectory(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, TVFS_DIRECTORY_HEADER & SubHeader, LPBYTE EKey, DWORD dwFileSize) { PCASC_CKEY_ENTRY pCKeyEntry; LPBYTE pbVfsData = NULL; DWORD cbVfsData = dwFileSize; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Verify whether the EKey is in the list of VFS root files if(IsVfsFileEKey(hs, EKey, DirHeader.EKeySize)) { // Locate the CKey entry - if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL) + if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey)) != NULL) { // Load the entire file into memory pbVfsData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbVfsData); if (pbVfsData && cbVfsData) { // Capture the file folder. This also serves as test - nError = CaptureDirectoryHeader(SubHeader, pbVfsData, pbVfsData + cbVfsData); - if (nError == ERROR_SUCCESS) - return nError; + dwErrCode = CaptureDirectoryHeader(SubHeader, pbVfsData, pbVfsData + cbVfsData); + if (dwErrCode == ERROR_SUCCESS) + return dwErrCode; // Clear the captured header memset(&SubHeader, 0, sizeof(TVFS_DIRECTORY_HEADER)); @@ -416,7 +394,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot } } - return nError; + return dwErrCode; } void InsertRootVfsEntry(TCascStorage * hs, LPBYTE pbCKey, const char * szFormat, size_t nIndex) @@ -432,15 +410,18 @@ struct TRootHandler_TVFS : public TFileTreeRoot } } - DWORD ParsePathFileTable(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd) + DWORD ParsePathFileTable(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, CASC_PATH<char> & PathBuffer, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd) { TVFS_DIRECTORY_HEADER SubHeader; TVFS_PATH_TABLE_ENTRY PathEntry; PCASC_CKEY_ENTRY pCKeyEntry; LPBYTE pbVfsSpanEntry; - char * szSavePathPtr = PathBuffer.szPtr; - DWORD dwSpanCount = 0; - int nError; + size_t nSavePos = PathBuffer.Save(); + DWORD dwSpanCount; + DWORD dwErrCode; + + // Sanity check + assert(SpanArray.IsInitialized()); // Parse the file table while(pbPathTablePtr < pbPathTableEnd) @@ -467,9 +448,9 @@ struct TRootHandler_TVFS : public TFileTreeRoot assert((PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) >= sizeof(DWORD)); // Recursively call the folder parser on the same file - nError = ParsePathFileTable(hs, DirHeader, PathBuffer, pbPathTablePtr, pbDirectoryEnd); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = ParsePathFileTable(hs, DirHeader, PathBuffer, pbPathTablePtr, pbDirectoryEnd); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; // Skip the directory data pbPathTablePtr = pbDirectoryEnd; @@ -481,63 +462,117 @@ struct TRootHandler_TVFS : public TFileTreeRoot if(pbVfsSpanEntry == NULL) return ERROR_BAD_FORMAT; - // TODO: Need to support multi-span files larger than 4 GB - // Example: CoD: Black Ops 4, file "zone/base.xpak" 0x16 spans, over 15 GB size - assert(dwSpanCount == 1); - dwSpanCount = 1; - - // Parse all span entries - for(DWORD i = 0; i < dwSpanCount; i++) + // If it's one span, it's either a subdirectory or an entire file + if(dwSpanCount == 1) { - TVFS_SPAN_ENTRY SpanEntry; - ENCODED_KEY EKey = {0}; + CASC_CKEY_ENTRY SpanEntry; - // Capture the n-th span entry - pbVfsSpanEntry = CaptureVfsSpanEntry(DirHeader, pbVfsSpanEntry, SpanEntry); + // Capture the single span entry + pbVfsSpanEntry = CaptureVfsSpanEntries(DirHeader, pbVfsSpanEntry, &SpanEntry, 1); if(pbVfsSpanEntry == NULL) return ERROR_FILE_CORRUPT; - // Capture the encoded key - if((SpanEntry.pbCftFileEntry + DirHeader.EKeySize) > SpanEntry.pbCftFileEnd) - return ERROR_BAD_FORMAT; + // Find the CKey entry + pCKeyEntry = FindCKeyEntry_EKey(hs, SpanEntry.EKey); + if(pCKeyEntry != NULL) + { + // We need to check whether this is another TVFS directory file + if (IsVfsSubDirectory(hs, DirHeader, SubHeader, SpanEntry.EKey, SpanEntry.ContentSize) == ERROR_SUCCESS) + { + // Add colon (':') + PathBuffer.AppendChar(':'); + + // The file content size should already be there + assert(pCKeyEntry->ContentSize == SpanEntry.ContentSize); + FileTree.InsertByName(pCKeyEntry, PathBuffer); - // Copy the EKey - memcpy(EKey.Value, SpanEntry.pbCftFileEntry, DirHeader.EKeySize); + // Parse the subdir + ParseDirectoryData(hs, SubHeader, PathBuffer); + CASC_FREE(SubHeader.pbDirectoryData); + } + else + { + // If the content content size is not there, supply it now + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + pCKeyEntry->ContentSize = SpanEntry.ContentSize; + FileTree.InsertByName(pCKeyEntry, PathBuffer); + } + } + } + else + { + PCASC_CKEY_ENTRY pSpanEntries; + PCASC_FILE_NODE pFileNode; + USHORT RefCount; + bool bFilePresent = true; + + // + // Need to support multi-span files, possibly lager than 4 GB + // Example: CoD: Black Ops 4, file "zone/base.xpak" 0x16 spans, over 15 GB size + // + + // Allocate buffer for all span entries + pSpanEntries = (PCASC_CKEY_ENTRY)SpanArray.Insert(dwSpanCount); + if(pSpanEntries == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Capture all span entries + pbVfsSpanEntry = CaptureVfsSpanEntries(DirHeader, pbVfsSpanEntry, pSpanEntries, dwSpanCount); + if(pbVfsSpanEntry == NULL) + return ERROR_FILE_CORRUPT; - // We need to check whether this is another TVFS directory file - if (IsVfsSubDirectory(hs, DirHeader, SubHeader, EKey, SpanEntry.dwSpanSize) == ERROR_SUCCESS) + // Parse all span entries + for(DWORD dwSpanIndex = 0; dwSpanIndex < dwSpanCount; dwSpanIndex++) { - // Add colon (':') - PathBuffer_AddChar(PathBuffer, ':'); + PCASC_CKEY_ENTRY pSpanEntry = pSpanEntries + dwSpanIndex; - // Insert the file to the file tree - if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL) + // Find the CKey entry + pCKeyEntry = FindCKeyEntry_EKey(hs, pSpanEntries[dwSpanIndex].EKey); + if(pCKeyEntry == NULL) { - // The file content size should already be there - assert(pCKeyEntry->ContentSize == SpanEntry.dwSpanSize); - FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + bFilePresent = false; + break; } - ParseDirectoryData(hs, SubHeader, PathBuffer); - CASC_FREE(SubHeader.pbDirectoryData); - } - else - { - // Insert the file to the file tree - if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL) + // Supply the content size + if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) + pCKeyEntry->ContentSize = pSpanEntry->ContentSize; + assert(pCKeyEntry->ContentSize == pSpanEntry->ContentSize); + + // Fill-in the span entry + if(dwSpanIndex == 0) { - // If the file content is not there, supply it now - if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE) - pCKeyEntry->ContentSize = SpanEntry.dwSpanSize; - FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + pCKeyEntry->SpanCount = (BYTE)(dwSpanCount); + pCKeyEntry->RefCount++; } + else + { + // Mark the CKey entry as a file span. Note that a CKey entry + // can actually be both a file span and a standalone file: + // * zone/zm_red.xpak - { zone/zm_red.xpak_1, zone/zm_red.xpak_2, ..., zone/zm_red.xpak_6 } + pCKeyEntry->Flags |= CASC_CE_FILE_SPAN; + } + + // Copy all from the existing CKey entry + memcpy(pSpanEntry, pCKeyEntry, sizeof(CASC_CKEY_ENTRY)); + } + + // Do nothing if the file is not present locally + if(bFilePresent) + { + // Insert a new file node that will contain pointer to the span entries + RefCount = pSpanEntries->RefCount; + pFileNode = FileTree.InsertByName(pSpanEntries, PathBuffer); + pSpanEntries->RefCount = RefCount; + + if(pFileNode == NULL) + return ERROR_NOT_ENOUGH_MEMORY; } } } // Reset the position of the path buffer - PathBuffer.szPtr = szSavePathPtr; - PathBuffer.szPtr[0] = 0; + PathBuffer.Restore(nSavePos); } } @@ -545,7 +580,7 @@ struct TRootHandler_TVFS : public TFileTreeRoot return ERROR_SUCCESS; } - int ParseDirectoryData(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer) + DWORD ParseDirectoryData(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, CASC_PATH<char> & PathBuffer) { LPBYTE pbRootDirectory = DirHeader.pbDirectoryData + DirHeader.PathTableOffset; LPBYTE pbRootDirPtr = pbRootDirectory; @@ -583,21 +618,19 @@ struct TRootHandler_TVFS : public TFileTreeRoot return ParsePathFileTable(hs, DirHeader, PathBuffer, pbRootDirPtr, pbRootDirEnd); } - int Load(TCascStorage * hs, TVFS_DIRECTORY_HEADER & RootHeader) + DWORD Load(TCascStorage * hs, TVFS_DIRECTORY_HEADER & RootHeader) { -// PCASC_CKEY_ENTRY pCKeyEntry; - PATH_BUFFER PathBuffer; - char szPathBuffer[MAX_PATH]; - - // Initialize the path buffer - memset(szPathBuffer, 0, sizeof(szPathBuffer)); - PathBuffer.szBegin = - PathBuffer.szPtr = szPathBuffer; - PathBuffer.szEnd = szPathBuffer + MAX_PATH; + CASC_PATH<char> PathBuffer; + DWORD dwErrCode; // Save the length of the key FileTree.SetKeyLength(RootHeader.EKeySize); + // Initialize the array of span entries + dwErrCode = SpanArray.Create(sizeof(CASC_CKEY_ENTRY), 0x100); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; + // Insert the main VFS root file as named entry InsertRootVfsEntry(hs, hs->VfsRoot.CKey, "vfs-root", 0); @@ -612,29 +645,29 @@ struct TRootHandler_TVFS : public TFileTreeRoot return ParseDirectoryData(hs, RootHeader, PathBuffer); } - DWORD dwNestLevel; + CASC_ARRAY SpanArray; // Array of CASC_SPAN_ENTRY for all multi-span files }; //----------------------------------------------------------------------------- // Public functions - TVFS root -int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +DWORD RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { TRootHandler_TVFS * pRootHandler = NULL; TVFS_DIRECTORY_HEADER RootHeader; - int nError; + DWORD dwErrCode; // Capture the entire root directory - nError = TRootHandler_TVFS::CaptureDirectoryHeader(RootHeader, pbRootFile, pbRootFile + cbRootFile); - if(nError == ERROR_SUCCESS) + dwErrCode = TRootHandler_TVFS::CaptureDirectoryHeader(RootHeader, pbRootFile, pbRootFile + cbRootFile); + if(dwErrCode == ERROR_SUCCESS) { // Allocate the root handler object pRootHandler = new TRootHandler_TVFS(); if(pRootHandler != NULL) { // Load the root directory. If load failed, we free the object - nError = pRootHandler->Load(hs, RootHeader); - if(nError != ERROR_SUCCESS) + dwErrCode = pRootHandler->Load(hs, RootHeader); + if(dwErrCode != ERROR_SUCCESS) { delete pRootHandler; pRootHandler = NULL; @@ -644,5 +677,5 @@ int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFil // Assign the root directory (or NULL) and return error hs->pRootHandler = pRootHandler; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascRootFile_Text.cpp b/dep/CascLib/src/CascRootFile_Text.cpp index e78b0bf086f..7662f85d86d 100644 --- a/dep/CascLib/src/CascRootFile_Text.cpp +++ b/dep/CascLib/src/CascRootFile_Text.cpp @@ -49,16 +49,16 @@ struct TRootHandler_SC1 : public TFileTreeRoot return bResult; } - int Load(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) + DWORD Load(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { PCASC_CKEY_ENTRY pCKeyEntry; CASC_CSV Csv(0, false); BYTE CKey[MD5_HASH_SIZE]; - int nError; + DWORD dwErrCode; // Parse the ROOT file first in order to see whether we have the correct format - nError = Csv.Load(pbRootFile, cbRootFile); - if(nError == ERROR_SUCCESS) + dwErrCode = Csv.Load(pbRootFile, cbRootFile); + if(dwErrCode == ERROR_SUCCESS) { // Parse all lines while(Csv.LoadNextLine()) @@ -79,7 +79,7 @@ struct TRootHandler_SC1 : public TFileTreeRoot } } - return nError; + return dwErrCode; } }; @@ -93,10 +93,10 @@ struct TRootHandler_SC1 : public TFileTreeRoot // locales/zhCN/Assets/sound/terran/ghost/tghdth01.wav|6637ed776bd22089e083b8b0b2c0374c // -int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) +DWORD RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { TRootHandler_SC1 * pRootHandler = NULL; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Verify whether this looks like a Starcraft I root file if(TRootHandler_SC1::IsRootFile(pbRootFile, cbRootFile)) @@ -106,8 +106,8 @@ int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbR if(pRootHandler != NULL) { // Load the root directory. If load failed, we free the object - nError = pRootHandler->Load(hs, pbRootFile, cbRootFile); - if(nError != ERROR_SUCCESS) + dwErrCode = pRootHandler->Load(hs, pbRootFile, cbRootFile); + if(dwErrCode != ERROR_SUCCESS) { delete pRootHandler; pRootHandler = NULL; @@ -117,5 +117,5 @@ int RootHandler_CreateStarcraft1(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbR // Assign the root directory (or NULL) and return error hs->pRootHandler = pRootHandler; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascRootFile_WoW.cpp b/dep/CascLib/src/CascRootFile_WoW.cpp index 766e226a2b1..3bbd8b81ef3 100644 --- a/dep/CascLib/src/CascRootFile_WoW.cpp +++ b/dep/CascLib/src/CascRootFile_WoW.cpp @@ -178,7 +178,7 @@ struct TRootHandler_WoW : public TFileTreeRoot } } - int ParseWowRootFile_AddFiles_6x(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup) + DWORD ParseWowRootFile_AddFiles_6x(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup) { PFILE_ROOT_ENTRY pRootEntry = RootGroup.pRootEntries; PCASC_CKEY_ENTRY pCKeyEntry; @@ -215,7 +215,7 @@ struct TRootHandler_WoW : public TFileTreeRoot return ERROR_SUCCESS; } - int ParseWowRootFile_AddFiles_82(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup) + DWORD ParseWowRootFile_AddFiles_82(TCascStorage * hs, FILE_ROOT_GROUP & RootGroup) { PCASC_CKEY_ENTRY pCKeyEntry; PCONTENT_KEY pCKey = RootGroup.pCKeyEntries; @@ -229,6 +229,7 @@ struct TRootHandler_WoW : public TFileTreeRoot { // Set the file data ID FileDataId = FileDataId + RootGroup.FileDataIds[i]; + //printf("File Data ID: %u\n", FileDataId); // Find the item in the central storage. Insert it to the tree if((pCKeyEntry = FindCKeyEntry_CKey(hs, pCKey->Value)) != NULL) @@ -253,7 +254,7 @@ struct TRootHandler_WoW : public TFileTreeRoot return ERROR_SUCCESS; } - int ParseWowRootFile_Level2( + DWORD ParseWowRootFile_Level2( TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, @@ -269,6 +270,10 @@ struct TRootHandler_WoW : public TFileTreeRoot // Now parse the root file while(pbRootPtr < pbRootEnd) { + //char szMessage[0x100]; + //StringCchPrintfA(szMessage, _countof(szMessage), "%p\n", (pbRootEnd - pbRootPtr)); + //OutputDebugStringA(szMessage); + // Validate the file locale block pbRootPtr = CaptureRootGroup(RootBlock, pbRootPtr, pbRootEnd); if(pbRootPtr == NULL) @@ -287,7 +292,7 @@ struct TRootHandler_WoW : public TFileTreeRoot continue; // WoW.exe (build 19116): Locales other than defined mask are skipped too - if((RootBlock.Header.LocaleFlags & dwLocaleMask) == 0) + if(RootBlock.Header.LocaleFlags != 0 && (RootBlock.Header.LocaleFlags & dwLocaleMask) == 0) continue; // Now call the custom function @@ -351,19 +356,19 @@ struct TRootHandler_WoW : public TFileTreeRoot LoadWowRootFileLocales(hs, pbRootPtr, cbRootFile, (1 << dwLocale), bOverrideArchive, bAudioLocale); */ - int ParseWowRootFile_Level1( + DWORD ParseWowRootFile_Level1( TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask, BYTE bAudioLocale) { - int nError; + DWORD dwErrCode; // Load the locale as-is - nError = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale); - if (nError != ERROR_SUCCESS) - return nError; + dwErrCode = ParseWowRootFile_Level2(hs, pbRootPtr, pbRootEnd, dwLocaleMask, false, bAudioLocale); + if (dwErrCode != ERROR_SUCCESS) + return dwErrCode; // If we wanted enGB, we also load enUS for the missing files if(dwLocaleMask == CASC_LOCALE_ENGB) @@ -376,15 +381,15 @@ struct TRootHandler_WoW : public TFileTreeRoot } // WoW.exe: 004146C7 (BuildManifest::Load) - int Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask) + DWORD Load(TCascStorage * hs, LPBYTE pbRootPtr, LPBYTE pbRootEnd, DWORD dwLocaleMask) { - int nError; + DWORD dwErrCode; - nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0); - if (nError == ERROR_SUCCESS) - nError = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1); + dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 0); + if (dwErrCode == ERROR_SUCCESS) + dwErrCode = ParseWowRootFile_Level1(hs, pbRootPtr, pbRootEnd, dwLocaleMask, 1); - return nError; + return dwErrCode; } // Search for files @@ -461,7 +466,7 @@ struct TRootHandler_WoW : public TFileTreeRoot //----------------------------------------------------------------------------- // Public functions -int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask) +DWORD RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, DWORD dwLocaleMask) { TRootHandler_WoW * pRootHandler = NULL; FILE_ROOT_HEADER_82 RootHeader; @@ -469,7 +474,7 @@ int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile LPBYTE pbRootEnd = pbRootFile + cbRootFile; LPBYTE pbRootPtr; DWORD FileCounterHashless = 0; - int nError = ERROR_BAD_FORMAT; + DWORD dwErrCode = ERROR_BAD_FORMAT; // Check for the new format (World of Warcraft 8.2, build 30170) pbRootPtr = TRootHandler_WoW::CaptureRootHeader(RootHeader, pbRootFile, pbRootEnd); @@ -485,8 +490,8 @@ int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile if(pRootHandler != NULL) { // Load the root directory. If load failed, we free the object - nError = pRootHandler->Load(hs, pbRootFile, pbRootEnd, dwLocaleMask); - if(nError != ERROR_SUCCESS) + dwErrCode = pRootHandler->Load(hs, pbRootFile, pbRootEnd, dwLocaleMask); + if(dwErrCode != ERROR_SUCCESS) { delete pRootHandler; pRootHandler = NULL; @@ -495,6 +500,6 @@ int RootHandler_CreateWoW(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile // Assign the root directory (or NULL) and return error hs->pRootHandler = pRootHandler; - return nError; + return dwErrCode; } diff --git a/dep/CascLib/src/CascStructs.h b/dep/CascLib/src/CascStructs.h index f67da810c9e..42478c324f0 100644 --- a/dep/CascLib/src/CascStructs.h +++ b/dep/CascLib/src/CascStructs.h @@ -94,7 +94,7 @@ struct FILE_INDEX_FOOTER BYTE PageSizeKB; // Length, in kilobytes, of the index page BYTE OffsetBytes; // Normally 4 for archive indices, 6 for group indices, and 0 for loose file indices BYTE SizeBytes; // Normally 4 - BYTE EKeySizeBytes; // Normally 16 + BYTE EKeyLength; // Normally 16 BYTE FooterHashBytes; // Normally 8, <= 0x10 BYTE ElementCount[4]; // BigEndian in _old_ versions (e.g. 18179) BYTE FooterHash[CHKSUM_LENGTH]; diff --git a/dep/CascLib/src/DllMain.rc b/dep/CascLib/src/DllMain.rc index 0ae89026266..7cb068f5ada 100644 --- a/dep/CascLib/src/DllMain.rc +++ b/dep/CascLib/src/DllMain.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,50,0,156 - PRODUCTVERSION 1,50,0,156 + FILEVERSION 1,50,0,158 + PRODUCTVERSION 1,50,0,158 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -42,12 +42,12 @@ BEGIN BLOCK "040504b0" BEGIN VALUE "FileDescription", "CascLib library for reading Blizzard CASC storages" - VALUE "FileVersion", "1, 50, 0, 156\0" + VALUE "FileVersion", "1, 50, 0, 158\0" VALUE "InternalName", "CascLib" VALUE "LegalCopyright", "Copyright (c) 2014 - 2019 Ladislav Zezula" VALUE "OriginalFilename", "CascLib.dll" VALUE "ProductName", "CascLib" - VALUE "ProductVersion", "1, 50, 0, 156\0" + VALUE "ProductVersion", "1, 50, 0, 158\0" END END BLOCK "VarFileInfo" diff --git a/dep/CascLib/src/common/Array.h b/dep/CascLib/src/common/Array.h index 1dc96b7be8e..ea99d10fe22 100644 --- a/dep/CascLib/src/common/Array.h +++ b/dep/CascLib/src/common/Array.h @@ -43,7 +43,7 @@ class CASC_ARRAY int Create(size_t ItemSize, size_t ItemCountMax) { // Create the array - if ((m_pItemArray = CASC_ALLOC(BYTE, ItemSize * ItemCountMax)) == NULL) + if ((m_pItemArray = CASC_ALLOC<BYTE>(ItemSize * ItemCountMax)) == NULL) return ERROR_NOT_ENOUGH_MEMORY; m_ItemCountMax = ItemCountMax; @@ -53,12 +53,12 @@ class CASC_ARRAY } // Inserts one or more items; returns pointer to the first inserted item - void * Insert(size_t NewItemCount) + void * Insert(size_t NewItemCount, bool bEnlargeAllowed = true) { void * pNewItems; // Try to enlarge the buffer, if needed - if (!EnlargeArray(m_ItemCount + NewItemCount)) + if (!EnlargeArray(m_ItemCount + NewItemCount, bEnlargeAllowed)) return NULL; pNewItems = m_pItemArray + (m_ItemCount * m_ItemSize); @@ -70,9 +70,9 @@ class CASC_ARRAY } // Inserts one or more items; returns pointer to the first inserted item - void * Insert(const void * NewItems, size_t NewItemCount) + void * Insert(const void * NewItems, size_t NewItemCount, bool bEnlargeAllowed = true) { - void * pNewItem = Insert(NewItemCount); + void * pNewItem = Insert(NewItemCount, bEnlargeAllowed); // Copy the item(s) to the array, if any if (pNewItem && NewItems) @@ -99,7 +99,7 @@ class CASC_ARRAY LPBYTE pbNewItem; // Make sure we have array large enough - if(!EnlargeArray(ItemIndex + 1)) + if(!EnlargeArray(ItemIndex + 1, true)) return NULL; // Get the items range @@ -169,7 +169,7 @@ class CASC_ARRAY protected: - bool EnlargeArray(size_t NewItemCount) + bool EnlargeArray(size_t NewItemCount, bool bEnlargeAllowed) { LPBYTE NewItemArray; size_t ItemCountMax; @@ -181,6 +181,10 @@ class CASC_ARRAY // Shall we enlarge the table? if (NewItemCount > m_ItemCountMax) { + // Deny enlarge if not allowed + if(bEnlargeAllowed == false) + return false; + // Calculate new table size ItemCountMax = m_ItemCountMax; while (ItemCountMax < NewItemCount) diff --git a/dep/CascLib/src/common/Common.cpp b/dep/CascLib/src/common/Common.cpp index 2860671227b..1fc6cc977b4 100644 --- a/dep/CascLib/src/common/Common.cpp +++ b/dep/CascLib/src/common/Common.cpp @@ -81,6 +81,19 @@ void SetLastError(DWORD dwErrCode) //----------------------------------------------------------------------------- // Linear data stream manipulation +LPBYTE CaptureInteger16_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue) +{ + // Is there enough data? + if((pbDataPtr + sizeof(USHORT)) > pbDataEnd) + return NULL; + + // Convert data from Little endian to + PtrValue[0] = ConvertBytesToInteger_2(pbDataPtr); + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(USHORT); +} + LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue) { // Is there enough data? @@ -133,6 +146,49 @@ LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrC return pbDataPtr + sizeof(CONTENT_KEY); } +LPBYTE CaptureEncodedKey(LPBYTE pbEKey, LPBYTE pbData, BYTE EKeyLength) +{ + // Two usual lengths of EKey + assert(EKeyLength == 0x09 || EKeyLength == 0x10); + + // Copy the first 0x09 bytes + if(EKeyLength >= 0x09) + { + pbEKey[0x00] = pbData[0x00]; + pbEKey[0x01] = pbData[0x01]; + pbEKey[0x02] = pbData[0x02]; + pbEKey[0x03] = pbData[0x03]; + pbEKey[0x04] = pbData[0x04]; + pbEKey[0x05] = pbData[0x05]; + pbEKey[0x06] = pbData[0x06]; + pbEKey[0x07] = pbData[0x07]; + pbEKey[0x08] = pbData[0x08]; + + if(EKeyLength == 0x10) + { + pbEKey[0x09] = pbData[0x09]; + pbEKey[0x0A] = pbData[0x0A]; + pbEKey[0x0B] = pbData[0x0B]; + pbEKey[0x0C] = pbData[0x0C]; + pbEKey[0x0D] = pbData[0x0D]; + pbEKey[0x0E] = pbData[0x0E]; + pbEKey[0x0F] = pbData[0x0F]; + } + else + { + pbEKey[0x09] = 0; + pbEKey[0x0A] = 0; + pbEKey[0x0B] = 0; + pbEKey[0x0C] = 0; + pbEKey[0x0D] = 0; + pbEKey[0x0E] = 0; + pbEKey[0x0F] = 0; + } + } + + return pbData + EKeyLength; +} + LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount) { size_t ArraySize = ItemSize * ItemCount; @@ -229,6 +285,7 @@ size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...) #ifdef PLATFORM_WINDOWS StringCchVPrintfExA(buffer, nCount, &buffend, NULL, 0, format, argList); +// buffend = buffer + vsnprintf(buffer, nCount, format, argList); #else buffend = buffer + vsnprintf(buffer, nCount, format, argList); #endif @@ -248,6 +305,7 @@ size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, .. #ifdef PLATFORM_WINDOWS StringCchVPrintfExW(buffer, nCount, &buffend, NULL, 0, format, argList); +// buffend = buffer + vswprintf(buffer, nCount, format, argList); #else buffend = buffer + vswprintf(buffer, nCount, format, argList); #endif @@ -268,7 +326,7 @@ char * CascNewStr(const char * szString, size_t nCharsToReserve) if(szString != NULL) { nLength = strlen(szString); - szNewString = CASC_ALLOC(char, nLength + nCharsToReserve + 1); + szNewString = CASC_ALLOC<char>(nLength + nCharsToReserve + 1); if(szNewString != NULL) { memcpy(szNewString, szString, nLength); @@ -287,7 +345,7 @@ wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve) if(szString != NULL) { nLength = wcslen(szString); - szNewString = CASC_ALLOC(wchar_t, nLength + nCharsToReserve + 1); + szNewString = CASC_ALLOC<wchar_t>(nLength + nCharsToReserve + 1); if(szNewString != NULL) { memcpy(szNewString, szString, nLength * sizeof(wchar_t)); @@ -298,43 +356,8 @@ wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve) return szNewString; } -template <typename XCHAR> -TCHAR * AppendPathFragment(TCHAR * szBuffer, TCHAR * szBufferEnd, const XCHAR * szPath, char chSeparator, bool bFirstFragment = false) -{ - // The "Path" must not be empty - if(szPath && szPath[0]) - { - // Append the path separator after the first fragment - if(szBuffer < szBufferEnd && bFirstFragment == false) - { - if(szBuffer[-1] != chSeparator) - { - *szBuffer++ = chSeparator; - } - } - - // Copy the sub path - while(szBuffer < szBufferEnd && szPath[0] != 0) - { - // If there is a path separator, we skip it (all of them) and put single separator there - if(szPath[0] == '\\' || szPath[0] == '/') - { - while(szPath[0] == '\\' || szPath[0] == '/') - szPath++; - *szBuffer++ = chSeparator; - } - else - { - *szBuffer++ = *szPath++; - } - } - - // Append end of string - szBuffer[0] = 0; - } - - return szBuffer; -} +//----------------------------------------------------------------------------- +// String merging LPTSTR GetLastPathPart(LPTSTR szWorkPath) { @@ -377,19 +400,18 @@ bool CutLastPathPart(TCHAR * szWorkPath) size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, va_list argList) { - LPTSTR szSaveBuffer = szBuffer; - LPTSTR szBufferEnd = szBuffer + nMaxChars - 1; - LPTSTR szFragment; - bool bFirstFragment = true; + CASC_PATH<TCHAR> Path(chSeparator); + LPCTSTR szFragment; + bool bWithSeparator = false; // Combine all parts of the path here while((szFragment = va_arg(argList, LPTSTR)) != NULL) { - szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szFragment, chSeparator, bFirstFragment); - bFirstFragment = false; + Path.AppendString(szFragment, bWithSeparator); + bWithSeparator = true; } - return (szBuffer - szSaveBuffer); + return Path.Copy(szBuffer, nMaxChars); } size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, ...) @@ -406,40 +428,12 @@ size_t CombinePath(LPTSTR szBuffer, size_t nMaxChars, char chSeparator, ...) LPTSTR CombinePath(LPCTSTR szDirectory, LPCTSTR szSubDir) { - LPTSTR szFullPath; - size_t nLength = 0; + CASC_PATH<TCHAR> Path(PATH_SEP_CHAR); - // Calculate length - if(szDirectory != NULL) - nLength += (_tcslen(szDirectory) + 1); - if(szSubDir != NULL) - nLength += (_tcslen(szSubDir) + 1); - - // Allocate buffer - if((szFullPath = CASC_ALLOC(TCHAR, nLength)) != NULL) - { - CombinePath(szFullPath, nLength, PATH_SEP_CHAR, szDirectory, szSubDir, NULL); - } - - return szFullPath; -} - -size_t CreateCascSubdirectoryName(LPTSTR szBuffer, size_t nMaxChars, LPCTSTR szSubDir, LPCTSTR szExtension, LPBYTE pbEKey) -{ - TCHAR * szSaveBuffer = szBuffer; - TCHAR * szBufferEnd = szBuffer + nMaxChars - 1; - char szHashSubPath[0x80]; - char szHashText[MD5_STRING_SIZE+1]; - - // Prepare the subpath - StringFromBinary(pbEKey, MD5_HASH_SIZE, szHashText); - CascStrPrintf(szHashSubPath, _countof(szHashSubPath), "%02x/%02x/%s", pbEKey[0], pbEKey[1], szHashText); - - // Combine the path together - szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubDir, URL_SEP_CHAR, true); - szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHashSubPath, URL_SEP_CHAR); - szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szExtension, URL_SEP_CHAR, true); - return (szBuffer - szSaveBuffer); + // 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) @@ -585,32 +579,6 @@ int ConvertStringToBinary( return ERROR_SUCCESS; } -char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer) -{ - char * szSaveBuffer = szBuffer; - - // Verify the binary pointer - if(pbBinary && cbBinary) - { - // Convert the string to the array of MD5 - // Copy the blob data as text - for(size_t i = 0; i < cbBinary; i++) - { - *szBuffer++ = IntToHexChar[pbBinary[i] >> 0x04]; - *szBuffer++ = IntToHexChar[pbBinary[i] & 0x0F]; - } - } - - // Terminate the string - *szBuffer = 0; - return szSaveBuffer; -} - -char * StringFromMD5(LPBYTE md5, char * szBuffer) -{ - return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer); -} - //----------------------------------------------------------------------------- // File name utilities @@ -693,10 +661,8 @@ bool CascCheckWildCard(const char * szString, const char * szWildCard) { if(szWildCardPtr[0] == '*') { - szWildCardPtr++; - - if(szWildCardPtr[0] == '*') - continue; + while(szWildCardPtr[0] == '*') + szWildCardPtr++; if(szWildCardPtr[0] == 0) return true; diff --git a/dep/CascLib/src/common/Common.h b/dep/CascLib/src/common/Common.h index 45a2ec93e6b..d32202413ba 100644 --- a/dep/CascLib/src/common/Common.h +++ b/dep/CascLib/src/common/Common.h @@ -39,13 +39,19 @@ typedef struct _CONTENT_KEY } CONTENT_KEY, *PCONTENT_KEY, ENCODED_KEY, *PENCODED_KEY; -// Helper structure for merging file paths -typedef struct _PATH_BUFFER +//----------------------------------------------------------------------------- +// EKey entry, captured from index files of all types. This structure +// is somewhat less memory consuming than CASC_CKEY_ENTRY + +typedef struct _CASC_EKEY_ENTRY { - char * szBegin; - char * szPtr; - char * szEnd; -} PATH_BUFFER, *PPATH_BUFFER; + BYTE EKey[MD5_HASH_SIZE]; // Encoded key. Length depends on TCascStorage::EKeyLength + ULONGLONG StorageOffset; // Offset of the encoded file in archive. + // Lower (TCascStorage::FileOffsetBits) bits are archive offset. + // Upper bits are archive index + DWORD EncodedSize; // Encoded size + DWORD Alignment; // Alignment to 8-byte boundary. Reserved for future use +} CASC_EKEY_ENTRY, *PCASC_EKEY_ENTRY; //----------------------------------------------------------------------------- // Basic structure used by all CascLib objects to describe a single entry @@ -59,7 +65,12 @@ typedef struct _PATH_BUFFER #define CASC_CE_HAS_EKEY_PARTIAL 0x00000008 // The EKey is only partial, padded by zeros. Always used with CASC_CE_HAS_EKEY #define CASC_CE_IN_ENCODING 0x00000010 // Present in the ENCODING manifest #define CASC_CE_IN_DOWNLOAD 0x00000020 // Present in the DOWNLOAD manifest -#define CASC_CE_FOLDER_ENTRY 0x00000040 // This CKey entry is a folder +#define CASC_CE_IN_BUILD 0x00000040 // Present in the BUILD (text) manifest +#define CASC_CE_IN_ARCHIVE 0x00000080 // File is stored in an archive (for online storages) +#define CASC_CE_FOLDER_ENTRY 0x00000100 // This CKey entry is a folder +#define CASC_CE_FILE_SPAN 0x00000200 // This CKey entry is a follow-up file span +#define CASC_CE_FILE_PATCH 0x00000400 // The file is in PATCH subfolder in remote storage +#define CASC_CE_PLAIN_DATA 0x00000800 // The file data is not BLTE encoded, but in plain format // In-memory representation of a single entry. struct CASC_CKEY_ENTRY @@ -75,19 +86,36 @@ struct CASC_CKEY_ENTRY StorageOffset = CASC_INVALID_OFFS64; EncodedSize = CASC_INVALID_SIZE; ContentSize = CASC_INVALID_SIZE; + SpanCount = 1; + } + + bool IsFile() + { + // Must not be a folder entry + if((Flags & CASC_CE_FOLDER_ENTRY) == 0) + { + // There can be entries that are both file span or the standalone file + // * zone/zm_red.xpak - { zone/zm_red.xpak_1, zone/zm_red.xpak_2, ..., zone/zm_red.xpak_6 } + if(RefCount != 0) + return true; + + // To include the file, it must either be present in ENCODING, DOWNLOAD or in BUILD file + if(((Flags & CASC_CE_FILE_SPAN) == 0) && (Flags & (CASC_CE_IN_ENCODING | CASC_CE_IN_DOWNLOAD | CASC_CE_IN_BUILD))) + return true; + } + return false; } BYTE CKey[MD5_HASH_SIZE]; // Content key of the full length BYTE EKey[MD5_HASH_SIZE]; // Encoded key of the full length ULONGLONG StorageOffset; // Linear offset over the entire storage. 0 if not present ULONGLONG TagBitMask; // Bitmap for the tags. 0 ig tags are not supported - DWORD EncodedSize; // Encoded size of the file. 0 if not supported - DWORD ContentSize; // Content size of the file. 0 if not supported + DWORD ContentSize; // Content size of the file + DWORD EncodedSize; // Encoded size of the file DWORD Flags; // See CASC_CE_XXX USHORT RefCount; // This is the number of file names referencing this entry + BYTE SpanCount; // Number of spans for the file BYTE Priority; // Download priority - BYTE Alignment; - }; typedef CASC_CKEY_ENTRY *PCASC_CKEY_ENTRY; @@ -112,7 +140,22 @@ extern unsigned char IntToHexChar[]; // #define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type)) -#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type)) + +template <typename T> +T * CASC_ALLOC(size_t nCount) +{ + return (T *)malloc(nCount * sizeof(T)); +} + +template <typename T> +T * CASC_ALLOC_ZERO(size_t nCount) +{ + T * ptr = CASC_ALLOC<T>(nCount); + + if(ptr != NULL) + memset(ptr, 0, sizeof(T) * nCount); + return ptr; +} template <typename T> void CASC_FREE(T *& ptr) @@ -241,12 +284,14 @@ inline void CopyMemory16(void * Target, void * Source) } //----------------------------------------------------------------------------- -// Linear data stream manipulation +// Capturing various integral values +LPBYTE CaptureInteger16_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue); LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue); LPBYTE CaptureInteger32_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue); LPBYTE CaptureByteArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nLength, LPBYTE pbOutput); LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrCKey); +LPBYTE CaptureEncodedKey(LPBYTE pbEKey, LPBYTE pbData, BYTE EKeyLength); LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount); #define CaptureArray(pbDataPtr, pbDataEnd, PtrArray, type, count) CaptureArray_(pbDataPtr, pbDataEnd, PtrArray, sizeof(type), count) @@ -277,7 +322,6 @@ LPTSTR CombinePath(LPCTSTR szPath, LPCTSTR szSubDir); LPTSTR GetLastPathPart(LPTSTR szWorkPath); bool CutLastPathPart(TCHAR * szWorkPath); -size_t CreateCascSubdirectoryName(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szSubDir, const TCHAR * szExtension, LPBYTE pbEKey); size_t NormalizeFileName_UpperBkSlash(char * szNormName, const char * szFileName, size_t cchMaxChars); size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, size_t cchMaxChars); @@ -288,8 +332,31 @@ int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue); int ConvertStringToInt08(const char * szString, PDWORD PtrValue); int ConvertStringToInt32(const TCHAR * szString, size_t nMaxDigits, PDWORD PtrValue); int ConvertStringToBinary(const char * szString, size_t nMaxDigits, LPBYTE pbBinary); -char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer); -char * StringFromMD5(LPBYTE md5, char * szBuffer); + +//----------------------------------------------------------------------------- +// Conversion from binary array to string. The caller must ensure that +// the buffer has at least ((cbBinary * 2) + 1) characters + +template <typename xchar> +xchar * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, xchar * szBuffer) +{ + xchar * szSaveBuffer = szBuffer; + + // Verify the binary pointer + if(pbBinary && cbBinary) + { + // Convert the bytes to string array + for(size_t i = 0; i < cbBinary; i++) + { + *szBuffer++ = IntToHexChar[pbBinary[i] >> 0x04]; + *szBuffer++ = IntToHexChar[pbBinary[i] & 0x0F]; + } + } + + // Terminate the string + *szBuffer = 0; + return szSaveBuffer; +} //----------------------------------------------------------------------------- // Structure query key diff --git a/dep/CascLib/src/common/Csv.cpp b/dep/CascLib/src/common/Csv.cpp index 24b654daa86..c665fc00c4c 100644 --- a/dep/CascLib/src/common/Csv.cpp +++ b/dep/CascLib/src/common/Csv.cpp @@ -185,10 +185,10 @@ CASC_CSV::~CASC_CSV() m_szCsvFile = NULL; } -int CASC_CSV::Load(const TCHAR * szFileName) +DWORD CASC_CSV::Load(const TCHAR * szFileName) { DWORD cbFileData = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; m_szCsvFile = (char *)LoadFileToMemory(szFileName, &cbFileData); if (m_szCsvFile != NULL) @@ -199,19 +199,19 @@ int CASC_CSV::Load(const TCHAR * szFileName) m_nCsvFile = cbFileData; // Parse the data - nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + dwErrCode = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; } else { - nError = GetLastError(); + dwErrCode = GetLastError(); } - return nError; + return dwErrCode; } -int CASC_CSV::Load(LPBYTE pbData, size_t cbData) +DWORD CASC_CSV::Load(LPBYTE pbData, size_t cbData) { - int nError = ERROR_NOT_ENOUGH_MEMORY; + DWORD dwErrCode = ERROR_NOT_ENOUGH_MEMORY; m_szCsvFile = new char[cbData + 1]; if (m_szCsvFile != NULL) @@ -223,10 +223,10 @@ int CASC_CSV::Load(LPBYTE pbData, size_t cbData) m_nCsvFile = cbData; // Parse the data - nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + dwErrCode = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; } - return nError; + return dwErrCode; } bool CASC_CSV::LoadNextLine() diff --git a/dep/CascLib/src/common/Csv.h b/dep/CascLib/src/common/Csv.h index bf1a7413e13..394eab1e2f0 100644 --- a/dep/CascLib/src/common/Csv.h +++ b/dep/CascLib/src/common/Csv.h @@ -69,8 +69,8 @@ class CASC_CSV CASC_CSV(size_t nLinesMax, bool bHasHeader); ~CASC_CSV(); - int Load(LPBYTE pbData, size_t cbData); - int Load(const TCHAR * szFileName); + DWORD Load(LPBYTE pbData, size_t cbData); + DWORD Load(const TCHAR * szFileName); bool LoadNextLine(); const CASC_CSV_COLUMN & operator[](const char * szColumnName) const; diff --git a/dep/CascLib/src/common/FileStream.cpp b/dep/CascLib/src/common/FileStream.cpp index 845e793a6fa..cd3300a46a7 100644 --- a/dep/CascLib/src/common/FileStream.cpp +++ b/dep/CascLib/src/common/FileStream.cpp @@ -615,7 +615,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD HINTERNET hRequest; DWORD dwTemp = 0; bool bFileAvailable = false; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Check alternate ports if(dwStreamFlags & STREAM_FLAG_USE_PORT_1119) @@ -625,10 +625,10 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD // Don't download if we are not connected to the internet if(!InternetGetConnectedState(&dwTemp, 0)) - nError = GetLastError(); + dwErrCode = GetLastError(); // Initiate the connection to the internet - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { pStream->Base.Http.hInternet = InternetOpen(_T("agent/2.17.2.6700"), INTERNET_OPEN_TYPE_PRECONFIG, @@ -636,11 +636,11 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD NULL, 0); if(pStream->Base.Http.hInternet == NULL) - nError = GetLastError(); + dwErrCode = GetLastError(); } // Connect to the server - if(nError == ERROR_SUCCESS) + if(dwErrCode == ERROR_SUCCESS) { TCHAR szServerName[MAX_PATH]; DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE; @@ -656,11 +656,11 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwFlags, 0); if(pStream->Base.Http.hConnect == NULL) - nError = GetLastError(); + dwErrCode = GetLastError(); } // Now try to query the file size - if(nError == ERROR_SUCCESS) + if(dwErrCode == 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); @@ -702,7 +702,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD } else { - nError = ERROR_FILE_NOT_FOUND; + dwErrCode = ERROR_FILE_NOT_FOUND; } } InternetCloseHandle(hRequest); @@ -714,7 +714,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD if(bFileAvailable == false) { pStream->BaseClose(pStream); - SetLastError(nError); + SetLastError(dwErrCode); return false; } @@ -889,7 +889,7 @@ static bool BlockStream_Read( BlockBufferOffset = (DWORD)(ByteOffset & (BlockSize - 1)); // Allocate buffer for reading blocks - TransferBuffer = BlockBuffer = CASC_ALLOC(BYTE, (BlockCount * BlockSize)); + TransferBuffer = BlockBuffer = CASC_ALLOC<BYTE>(BlockCount * BlockSize); if(TransferBuffer == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); @@ -1054,7 +1054,7 @@ static TFileStream * AllocateFileStream( } // Allocate the stream structure for the given stream type - pStream = (TFileStream *)CASC_ALLOC(BYTE, StreamSize + FileNameSize + sizeof(TCHAR)); + pStream = (TFileStream *)CASC_ALLOC<BYTE>(StreamSize + FileNameSize + sizeof(TCHAR)); if(pStream != NULL) { // Zero the entire structure @@ -1137,7 +1137,7 @@ static bool FlatStream_LoadBitmap(TBlockStream * pStream) if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize) { // Allocate space for the bitmap - FileBitmap = CASC_ALLOC(BYTE, BitmapSize); + FileBitmap = CASC_ALLOC<BYTE>(BitmapSize); if(FileBitmap != NULL) { // Load the bitmap bits @@ -1349,12 +1349,11 @@ static bool FlatStream_CreateMirror(TBlockStream * pStream) } // Allocate the bitmap array - FileBitmap = CASC_ALLOC(BYTE, dwBitmapSize); + FileBitmap = CASC_ALLOC_ZERO<BYTE>(dwBitmapSize); if(FileBitmap == NULL) return false; // Initialize the bitmap - memset(FileBitmap, 0, dwBitmapSize); pStream->FileBitmap = FileBitmap; pStream->BitmapSize = dwBitmapSize; pStream->BlockSize = DEFAULT_BLOCK_SIZE; @@ -1521,7 +1520,7 @@ static bool PartStream_LoadBitmap(TBlockStream * pStream) if((ByteOffset + BitmapSize) < pStream->Base.File.FileSize) { // Allocate space for the array of PART_FILE_MAP_ENTRY - FileBitmap = CASC_ALLOC(PART_FILE_MAP_ENTRY, BlockCount); + FileBitmap = CASC_ALLOC<PART_FILE_MAP_ENTRY>(BlockCount); if(FileBitmap != NULL) { // Load the block map @@ -1771,12 +1770,11 @@ static bool PartStream_CreateMirror(TBlockStream * pStream) } // Allocate the bitmap array - FileBitmap = CASC_ALLOC(BYTE, dwBitmapSize); + FileBitmap = CASC_ALLOC_ZERO<BYTE>(dwBitmapSize); if(FileBitmap == NULL) return false; // Initialize the bitmap - memset(FileBitmap, 0, dwBitmapSize); pStream->FileBitmap = FileBitmap; pStream->BitmapSize = dwBitmapSize; pStream->BlockSize = DEFAULT_BLOCK_SIZE; @@ -2268,7 +2266,7 @@ static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamF pStream->BlockRead = (BLOCK_READ)Block4Stream_BlockRead; // Allocate work space for numeric names - szNameBuff = CASC_ALLOC(TCHAR, nNameLength + 4); + szNameBuff = CASC_ALLOC<TCHAR>(nNameLength + 4); if(szNameBuff != NULL) { // Set the base flags @@ -2283,7 +2281,7 @@ static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamF break; // If the open succeeded, we re-allocate the base provider array - NewBaseArray = CASC_ALLOC(TBaseProviderData, dwBaseFiles + 1); + NewBaseArray = CASC_ALLOC<TBaseProviderData>(dwBaseFiles + 1); if(NewBaseArray == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); @@ -2369,7 +2367,7 @@ static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamF */ TFileStream * FileStream_CreateFile( - const TCHAR * szFileName, + LPCTSTR szFileName, DWORD dwStreamFlags) { TFileStream * pStream; @@ -2424,7 +2422,7 @@ TFileStream * FileStream_CreateFile( */ TFileStream * FileStream_OpenFile( - const TCHAR * szFileName, + LPCTSTR szFileName, DWORD dwStreamFlags) { DWORD dwProvider = dwStreamFlags & STREAM_PROVIDERS_MASK; @@ -2473,7 +2471,7 @@ const TCHAR * FileStream_GetFileName(TFileStream * pStream) * \a pdwStreamProvider Pointer to a DWORD variable that receives stream provider (STREAM_PROVIDER_XXX) */ -size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider) +size_t FileStream_Prefix(LPCTSTR szFileName, DWORD * pdwProvider) { size_t nPrefixLength1 = 0; size_t nPrefixLength2 = 0; @@ -2592,14 +2590,6 @@ bool FileStream_SetCallback(TFileStream * pStream, STREAM_DOWNLOAD_CALLBACK pfnC */ bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead) { - //FILE * fp = fopen("E:\\Loading.txt", "at"); - //if(fp != NULL) - //{ - // ULONGLONG ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : 0; - // fprintf(fp, "%-32ws\t%08X\t%08X\n", GetPlainFileName(pStream->szFileName), (ULONG)ByteOffset, dwBytesToRead); - // fclose(fp); - //} - assert(pStream->StreamRead != NULL); return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead); } diff --git a/dep/CascLib/src/common/FileStream.h b/dep/CascLib/src/common/FileStream.h index a2b0c5b9f7d..e934cca062f 100644 --- a/dep/CascLib/src/common/FileStream.h +++ b/dep/CascLib/src/common/FileStream.h @@ -247,10 +247,10 @@ struct TEncryptedStream : public TBlockStream //----------------------------------------------------------------------------- // Public functions for file stream -TFileStream * FileStream_CreateFile(const TCHAR * szFileName, DWORD dwStreamFlags); -TFileStream * FileStream_OpenFile(const TCHAR * szFileName, DWORD dwStreamFlags); +TFileStream * FileStream_CreateFile(LPCTSTR szFileName, DWORD dwStreamFlags); +TFileStream * FileStream_OpenFile(LPCTSTR szFileName, DWORD dwStreamFlags); const TCHAR * FileStream_GetFileName(TFileStream * pStream); -size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider); +size_t FileStream_Prefix(LPCTSTR szFileName, DWORD * pdwProvider); bool FileStream_SetCallback(TFileStream * pStream, STREAM_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData); diff --git a/dep/CascLib/src/common/FileTree.cpp b/dep/CascLib/src/common/FileTree.cpp index f753c25f263..ceb578cf578 100644 --- a/dep/CascLib/src/common/FileTree.cpp +++ b/dep/CascLib/src/common/FileTree.cpp @@ -30,28 +30,7 @@ inline void SET_NODE_INT32(void * node, size_t offset, DWORD value) PtrValue[0] = value; } -/* -static bool CompareFileNode(void * pvObject, void * pvUserData) -{ - PCASC_COMPARE_CONTEXT pCtx = (PCASC_COMPARE_CONTEXT)pvUserData; - PCASC_FILE_TREE pFileTree = (PCASC_FILE_TREE)pCtx->pThis; - PCASC_FILE_NODE pFileNode = (PCASC_FILE_NODE)pvObject; - char szFullPath[MAX_PATH]; - // First of all, the name hash must match - if(pFileNode->FileNameHash == pCtx->FileNameHash) - { - // Then also compare the full path name - pFileTree->PathAt(szFullPath, _countof(szFullPath), pFileNode); - if(!_stricmp(szFullPath, pCtx->szFileName)) - { - return true; - } - } - - return false; -} -*/ //----------------------------------------------------------------------------- // Protected functions @@ -224,11 +203,11 @@ bool CASC_FILE_TREE::RebuildNameMaps() //----------------------------------------------------------------------------- // Public functions -int CASC_FILE_TREE::Create(DWORD Flags) +DWORD CASC_FILE_TREE::Create(DWORD Flags) { PCASC_FILE_NODE pRootNode; size_t FileNodeSize = FIELD_OFFSET(CASC_FILE_NODE, ExtraValues); - int nError; + DWORD dwErrCode; // Initialize the file tree memset(this, 0, sizeof(CASC_FILE_TREE)); @@ -242,9 +221,9 @@ int CASC_FILE_TREE::Create(DWORD Flags) FileNodeSize += sizeof(DWORD); // Create the array for FileDataId -> CASC_FILE_NODE - nError = FileDataIds.Create<PCASC_FILE_NODE>(START_ITEM_COUNT); - if(nError != ERROR_SUCCESS) - return nError; + dwErrCode = FileDataIds.Create<PCASC_FILE_NODE>(START_ITEM_COUNT); + if(dwErrCode != ERROR_SUCCESS) + return dwErrCode; } // Shall we use the locale ID in the tree node? @@ -264,12 +243,12 @@ int CASC_FILE_TREE::Create(DWORD Flags) FileNodeSize = ALIGN_TO_SIZE(FileNodeSize, 8); // Initialize the dynamic array - nError = NodeTable.Create(FileNodeSize, START_ITEM_COUNT); - if(nError == ERROR_SUCCESS) + dwErrCode = NodeTable.Create(FileNodeSize, START_ITEM_COUNT); + if(dwErrCode == ERROR_SUCCESS) { // Create the dynamic array that will hold the node names - nError = NameTable.Create<char>(START_ITEM_COUNT); - if(nError == ERROR_SUCCESS) + dwErrCode = NameTable.Create<char>(START_ITEM_COUNT); + if(dwErrCode == ERROR_SUCCESS) { // Insert the first "root" node, without name pRootNode = (PCASC_FILE_NODE)NodeTable.Insert(1); @@ -287,8 +266,8 @@ int CASC_FILE_TREE::Create(DWORD Flags) // Create both maps if(!RebuildNameMaps()) - nError = ERROR_NOT_ENOUGH_MEMORY; - return nError; + dwErrCode = ERROR_NOT_ENOUGH_MEMORY; + return dwErrCode; } void CASC_FILE_TREE::Free() @@ -307,21 +286,26 @@ void CASC_FILE_TREE::Free() PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) { - CASC_COMPARE_CONTEXT CmpCtx; PCASC_FILE_NODE pFileNode; + ULONGLONG FileNameHash; // Sanity checks assert(szFileName != NULL && szFileName[0] != 0); assert(pCKeyEntry != NULL); + //char szCKey[MD5_STRING_SIZE+1]; + //char szEKey[MD5_STRING_SIZE+1]; + //StringFromBinary(pCKeyEntry->CKey, MD5_HASH_SIZE, szCKey); + //StringFromBinary(pCKeyEntry->EKey, MD5_HASH_SIZE, szEKey); + //printf("%s\t%s\t%s\n", szCKey, szEKey, szFileName); + + //BREAK_ON_XKEY3(pCKeyEntry->EKey, 0x03, 0xDC, 0x7D); + // Calculate the file name hash - CmpCtx.FileNameHash = CalcFileNameHash(szFileName); -// CmpCtx.szFileName = szFileName; -// CmpCtx.pThis = this; + FileNameHash = CalcFileNameHash(szFileName); // Do nothing if the file name is there already. -// pFileNode = (PCASC_FILE_NODE)NameMap.FindObjectEx(CompareFileNode, &CmpCtx); - pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&CmpCtx.FileNameHash); + pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash); if(pFileNode == NULL) { // Insert new item @@ -329,7 +313,7 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const if(pFileNode != NULL) { // Supply the name hash - pFileNode->FileNameHash = CmpCtx.FileNameHash; + pFileNode->FileNameHash = FileNameHash; // Set the file data id and the extra values SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags); @@ -340,12 +324,13 @@ PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const // Also make sure that it's in the file data id table, if the table is initialized InsertToIdTable(pFileNode); - // Set the file name of the new file node. This also increments the number of references + // Set the file name of the new file node SetNodeFileName(pFileNode, szFileName); // If we created a new node, we need to increment the reference count assert(pCKeyEntry->RefCount != 0xFFFF); pCKeyEntry->RefCount++; + FileNodes++; } } @@ -492,8 +477,6 @@ PCASC_FILE_NODE CASC_FILE_TREE::Find(const char * szFullPath, DWORD FileDataId, if(pFileNode != NULL && pFindData != NULL) { GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags); - pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0); - pFindData->bCanOpenByDataId = (FileDataIdOffset != 0); } return pFileNode; @@ -543,7 +526,85 @@ bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szF { ULONGLONG FileNameHash = 0; PCASC_FILE_NODE pFolderNode = NULL; - const char * szNodeBegin = szFileName; + CASC_PATH<char> PathBuffer; + LPCSTR szNodeBegin = szFileName; + size_t nFileNode = NodeTable.IndexOf(pFileNode); + size_t i; + DWORD Parent = 0; + + // Sanity checks + assert(szFileName != NULL && szFileName[0] != 0); + + // Traverse the entire path. For each subfolder, we insert an appropriate fake entry + for(i = 0; szFileName[i] != 0; i++) + { + char chOneChar = szFileName[i]; + + // Is there a path separator? + // Note: Warcraft III paths may contain "mount points". + // Example: "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" + if(chOneChar == '\\' || chOneChar == '/' || chOneChar == ':') + { + // Calculate hash of the file name up to the end of the node name + FileNameHash = CalcNormNameHash(PathBuffer, i); + + // If the entry is not there yet, create new one + if((pFolderNode = Find(FileNameHash)) == NULL) + { + // Insert new entry to the tree + pFolderNode = InsertNew(); + if(pFolderNode == NULL) + return false; + + // Populate the file entry + pFolderNode->FileNameHash = FileNameHash; + pFolderNode->Parent = Parent; + pFolderNode->Flags |= (chOneChar == ':') ? (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT) : CFN_FLAG_FOLDER; + FolderNodes++; + + // Set the node sub name to the node + SetNodePlainName(pFolderNode, szNodeBegin, szFileName + i); + + // Insert the entry to the name map + InsertToHashTable(pFolderNode); + } + + // Move the parent to the current node + Parent = (DWORD)NodeTable.IndexOf(pFolderNode); + + // Move the begin of the node after the separator + szNodeBegin = szFileName + i + 1; + } + + // Copy the next character, even if it was slash/backslash before + PathBuffer.AppendChar(AsciiToUpperTable_BkSlash[chOneChar]); + } + + // If anything left, this is gonna be our node name + if(szNodeBegin < szFileName + i) + { + // We need to reset the file node pointer, as the file node table might have changed + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nFileNode); + + // Write the plain file name to the node + SetNodePlainName(pFileNode, szNodeBegin, szFileName + i); + pFileNode->Parent = Parent; + + // Also insert the node to the hash table so CascOpenFile can find it + if(pFileNode->FileNameHash == 0) + { + pFileNode->FileNameHash = CalcNormNameHash(PathBuffer, i); + InsertToHashTable(pFileNode); + } + } + return true; +} +/* +bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName) +{ + ULONGLONG FileNameHash = 0; + PCASC_FILE_NODE pFolderNode = NULL; + LPCSTR szNodeBegin = szFileName; char szPathBuffer[MAX_PATH+1]; size_t nFileNode = NodeTable.IndexOf(pFileNode); size_t i; @@ -551,7 +612,6 @@ bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szF // Sanity checks assert(szFileName != NULL && szFileName[0] != 0); - assert(pFileNode->pCKeyEntry != NULL); // Traverse the entire path. For each subfolder, we insert an appropriate fake entry for(i = 0; szFileName[i] != 0; i++) @@ -577,8 +637,8 @@ bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szF // Populate the file entry pFolderNode->FileNameHash = FileNameHash; pFolderNode->Parent = Parent; - pFolderNode->Flags |= (chOneChar == ':') ? CFN_FLAG_MOUNT_POINT : 0; - pFolderNode->Flags |= CFN_FLAG_FOLDER; + pFolderNode->Flags |= (chOneChar == ':') ? (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT) : CFN_FLAG_FOLDER; + FolderNodes++; // Set the node sub name to the node SetNodePlainName(pFolderNode, szNodeBegin, szFileName + i); @@ -604,12 +664,20 @@ bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szF // We need to reset the file node pointer, as the file node table might have changed pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nFileNode); + // Write the plain file name to the node SetNodePlainName(pFileNode, szNodeBegin, szFileName + i); pFileNode->Parent = Parent; + + // Also insert the node to the hash table so CascOpenFile can find it + if(pFileNode->FileNameHash == 0) + { + pFileNode->FileNameHash = CalcNormNameHash(szPathBuffer, i); + InsertToHashTable(pFileNode); + } } return true; } - +*/ size_t CASC_FILE_TREE::GetMaxFileIndex() { if(FileDataIds.IsInitialized()) diff --git a/dep/CascLib/src/common/FileTree.h b/dep/CascLib/src/common/FileTree.h index 904da6c4c46..38bc4e07e9f 100644 --- a/dep/CascLib/src/common/FileTree.h +++ b/dep/CascLib/src/common/FileTree.h @@ -25,32 +25,25 @@ typedef struct _CASC_FILE_NODE { ULONGLONG FileNameHash; // Jenkins hash of the normalized file name (uppercase, backslashes) - PCASC_CKEY_ENTRY pCKeyEntry; // Pointer to the CKey entry. + PCASC_CKEY_ENTRY pCKeyEntry; // Pointer to the CKey entry + DWORD Parent; // The index of a parent directory. If CASC_INVALID_INDEX, then this is the root item DWORD NameIndex; // Index of the node name. If CASC_INVALID_INDEX, then this node has no name USHORT NameLength; // Length of the node name (without the zero terminator) - USHORT Flags; // See CFE_FLAG_XXX + USHORT Flags; // See CFN_FLAG_XXX DWORD ExtraValues[4]; // FileDataId: Only if FTREE_FLAG_USE_DATA_ID specified at create // LocaleFlags: Only if FTREE_FLAG_USE_LOCALE_FLAGS specified at create // ContentFlags: Only if FTREE_FLAG_USE_CONTENT_FLAGS specified at create } CASC_FILE_NODE, *PCASC_FILE_NODE; -// Common structure for comparing a file node -typedef struct _CASC_COMPARE_CONTEXT -{ - ULONGLONG FileNameHash; - const char * szFileName; - void * pThis; -} CASC_COMPARE_CONTEXT, *PCASC_COMPARE_CONTEXT; - // Main structure for the file tree class CASC_FILE_TREE { public: // Initializes/destroys the entire tree - int Create(DWORD Flags = 0); + DWORD Create(DWORD Flags = 0); void Free(); // Inserts a new node to the tree; either with name or nameless @@ -108,6 +101,8 @@ class CASC_FILE_TREE size_t FileDataIdOffset; // If nonzero, this is the offset of the "FileDataId" field in the CASC_FILE_NODE size_t LocaleFlagsOffset; // If nonzero, this is the offset of the "LocaleFlags" field in the CASC_FILE_NODE size_t ContentFlagsOffset; // If nonzero, this is the offset of the "ContentFlags" field in the CASC_FILE_NODE + size_t FolderNodes; // Number of folder nodes + size_t FileNodes; // Number of file nodes DWORD KeyLength; // Actual length of the key supported by the root handler }; diff --git a/dep/CascLib/src/common/ListFile.cpp b/dep/CascLib/src/common/ListFile.cpp index 36607d39186..a2239e18fa3 100644 --- a/dep/CascLib/src/common/ListFile.cpp +++ b/dep/CascLib/src/common/ListFile.cpp @@ -36,7 +36,7 @@ static PLISTFILE_CACHE ListFile_CreateCache(DWORD dwFileSize) PLISTFILE_CACHE pCache; // Allocate cache for one file block - pCache = (PLISTFILE_CACHE)CASC_ALLOC(BYTE, sizeof(LISTFILE_CACHE) + dwFileSize); + pCache = (PLISTFILE_CACHE)CASC_ALLOC<BYTE>(sizeof(LISTFILE_CACHE) + dwFileSize); if(pCache != NULL) { // Set the initial pointers @@ -254,7 +254,7 @@ size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PD PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile; const char * szTemp; size_t nLength = 0; - int nError = ERROR_SUCCESS; + DWORD dwErrCode = ERROR_SUCCESS; // Check for parameters for(;;) @@ -266,12 +266,12 @@ size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PD if(pCache->Flags & LISTFILE_FLAG_USES_FILEDATAID) { // Retrieve the data ID from the current position - nError = ListFile_GetFileDataId(pCache, &FileDataId); - if(nError == ERROR_NO_MORE_FILES) + dwErrCode = ListFile_GetFileDataId(pCache, &FileDataId); + if(dwErrCode == ERROR_NO_MORE_FILES) break; // If there was an error, skip the current line - if(nError != ERROR_SUCCESS || FileDataId == CASC_INVALID_ID) + if(dwErrCode != ERROR_SUCCESS || FileDataId == CASC_INVALID_ID) { ListFile_GetNextLine(pvListFile, &szTemp, &szTemp); continue; @@ -282,7 +282,7 @@ size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PD nLength = ListFile_GetNextLine(pvListFile, szBuffer, nMaxChars); if(nLength == 0) { - nError = GetLastError(); + dwErrCode = GetLastError(); break; } @@ -291,8 +291,8 @@ size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PD return nLength; } - if(nError != ERROR_SUCCESS) - SetLastError(nError); + if(dwErrCode != ERROR_SUCCESS) + SetLastError(dwErrCode); return nLength; } diff --git a/dep/CascLib/src/common/Map.h b/dep/CascLib/src/common/Map.h index 13799eb0528..a6eface844d 100644 --- a/dep/CascLib/src/common/Map.h +++ b/dep/CascLib/src/common/Map.h @@ -129,13 +129,8 @@ class CASC_MAP return ERROR_NOT_ENOUGH_MEMORY; // Allocate new map for the objects - m_HashTable = (void **)CASC_ALLOC(void *, m_HashTableSize); - if(m_HashTable == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Initialize the map object - memset(m_HashTable, 0, m_HashTableSize * sizeof(void *)); - return ERROR_SUCCESS; + m_HashTable = (void **)CASC_ALLOC_ZERO<void *>(m_HashTableSize); + return (m_HashTable != NULL) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; } void * FindObject(void * pvKey, PDWORD PtrIndex = NULL) diff --git a/dep/CascLib/src/common/Path.h b/dep/CascLib/src/common/Path.h new file mode 100644 index 00000000000..b77dde0cc3d --- /dev/null +++ b/dep/CascLib/src/common/Path.h @@ -0,0 +1,182 @@ +/*****************************************************************************/ +/* Path.h Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* Global path merger class */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 24.07.17 1.00 Lad Created */ +/*****************************************************************************/ + +#ifndef __CASC_PATH_H__ +#define __CASC_PATH_H__ + +//----------------------------------------------------------------------------- +// Structures + +template <typename xchar> +struct CASC_PATH +{ + CASC_PATH(xchar chSeparator = PATH_SEP_CHAR) + { + m_szBufferBegin = m_szBufferPtr = m_Buffer; + m_szBufferEnd = m_szBufferBegin + _countof(m_Buffer); + m_chSeparator = chSeparator; + m_Buffer[0] = 0; + } + + ~CASC_PATH() + { + if(m_szBufferBegin != m_Buffer) + { + CASC_FREE(m_szBufferBegin); + } + } + + // LPCTSTR szPath = Path; + operator const xchar *() + { + return m_szBufferBegin; + } + + // LPTSTR szPath = Path.New(); + xchar * New() + { + xchar * szNewStr; + + if((szNewStr = CASC_ALLOC<xchar>(Length() + 1)) != NULL) + { + memcpy(szNewStr, m_szBufferBegin, Length() * sizeof(xchar)); + szNewStr[Length()] = 0; + } + + return szNewStr; + } + + size_t Save() + { + return (m_szBufferPtr - m_szBufferBegin); + } + + bool Restore(size_t nSavePos) + { + if((m_szBufferBegin + nSavePos) < m_szBufferEnd) + { + m_szBufferPtr = m_szBufferBegin + nSavePos; + m_szBufferPtr[0] = 0; + return true; + } + + return false; + } + + // Path.Copy(szBuffer, _countof(szBuffer)); + bool Copy(xchar * szBuffer, size_t cchBuffer) + { + if((Length() + 1) > cchBuffer) + return false; + + memcpy(szBuffer, m_szBufferBegin, Length() * sizeof(xchar)); + szBuffer[Length()] = 0; + return true; + } + + size_t Length() + { + return m_szBufferPtr - m_szBufferBegin; + } + + bool SetPathRoot(const xchar * szRoot) + { + // Make sure that there is no characters + m_szBufferPtr = m_szBufferBegin; + m_szBufferPtr[0] = 0; + + // Append the root path + return AppendString(szRoot, false); + } + + bool AppendStringN(const xchar * szString, size_t nMaxchars, bool bWithSeparator) + { + const xchar * szStringEnd = szString + nMaxchars; + xchar chOneChar; + + if(szString && szString[0] && nMaxchars) + { + // Append separator, if required and not in begin of the string + if(m_szBufferPtr > m_szBufferBegin && bWithSeparator) + AppendChar(m_chSeparator); + + // Append the characters from the string + while(szString[0] && szString < szStringEnd) + { + // Retrieve the single character + chOneChar = *szString++; + + // Normalize the character + if(chOneChar == '/' || chOneChar == '\\') + chOneChar = m_chSeparator; + + if(!AppendChar(chOneChar)) + break; + } + } + + return true; + } + + bool AppendString(const xchar * szString, bool bWithSeparator) + { + return AppendStringN(szString, (0x10000 / sizeof(xchar)), bWithSeparator); + } + + bool AppendEKey(LPBYTE pbEKey) + { + xchar szEKey[MD5_STRING_SIZE + 1]; + + StringFromBinary(pbEKey, MD5_HASH_SIZE, szEKey); + AppendStringN(szEKey, 2, true); + AppendStringN(szEKey+2, 2, true); + return AppendString(szEKey, true); + } + + bool AppendChar(xchar chOneChar) + { + // Buffer our of space? + if((m_szBufferPtr + 2) >= m_szBufferEnd) + { + xchar * szOldBuffer = m_szBufferBegin; + xchar * szNewBuffer; + size_t nToAllocate = (m_szBufferEnd - m_szBufferBegin) * 2; + size_t nLength = (m_szBufferPtr - m_szBufferBegin); + + if((szNewBuffer = CASC_ALLOC<xchar>(nToAllocate)) == NULL) + return false; + + // Copy the chars + memcpy(szNewBuffer, m_szBufferBegin, (m_szBufferPtr - m_szBufferBegin) * sizeof(xchar)); + m_szBufferBegin = szNewBuffer; + m_szBufferPtr = m_szBufferBegin + nLength; + m_szBufferEnd = m_szBufferBegin + nToAllocate; + + // Free the old buffer + if(szOldBuffer != m_Buffer) + CASC_FREE(szOldBuffer); + } + + // Append the character + *m_szBufferPtr++ = chOneChar; + m_szBufferPtr[0] = 0; + return true; + } + + protected: + + xchar * m_szBufferBegin; + xchar * m_szBufferPtr; + xchar * m_szBufferEnd; + xchar m_Buffer[MAX_PATH]; + xchar m_chSeparator; +}; + +#endif // __CASC_PATH_H__ diff --git a/dep/CascLib/src/common/RootHandler.cpp b/dep/CascLib/src/common/RootHandler.cpp index 89b693f55cb..b33f96209d9 100644 --- a/dep/CascLib/src/common/RootHandler.cpp +++ b/dep/CascLib/src/common/RootHandler.cpp @@ -81,10 +81,6 @@ PCASC_CKEY_ENTRY TFileTreeRoot::Search(TCascSearch * pSearch, PCASC_FIND_DATA pF // Retrieve the extra values (FileDataId, file size and locale flags) FileTree.GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags); - // Supply the bCanOpenByDataId variable - pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0); - pFindData->bCanOpenByDataId = (pFindData->dwFileDataId != CASC_INVALID_ID); - // Return the found CKey entry return pFileNode->pCKeyEntry; } diff --git a/dep/PackageList.txt b/dep/PackageList.txt index 6a2cced5052..9d8561fefea 100644 --- a/dep/PackageList.txt +++ b/dep/PackageList.txt @@ -62,7 +62,7 @@ recastnavigation (Recast is state of the art navigation mesh construction toolse CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014) https://github.com/ladislav-zezula/CascLib - Version: b802391cfb9418d5550dc1671f18da1ca1c445d4 + Version: b91f87c770c78340dcd96df970e55b5c0469e884 rapidjson (A fast JSON parser/generator for C++ with both SAX/DOM style API http://rapidjson.org/) https://github.com/miloyip/rapidjson |